CellLayout.java revision a5902524d4403885eb4c50360bf3465c6be796ef
1/* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.launcher2; 18 19import android.content.Context; 20import android.content.res.TypedArray; 21import android.graphics.Rect; 22import android.graphics.RectF; 23import android.util.AttributeSet; 24import android.view.ContextMenu; 25import android.view.MotionEvent; 26import android.view.View; 27import android.view.ViewDebug; 28import android.view.ViewGroup; 29 30import java.util.ArrayList; 31 32public class CellLayout extends ViewGroup { 33 private boolean mPortrait; 34 35 private int mCellWidth; 36 private int mCellHeight; 37 38 private int mLongAxisStartPadding; 39 private int mLongAxisEndPadding; 40 41 private int mShortAxisStartPadding; 42 private int mShortAxisEndPadding; 43 44 private int mShortAxisCells; 45 private int mLongAxisCells; 46 47 private int mWidthGap; 48 private int mHeightGap; 49 50 private final Rect mRect = new Rect(); 51 private final CellInfo mCellInfo = new CellInfo(); 52 53 int[] mCellXY = new int[2]; 54 55 boolean[][] mOccupied; 56 57 private RectF mDragRect = new RectF(); 58 59 private boolean mDirtyTag; 60 61 public CellLayout(Context context) { 62 this(context, null); 63 } 64 65 public CellLayout(Context context, AttributeSet attrs) { 66 this(context, attrs, 0); 67 } 68 69 public CellLayout(Context context, AttributeSet attrs, int defStyle) { 70 super(context, attrs, defStyle); 71 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0); 72 73 mCellWidth = a.getDimensionPixelSize(R.styleable.CellLayout_cellWidth, 10); 74 mCellHeight = a.getDimensionPixelSize(R.styleable.CellLayout_cellHeight, 10); 75 76 mLongAxisStartPadding = 77 a.getDimensionPixelSize(R.styleable.CellLayout_longAxisStartPadding, 10); 78 mLongAxisEndPadding = 79 a.getDimensionPixelSize(R.styleable.CellLayout_longAxisEndPadding, 10); 80 mShortAxisStartPadding = 81 a.getDimensionPixelSize(R.styleable.CellLayout_shortAxisStartPadding, 10); 82 mShortAxisEndPadding = 83 a.getDimensionPixelSize(R.styleable.CellLayout_shortAxisEndPadding, 10); 84 85 mShortAxisCells = a.getInt(R.styleable.CellLayout_shortAxisCells, 4); 86 mLongAxisCells = a.getInt(R.styleable.CellLayout_longAxisCells, 4); 87 88 a.recycle(); 89 90 setAlwaysDrawnWithCacheEnabled(false); 91 92 if (mOccupied == null) { 93 if (mPortrait) { 94 mOccupied = new boolean[mShortAxisCells][mLongAxisCells]; 95 } else { 96 mOccupied = new boolean[mLongAxisCells][mShortAxisCells]; 97 } 98 } 99 } 100 101 @Override 102 public void cancelLongPress() { 103 super.cancelLongPress(); 104 105 // Cancel long press for all children 106 final int count = getChildCount(); 107 for (int i = 0; i < count; i++) { 108 final View child = getChildAt(i); 109 child.cancelLongPress(); 110 } 111 } 112 113 int getCountX() { 114 return mPortrait ? mShortAxisCells : mLongAxisCells; 115 } 116 117 int getCountY() { 118 return mPortrait ? mLongAxisCells : mShortAxisCells; 119 } 120 121 @Override 122 public void addView(View child, int index, ViewGroup.LayoutParams params) { 123 // Generate an id for each view, this assumes we have at most 256x256 cells 124 // per workspace screen 125 final LayoutParams cellParams = (LayoutParams) params; 126 child.setId(((getId() & 0xFF) << 16) | 127 (cellParams.cellX & 0xFF) << 8 | (cellParams.cellY & 0xFF)); 128 129 super.addView(child, index, params); 130 } 131 132 @Override 133 public void requestChildFocus(View child, View focused) { 134 super.requestChildFocus(child, focused); 135 if (child != null) { 136 Rect r = new Rect(); 137 child.getDrawingRect(r); 138 requestRectangleOnScreen(r); 139 } 140 } 141 142 @Override 143 protected void onAttachedToWindow() { 144 super.onAttachedToWindow(); 145 mCellInfo.screen = ((ViewGroup) getParent()).indexOfChild(this); 146 } 147 148 @Override 149 public boolean onInterceptTouchEvent(MotionEvent ev) { 150 final int action = ev.getAction(); 151 final CellInfo cellInfo = mCellInfo; 152 153 if (action == MotionEvent.ACTION_DOWN) { 154 final Rect frame = mRect; 155 final int x = (int) ev.getX() + mScrollX; 156 final int y = (int) ev.getY() + mScrollY; 157 final int count = getChildCount(); 158 159 boolean found = false; 160 for (int i = count - 1; i >= 0; i--) { 161 final View child = getChildAt(i); 162 163 if ((child.getVisibility()) == VISIBLE || child.getAnimation() != null) { 164 child.getHitRect(frame); 165 if (frame.contains(x, y)) { 166 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 167 cellInfo.cell = child; 168 cellInfo.cellX = lp.cellX; 169 cellInfo.cellY = lp.cellY; 170 cellInfo.spanX = lp.cellHSpan; 171 cellInfo.spanY = lp.cellVSpan; 172 cellInfo.valid = true; 173 found = true; 174 mDirtyTag = false; 175 break; 176 } 177 } 178 } 179 180 if (!found) { 181 int cellXY[] = mCellXY; 182 pointToCellExact(x, y, cellXY); 183 184 final boolean portrait = mPortrait; 185 final int xCount = portrait ? mShortAxisCells : mLongAxisCells; 186 final int yCount = portrait ? mLongAxisCells : mShortAxisCells; 187 188 final boolean[][] occupied = mOccupied; 189 findOccupiedCells(xCount, yCount, occupied, null); 190 191 cellInfo.cell = null; 192 cellInfo.cellX = cellXY[0]; 193 cellInfo.cellY = cellXY[1]; 194 cellInfo.spanX = 1; 195 cellInfo.spanY = 1; 196 cellInfo.valid = cellXY[0] >= 0 && cellXY[1] >= 0 && cellXY[0] < xCount && 197 cellXY[1] < yCount && !occupied[cellXY[0]][cellXY[1]]; 198 199 // Instead of finding the interesting vacant cells here, wait until a 200 // caller invokes getTag() to retrieve the result. Finding the vacant 201 // cells is a bit expensive and can generate many new objects, it's 202 // therefore better to defer it until we know we actually need it. 203 204 mDirtyTag = true; 205 } 206 setTag(cellInfo); 207 } else if (action == MotionEvent.ACTION_UP) { 208 cellInfo.cell = null; 209 cellInfo.cellX = -1; 210 cellInfo.cellY = -1; 211 cellInfo.spanX = 0; 212 cellInfo.spanY = 0; 213 cellInfo.valid = false; 214 mDirtyTag = false; 215 setTag(cellInfo); 216 } 217 218 return false; 219 } 220 221 @Override 222 public CellInfo getTag() { 223 final CellInfo info = (CellInfo) super.getTag(); 224 if (mDirtyTag && info.valid) { 225 final boolean portrait = mPortrait; 226 final int xCount = portrait ? mShortAxisCells : mLongAxisCells; 227 final int yCount = portrait ? mLongAxisCells : mShortAxisCells; 228 229 final boolean[][] occupied = mOccupied; 230 findOccupiedCells(xCount, yCount, occupied, null); 231 232 findIntersectingVacantCells(info, info.cellX, info.cellY, xCount, yCount, occupied); 233 234 mDirtyTag = false; 235 } 236 return info; 237 } 238 239 private static void findIntersectingVacantCells(CellInfo cellInfo, int x, int y, 240 int xCount, int yCount, boolean[][] occupied) { 241 242 cellInfo.maxVacantSpanX = Integer.MIN_VALUE; 243 cellInfo.maxVacantSpanXSpanY = Integer.MIN_VALUE; 244 cellInfo.maxVacantSpanY = Integer.MIN_VALUE; 245 cellInfo.maxVacantSpanYSpanX = Integer.MIN_VALUE; 246 cellInfo.clearVacantCells(); 247 248 if (occupied[x][y]) { 249 return; 250 } 251 252 cellInfo.current.set(x, y, x, y); 253 254 findVacantCell(cellInfo.current, xCount, yCount, occupied, cellInfo); 255 } 256 257 private static void findVacantCell(Rect current, int xCount, int yCount, boolean[][] occupied, 258 CellInfo cellInfo) { 259 260 addVacantCell(current, cellInfo); 261 262 if (current.left > 0) { 263 if (isColumnEmpty(current.left - 1, current.top, current.bottom, occupied)) { 264 current.left--; 265 findVacantCell(current, xCount, yCount, occupied, cellInfo); 266 current.left++; 267 } 268 } 269 270 if (current.right < xCount - 1) { 271 if (isColumnEmpty(current.right + 1, current.top, current.bottom, occupied)) { 272 current.right++; 273 findVacantCell(current, xCount, yCount, occupied, cellInfo); 274 current.right--; 275 } 276 } 277 278 if (current.top > 0) { 279 if (isRowEmpty(current.top - 1, current.left, current.right, occupied)) { 280 current.top--; 281 findVacantCell(current, xCount, yCount, occupied, cellInfo); 282 current.top++; 283 } 284 } 285 286 if (current.bottom < yCount - 1) { 287 if (isRowEmpty(current.bottom + 1, current.left, current.right, occupied)) { 288 current.bottom++; 289 findVacantCell(current, xCount, yCount, occupied, cellInfo); 290 current.bottom--; 291 } 292 } 293 } 294 295 private static void addVacantCell(Rect current, CellInfo cellInfo) { 296 CellInfo.VacantCell cell = CellInfo.VacantCell.acquire(); 297 cell.cellX = current.left; 298 cell.cellY = current.top; 299 cell.spanX = current.right - current.left + 1; 300 cell.spanY = current.bottom - current.top + 1; 301 if (cell.spanX > cellInfo.maxVacantSpanX) { 302 cellInfo.maxVacantSpanX = cell.spanX; 303 cellInfo.maxVacantSpanXSpanY = cell.spanY; 304 } 305 if (cell.spanY > cellInfo.maxVacantSpanY) { 306 cellInfo.maxVacantSpanY = cell.spanY; 307 cellInfo.maxVacantSpanYSpanX = cell.spanX; 308 } 309 cellInfo.vacantCells.add(cell); 310 } 311 312 private static boolean isColumnEmpty(int x, int top, int bottom, boolean[][] occupied) { 313 for (int y = top; y <= bottom; y++) { 314 if (occupied[x][y]) { 315 return false; 316 } 317 } 318 return true; 319 } 320 321 private static boolean isRowEmpty(int y, int left, int right, boolean[][] occupied) { 322 for (int x = left; x <= right; x++) { 323 if (occupied[x][y]) { 324 return false; 325 } 326 } 327 return true; 328 } 329 330 CellInfo findAllVacantCells(boolean[] occupiedCells, View ignoreView) { 331 final boolean portrait = mPortrait; 332 final int xCount = portrait ? mShortAxisCells : mLongAxisCells; 333 final int yCount = portrait ? mLongAxisCells : mShortAxisCells; 334 335 boolean[][] occupied = mOccupied; 336 337 if (occupiedCells != null) { 338 for (int y = 0; y < yCount; y++) { 339 for (int x = 0; x < xCount; x++) { 340 occupied[x][y] = occupiedCells[y * xCount + x]; 341 } 342 } 343 } else { 344 findOccupiedCells(xCount, yCount, occupied, ignoreView); 345 } 346 347 CellInfo cellInfo = new CellInfo(); 348 349 cellInfo.cellX = -1; 350 cellInfo.cellY = -1; 351 cellInfo.spanY = 0; 352 cellInfo.spanX = 0; 353 cellInfo.maxVacantSpanX = Integer.MIN_VALUE; 354 cellInfo.maxVacantSpanXSpanY = Integer.MIN_VALUE; 355 cellInfo.maxVacantSpanY = Integer.MIN_VALUE; 356 cellInfo.maxVacantSpanYSpanX = Integer.MIN_VALUE; 357 cellInfo.screen = mCellInfo.screen; 358 359 Rect current = cellInfo.current; 360 361 for (int x = 0; x < xCount; x++) { 362 for (int y = 0; y < yCount; y++) { 363 if (!occupied[x][y]) { 364 current.set(x, y, x, y); 365 findVacantCell(current, xCount, yCount, occupied, cellInfo); 366 occupied[x][y] = true; 367 } 368 } 369 } 370 371 cellInfo.valid = cellInfo.vacantCells.size() > 0; 372 373 // Assume the caller will perform their own cell searching, otherwise we 374 // risk causing an unnecessary rebuild after findCellForSpan() 375 376 return cellInfo; 377 } 378 379 /** 380 * Given a point, return the cell that strictly encloses that point 381 * @param x X coordinate of the point 382 * @param y Y coordinate of the point 383 * @param result Array of 2 ints to hold the x and y coordinate of the cell 384 */ 385 void pointToCellExact(int x, int y, int[] result) { 386 final boolean portrait = mPortrait; 387 388 final int hStartPadding = portrait ? mShortAxisStartPadding : mLongAxisStartPadding; 389 final int vStartPadding = portrait ? mLongAxisStartPadding : mShortAxisStartPadding; 390 391 result[0] = (x - hStartPadding) / (mCellWidth + mWidthGap); 392 result[1] = (y - vStartPadding) / (mCellHeight + mHeightGap); 393 394 final int xAxis = portrait ? mShortAxisCells : mLongAxisCells; 395 final int yAxis = portrait ? mLongAxisCells : mShortAxisCells; 396 397 if (result[0] < 0) result[0] = 0; 398 if (result[0] >= xAxis) result[0] = xAxis - 1; 399 if (result[1] < 0) result[1] = 0; 400 if (result[1] >= yAxis) result[1] = yAxis - 1; 401 } 402 403 /** 404 * Given a point, return the cell that most closely encloses that point 405 * @param x X coordinate of the point 406 * @param y Y coordinate of the point 407 * @param result Array of 2 ints to hold the x and y coordinate of the cell 408 */ 409 void pointToCellRounded(int x, int y, int[] result) { 410 pointToCellExact(x + (mCellWidth / 2), y + (mCellHeight / 2), result); 411 } 412 413 /** 414 * Given a cell coordinate, return the point that represents the upper left corner of that cell 415 * 416 * @param cellX X coordinate of the cell 417 * @param cellY Y coordinate of the cell 418 * 419 * @param result Array of 2 ints to hold the x and y coordinate of the point 420 */ 421 void cellToPoint(int cellX, int cellY, int[] result) { 422 final boolean portrait = mPortrait; 423 424 final int hStartPadding = portrait ? mShortAxisStartPadding : mLongAxisStartPadding; 425 final int vStartPadding = portrait ? mLongAxisStartPadding : mShortAxisStartPadding; 426 427 428 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap); 429 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap); 430 } 431 432 @Override 433 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 434 // TODO: currently ignoring padding 435 436 int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); 437 int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); 438 439 int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); 440 int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); 441 442 if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) { 443 throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions"); 444 } 445 446 final int shortAxisCells = mShortAxisCells; 447 final int longAxisCells = mLongAxisCells; 448 final int longAxisStartPadding = mLongAxisStartPadding; 449 final int longAxisEndPadding = mLongAxisEndPadding; 450 final int shortAxisStartPadding = mShortAxisStartPadding; 451 final int shortAxisEndPadding = mShortAxisEndPadding; 452 final int cellWidth = mCellWidth; 453 final int cellHeight = mCellHeight; 454 455 mPortrait = heightSpecSize > widthSpecSize; 456 457 int numShortGaps = shortAxisCells - 1; 458 int numLongGaps = longAxisCells - 1; 459 460 if (mPortrait) { 461 int vSpaceLeft = heightSpecSize - longAxisStartPadding - longAxisEndPadding 462 - (cellHeight * longAxisCells); 463 mHeightGap = vSpaceLeft / numLongGaps; 464 465 int hSpaceLeft = widthSpecSize - shortAxisStartPadding - shortAxisEndPadding 466 - (cellWidth * shortAxisCells); 467 if (numShortGaps > 0) { 468 mWidthGap = hSpaceLeft / numShortGaps; 469 } else { 470 mWidthGap = 0; 471 } 472 } else { 473 int hSpaceLeft = widthSpecSize - longAxisStartPadding - longAxisEndPadding 474 - (cellWidth * longAxisCells); 475 mWidthGap = hSpaceLeft / numLongGaps; 476 477 int vSpaceLeft = heightSpecSize - shortAxisStartPadding - shortAxisEndPadding 478 - (cellHeight * shortAxisCells); 479 if (numShortGaps > 0) { 480 mHeightGap = vSpaceLeft / numShortGaps; 481 } else { 482 mHeightGap = 0; 483 } 484 } 485 486 int count = getChildCount(); 487 488 for (int i = 0; i < count; i++) { 489 View child = getChildAt(i); 490 LayoutParams lp = (LayoutParams) child.getLayoutParams(); 491 492 if (mPortrait) { 493 lp.setup(cellWidth, cellHeight, mWidthGap, mHeightGap, shortAxisStartPadding, 494 longAxisStartPadding); 495 } else { 496 lp.setup(cellWidth, cellHeight, mWidthGap, mHeightGap, longAxisStartPadding, 497 shortAxisStartPadding); 498 } 499 500 int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY); 501 int childheightMeasureSpec = 502 MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY); 503 child.measure(childWidthMeasureSpec, childheightMeasureSpec); 504 } 505 506 setMeasuredDimension(widthSpecSize, heightSpecSize); 507 } 508 509 @Override 510 protected void onLayout(boolean changed, int l, int t, int r, int b) { 511 int count = getChildCount(); 512 513 for (int i = 0; i < count; i++) { 514 View child = getChildAt(i); 515 if (child.getVisibility() != GONE) { 516 517 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams(); 518 519 int childLeft = lp.x; 520 int childTop = lp.y; 521 child.layout(childLeft, childTop, childLeft + lp.width, childTop + lp.height); 522 } 523 } 524 } 525 526 @Override 527 protected void setChildrenDrawingCacheEnabled(boolean enabled) { 528 final int count = getChildCount(); 529 for (int i = 0; i < count; i++) { 530 final View view = getChildAt(i); 531 view.setDrawingCacheEnabled(enabled); 532 // Update the drawing caches 533 view.buildDrawingCache(true); 534 } 535 } 536 537 @Override 538 protected void setChildrenDrawnWithCacheEnabled(boolean enabled) { 539 super.setChildrenDrawnWithCacheEnabled(enabled); 540 } 541 542 /** 543 * Find a vacant area that will fit the given bounds nearest the requested 544 * cell location. Uses Euclidean distance to score multiple vacant areas. 545 * 546 * @param pixelX The X location at which you want to search for a vacant area. 547 * @param pixelY The Y location at which you want to search for a vacant area. 548 * @param spanX Horizontal span of the object. 549 * @param spanY Vertical span of the object. 550 * @param vacantCells Pre-computed set of vacant cells to search. 551 * @param recycle Previously returned value to possibly recycle. 552 * @return The X, Y cell of a vacant area that can contain this object, 553 * nearest the requested location. 554 */ 555 int[] findNearestVacantArea(int pixelX, int pixelY, int spanX, int spanY, 556 CellInfo vacantCells, int[] recycle) { 557 558 // Keep track of best-scoring drop area 559 final int[] bestXY = recycle != null ? recycle : new int[2]; 560 final int[] cellXY = mCellXY; 561 double bestDistance = Double.MAX_VALUE; 562 563 // Bail early if vacant cells aren't valid 564 if (!vacantCells.valid) { 565 return null; 566 } 567 568 // Look across all vacant cells for best fit 569 final int size = vacantCells.vacantCells.size(); 570 for (int i = 0; i < size; i++) { 571 final CellInfo.VacantCell cell = vacantCells.vacantCells.get(i); 572 573 // Reject if vacant cell isn't our exact size 574 if (cell.spanX != spanX || cell.spanY != spanY) { 575 continue; 576 } 577 578 // Score is center distance from requested pixel 579 cellToPoint(cell.cellX, cell.cellY, cellXY); 580 581 double distance = Math.sqrt(Math.pow(cellXY[0] - pixelX, 2) + 582 Math.pow(cellXY[1] - pixelY, 2)); 583 if (distance <= bestDistance) { 584 bestDistance = distance; 585 bestXY[0] = cell.cellX; 586 bestXY[1] = cell.cellY; 587 } 588 } 589 590 // Return null if no suitable location found 591 if (bestDistance < Double.MAX_VALUE) { 592 return bestXY; 593 } else { 594 return null; 595 } 596 } 597 598 /** 599 * Drop a child at the specified position 600 * 601 * @param child The child that is being dropped 602 * @param targetXY Destination area to move to 603 */ 604 void onDropChild(View child, int[] targetXY) { 605 LayoutParams lp = (LayoutParams) child.getLayoutParams(); 606 lp.cellX = targetXY[0]; 607 lp.cellY = targetXY[1]; 608 lp.isDragging = false; 609 mDragRect.setEmpty(); 610 child.requestLayout(); 611 invalidate(); 612 } 613 614 void onDropAborted(View child) { 615 if (child != null) { 616 ((LayoutParams) child.getLayoutParams()).isDragging = false; 617 invalidate(); 618 } 619 mDragRect.setEmpty(); 620 } 621 622 /** 623 * Start dragging the specified child 624 * 625 * @param child The child that is being dragged 626 */ 627 void onDragChild(View child) { 628 LayoutParams lp = (LayoutParams) child.getLayoutParams(); 629 lp.isDragging = true; 630 mDragRect.setEmpty(); 631 } 632 633 /** 634 * Drag a child over the specified position 635 * 636 * @param child The child that is being dropped 637 * @param cellX The child's new x cell location 638 * @param cellY The child's new y cell location 639 */ 640 void onDragOverChild(View child, int cellX, int cellY) { 641 int[] cellXY = mCellXY; 642 pointToCellRounded(cellX, cellY, cellXY); 643 LayoutParams lp = (LayoutParams) child.getLayoutParams(); 644 cellToRect(cellXY[0], cellXY[1], lp.cellHSpan, lp.cellVSpan, mDragRect); 645 invalidate(); 646 } 647 648 /** 649 * Computes a bounding rectangle for a range of cells 650 * 651 * @param cellX X coordinate of upper left corner expressed as a cell position 652 * @param cellY Y coordinate of upper left corner expressed as a cell position 653 * @param cellHSpan Width in cells 654 * @param cellVSpan Height in cells 655 * @param dragRect Rectnagle into which to put the results 656 */ 657 public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, RectF dragRect) { 658 final boolean portrait = mPortrait; 659 final int cellWidth = mCellWidth; 660 final int cellHeight = mCellHeight; 661 final int widthGap = mWidthGap; 662 final int heightGap = mHeightGap; 663 664 final int hStartPadding = portrait ? mShortAxisStartPadding : mLongAxisStartPadding; 665 final int vStartPadding = portrait ? mLongAxisStartPadding : mShortAxisStartPadding; 666 667 int width = cellHSpan * cellWidth + ((cellHSpan - 1) * widthGap); 668 int height = cellVSpan * cellHeight + ((cellVSpan - 1) * heightGap); 669 670 int x = hStartPadding + cellX * (cellWidth + widthGap); 671 int y = vStartPadding + cellY * (cellHeight + heightGap); 672 673 dragRect.set(x, y, x + width, y + height); 674 } 675 676 /** 677 * Computes the required horizontal and vertical cell spans to always 678 * fit the given rectangle. 679 * 680 * @param width Width in pixels 681 * @param height Height in pixels 682 */ 683 public int[] rectToCell(int width, int height) { 684 // Always assume we're working with the smallest span to make sure we 685 // reserve enough space in both orientations. 686 int actualWidth = mCellWidth + mWidthGap; 687 int actualHeight = mCellHeight + mHeightGap; 688 int smallerSize = Math.min(actualWidth, actualHeight); 689 690 // Always round up to next largest cell 691 int spanX = (width + smallerSize) / smallerSize; 692 int spanY = (height + smallerSize) / smallerSize; 693 return new int[] { spanX, spanY }; 694 } 695 696 /** 697 * Find the first vacant cell, if there is one. 698 * 699 * @param vacant Holds the x and y coordinate of the vacant cell 700 * @param spanX Horizontal cell span. 701 * @param spanY Vertical cell span. 702 * 703 * @return True if a vacant cell was found 704 */ 705 public boolean getVacantCell(int[] vacant, int spanX, int spanY) { 706 final boolean portrait = mPortrait; 707 final int xCount = portrait ? mShortAxisCells : mLongAxisCells; 708 final int yCount = portrait ? mLongAxisCells : mShortAxisCells; 709 final boolean[][] occupied = mOccupied; 710 711 findOccupiedCells(xCount, yCount, occupied, null); 712 713 return findVacantCell(vacant, spanX, spanY, xCount, yCount, occupied); 714 } 715 716 static boolean findVacantCell(int[] vacant, int spanX, int spanY, 717 int xCount, int yCount, boolean[][] occupied) { 718 719 for (int x = 0; x < xCount; x++) { 720 for (int y = 0; y < yCount; y++) { 721 boolean available = !occupied[x][y]; 722out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) { 723 for (int j = y; j < y + spanY - 1 && y < yCount; j++) { 724 available = available && !occupied[i][j]; 725 if (!available) break out; 726 } 727 } 728 729 if (available) { 730 vacant[0] = x; 731 vacant[1] = y; 732 return true; 733 } 734 } 735 } 736 737 return false; 738 } 739 740 boolean[] getOccupiedCells() { 741 final boolean portrait = mPortrait; 742 final int xCount = portrait ? mShortAxisCells : mLongAxisCells; 743 final int yCount = portrait ? mLongAxisCells : mShortAxisCells; 744 final boolean[][] occupied = mOccupied; 745 746 findOccupiedCells(xCount, yCount, occupied, null); 747 748 final boolean[] flat = new boolean[xCount * yCount]; 749 for (int y = 0; y < yCount; y++) { 750 for (int x = 0; x < xCount; x++) { 751 flat[y * xCount + x] = occupied[x][y]; 752 } 753 } 754 755 return flat; 756 } 757 758 private void findOccupiedCells(int xCount, int yCount, boolean[][] occupied, View ignoreView) { 759 for (int x = 0; x < xCount; x++) { 760 for (int y = 0; y < yCount; y++) { 761 occupied[x][y] = false; 762 } 763 } 764 765 int count = getChildCount(); 766 for (int i = 0; i < count; i++) { 767 View child = getChildAt(i); 768 if (child instanceof Folder || child.equals(ignoreView)) { 769 continue; 770 } 771 LayoutParams lp = (LayoutParams) child.getLayoutParams(); 772 773 for (int x = lp.cellX; x < lp.cellX + lp.cellHSpan && x < xCount; x++) { 774 for (int y = lp.cellY; y < lp.cellY + lp.cellVSpan && y < yCount; y++) { 775 occupied[x][y] = true; 776 } 777 } 778 } 779 } 780 781 @Override 782 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { 783 return new CellLayout.LayoutParams(getContext(), attrs); 784 } 785 786 @Override 787 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 788 return p instanceof CellLayout.LayoutParams; 789 } 790 791 @Override 792 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { 793 return new CellLayout.LayoutParams(p); 794 } 795 796 public static class LayoutParams extends ViewGroup.MarginLayoutParams { 797 /** 798 * Horizontal location of the item in the grid. 799 */ 800 @ViewDebug.ExportedProperty 801 public int cellX; 802 803 /** 804 * Vertical location of the item in the grid. 805 */ 806 @ViewDebug.ExportedProperty 807 public int cellY; 808 809 /** 810 * Number of cells spanned horizontally by the item. 811 */ 812 @ViewDebug.ExportedProperty 813 public int cellHSpan; 814 815 /** 816 * Number of cells spanned vertically by the item. 817 */ 818 @ViewDebug.ExportedProperty 819 public int cellVSpan; 820 821 /** 822 * Is this item currently being dragged 823 */ 824 public boolean isDragging; 825 826 // X coordinate of the view in the layout. 827 @ViewDebug.ExportedProperty 828 int x; 829 // Y coordinate of the view in the layout. 830 @ViewDebug.ExportedProperty 831 int y; 832 833 public LayoutParams(Context c, AttributeSet attrs) { 834 super(c, attrs); 835 cellHSpan = 1; 836 cellVSpan = 1; 837 } 838 839 public LayoutParams(ViewGroup.LayoutParams source) { 840 super(source); 841 cellHSpan = 1; 842 cellVSpan = 1; 843 } 844 845 public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) { 846 super(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT); 847 this.cellX = cellX; 848 this.cellY = cellY; 849 this.cellHSpan = cellHSpan; 850 this.cellVSpan = cellVSpan; 851 } 852 853 public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap, 854 int hStartPadding, int vStartPadding) { 855 856 final int myCellHSpan = cellHSpan; 857 final int myCellVSpan = cellVSpan; 858 final int myCellX = cellX; 859 final int myCellY = cellY; 860 861 width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) - 862 leftMargin - rightMargin; 863 height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) - 864 topMargin - bottomMargin; 865 866 x = hStartPadding + myCellX * (cellWidth + widthGap) + leftMargin; 867 y = vStartPadding + myCellY * (cellHeight + heightGap) + topMargin; 868 } 869 } 870 871 static final class CellInfo implements ContextMenu.ContextMenuInfo { 872 /** 873 * See View.AttachInfo.InvalidateInfo for futher explanations about 874 * the recycling mechanism. In this case, we recycle the vacant cells 875 * instances because up to several hundreds can be instanciated when 876 * the user long presses an empty cell. 877 */ 878 static final class VacantCell { 879 int cellX; 880 int cellY; 881 int spanX; 882 int spanY; 883 884 // We can create up to 523 vacant cells on a 4x4 grid, 100 seems 885 // like a reasonable compromise given the size of a VacantCell and 886 // the fact that the user is not likely to touch an empty 4x4 grid 887 // very often 888 private static final int POOL_LIMIT = 100; 889 private static final Object sLock = new Object(); 890 891 private static int sAcquiredCount = 0; 892 private static VacantCell sRoot; 893 894 private VacantCell next; 895 896 static VacantCell acquire() { 897 synchronized (sLock) { 898 if (sRoot == null) { 899 return new VacantCell(); 900 } 901 902 VacantCell info = sRoot; 903 sRoot = info.next; 904 sAcquiredCount--; 905 906 return info; 907 } 908 } 909 910 void release() { 911 synchronized (sLock) { 912 if (sAcquiredCount < POOL_LIMIT) { 913 sAcquiredCount++; 914 next = sRoot; 915 sRoot = this; 916 } 917 } 918 } 919 920 @Override 921 public String toString() { 922 return "VacantCell[x=" + cellX + ", y=" + cellY + ", spanX=" + spanX + 923 ", spanY=" + spanY + "]"; 924 } 925 } 926 927 View cell; 928 int cellX; 929 int cellY; 930 int spanX; 931 int spanY; 932 int screen; 933 boolean valid; 934 935 final ArrayList<VacantCell> vacantCells = new ArrayList<VacantCell>(VacantCell.POOL_LIMIT); 936 int maxVacantSpanX; 937 int maxVacantSpanXSpanY; 938 int maxVacantSpanY; 939 int maxVacantSpanYSpanX; 940 final Rect current = new Rect(); 941 942 void clearVacantCells() { 943 final ArrayList<VacantCell> list = vacantCells; 944 final int count = list.size(); 945 946 for (int i = 0; i < count; i++) list.get(i).release(); 947 948 list.clear(); 949 } 950 951 void findVacantCellsFromOccupied(boolean[] occupied, int xCount, int yCount) { 952 if (cellX < 0 || cellY < 0) { 953 maxVacantSpanX = maxVacantSpanXSpanY = Integer.MIN_VALUE; 954 maxVacantSpanY = maxVacantSpanYSpanX = Integer.MIN_VALUE; 955 clearVacantCells(); 956 return; 957 } 958 959 final boolean[][] unflattened = new boolean[xCount][yCount]; 960 for (int y = 0; y < yCount; y++) { 961 for (int x = 0; x < xCount; x++) { 962 unflattened[x][y] = occupied[y * xCount + x]; 963 } 964 } 965 CellLayout.findIntersectingVacantCells(this, cellX, cellY, xCount, yCount, unflattened); 966 } 967 968 /** 969 * This method can be called only once! Calling #findVacantCellsFromOccupied will 970 * restore the ability to call this method. 971 * 972 * Finds the upper-left coordinate of the first rectangle in the grid that can 973 * hold a cell of the specified dimensions. 974 * 975 * @param cellXY The array that will contain the position of a vacant cell if such a cell 976 * can be found. 977 * @param spanX The horizontal span of the cell we want to find. 978 * @param spanY The vertical span of the cell we want to find. 979 * 980 * @return True if a vacant cell of the specified dimension was found, false otherwise. 981 */ 982 boolean findCellForSpan(int[] cellXY, int spanX, int spanY) { 983 return findCellForSpan(cellXY, spanX, spanY, true); 984 } 985 986 boolean findCellForSpan(int[] cellXY, int spanX, int spanY, boolean clear) { 987 final ArrayList<VacantCell> list = vacantCells; 988 final int count = list.size(); 989 990 boolean found = false; 991 992 if (this.spanX >= spanX && this.spanY >= spanY) { 993 cellXY[0] = cellX; 994 cellXY[1] = cellY; 995 found = true; 996 } 997 998 // Look for an exact match first 999 for (int i = 0; i < count; i++) { 1000 VacantCell cell = list.get(i); 1001 if (cell.spanX == spanX && cell.spanY == spanY) { 1002 cellXY[0] = cell.cellX; 1003 cellXY[1] = cell.cellY; 1004 found = true; 1005 break; 1006 } 1007 } 1008 1009 // Look for the first cell large enough 1010 for (int i = 0; i < count; i++) { 1011 VacantCell cell = list.get(i); 1012 if (cell.spanX >= spanX && cell.spanY >= spanY) { 1013 cellXY[0] = cell.cellX; 1014 cellXY[1] = cell.cellY; 1015 found = true; 1016 break; 1017 } 1018 } 1019 1020 if (clear) clearVacantCells(); 1021 1022 return found; 1023 } 1024 1025 @Override 1026 public String toString() { 1027 return "Cell[view=" + (cell == null ? "null" : cell.getClass()) + ", x=" + cellX + 1028 ", y=" + cellY + "]"; 1029 } 1030 } 1031} 1032 1033 1034