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