CellLayout.java revision 0280c3be4d9f8fc6fdf015b7ecd276eb26f76f2d
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 com.android.launcher.R; 20 21import android.app.WallpaperManager; 22import android.content.Context; 23import android.content.res.Resources; 24import android.content.res.TypedArray; 25import android.graphics.Canvas; 26import android.graphics.Rect; 27import android.graphics.RectF; 28import android.graphics.drawable.Drawable; 29import android.util.AttributeSet; 30import android.view.ContextMenu; 31import android.view.MotionEvent; 32import android.view.View; 33import android.view.ViewDebug; 34import android.view.ViewGroup; 35import android.view.animation.Animation; 36import android.view.animation.LayoutAnimationController; 37 38import java.util.Arrays; 39 40public class CellLayout extends ViewGroup { 41 static final String TAG = "CellLayout"; 42 43 private int mCellWidth; 44 private int mCellHeight; 45 46 private int mLeftPadding; 47 private int mRightPadding; 48 private int mTopPadding; 49 private int mBottomPadding; 50 51 private int mCountX; 52 private int mCountY; 53 54 private int mWidthGap; 55 private int mHeightGap; 56 57 private final Rect mRect = new Rect(); 58 private final RectF mRectF = new RectF(); 59 private final CellInfo mCellInfo = new CellInfo(); 60 61 // This is a temporary variable to prevent having to allocate a new object just to 62 // return an (x, y) value from helper functions. Do NOT use it to maintain other state. 63 private final int[] mTmpCellXY = new int[2]; 64 65 boolean[][] mOccupied; 66 67 private OnTouchListener mInterceptTouchListener; 68 69 private float mBackgroundAlpha; 70 private final Rect mBackgroundLayoutRect = new Rect(); 71 private Drawable mBackground; 72 private Drawable mBackgroundHover; 73 // If we're actively dragging something over this screen and it's small, 74 // mHover is true 75 private boolean mHover = false; 76 77 private final RectF mDragRect = new RectF(); 78 79 // When dragging, used to indicate a vacant drop location 80 private Drawable mVacantDrawable; 81 82 // When dragging, used to indicate an occupied drop location 83 private Drawable mOccupiedDrawable; 84 85 // Updated to point to mVacantDrawable or mOccupiedDrawable, as appropriate 86 private Drawable mDragRectDrawable; 87 88 // When a drag operation is in progress, holds the nearest cell to the touch point 89 private final int[] mDragCell = new int[2]; 90 91 private final WallpaperManager mWallpaperManager; 92 93 public CellLayout(Context context) { 94 this(context, null); 95 } 96 97 public CellLayout(Context context, AttributeSet attrs) { 98 this(context, attrs, 0); 99 } 100 101 public CellLayout(Context context, AttributeSet attrs, int defStyle) { 102 super(context, attrs, defStyle); 103 104 // A ViewGroup usually does not draw, but CellLayout needs to draw a rectangle to show 105 // the user where a dragged item will land when dropped. 106 setWillNotDraw(false); 107 mVacantDrawable = getResources().getDrawable(R.drawable.rounded_rect_green); 108 mOccupiedDrawable = getResources().getDrawable(R.drawable.rounded_rect_red); 109 110 if (LauncherApplication.isScreenXLarge()) { 111 mBackground = getResources().getDrawable(R.drawable.mini_home_screen_bg); 112 mBackground.setFilterBitmap(true); 113 mBackgroundHover = getResources().getDrawable(R.drawable.mini_home_screen_bg_hover); 114 mBackgroundHover.setFilterBitmap(true); 115 } 116 117 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0); 118 119 mCellWidth = a.getDimensionPixelSize(R.styleable.CellLayout_cellWidth, 10); 120 mCellHeight = a.getDimensionPixelSize(R.styleable.CellLayout_cellHeight, 10); 121 122 mLeftPadding = 123 a.getDimensionPixelSize(R.styleable.CellLayout_xAxisStartPadding, 10); 124 mRightPadding = 125 a.getDimensionPixelSize(R.styleable.CellLayout_xAxisEndPadding, 10); 126 mTopPadding = 127 a.getDimensionPixelSize(R.styleable.CellLayout_yAxisStartPadding, 10); 128 mBottomPadding = 129 a.getDimensionPixelSize(R.styleable.CellLayout_yAxisEndPadding, 10); 130 131 mCountX = LauncherModel.getCellCountX(); 132 mCountY = LauncherModel.getCellCountY(); 133 mOccupied = new boolean[mCountX][mCountY]; 134 135 a.recycle(); 136 137 setAlwaysDrawnWithCacheEnabled(false); 138 139 mWallpaperManager = WallpaperManager.getInstance(getContext()); 140 } 141 142 public void setHover(boolean value) { 143 if (mHover != value) { 144 invalidate(); 145 } 146 mHover = value; 147 } 148 149 @Override 150 public void dispatchDraw(Canvas canvas) { 151 if (mBackgroundAlpha > 0.0f) { 152 final Drawable bg = mHover ? mBackgroundHover : mBackground; 153 bg.setAlpha((int) (mBackgroundAlpha * 255)); 154 bg.draw(canvas); 155 } 156 super.dispatchDraw(canvas); 157 } 158 159 @Override 160 protected void onDraw(Canvas canvas) { 161 if (!mDragRect.isEmpty()) { 162 mDragRectDrawable.setBounds( 163 (int)mDragRect.left, 164 (int)mDragRect.top, 165 (int)mDragRect.right, 166 (int)mDragRect.bottom); 167 mDragRectDrawable.draw(canvas); 168 } 169 super.onDraw(canvas); 170 } 171 172 @Override 173 public void cancelLongPress() { 174 super.cancelLongPress(); 175 176 // Cancel long press for all children 177 final int count = getChildCount(); 178 for (int i = 0; i < count; i++) { 179 final View child = getChildAt(i); 180 child.cancelLongPress(); 181 } 182 } 183 184 public void setOnInterceptTouchListener(View.OnTouchListener listener) { 185 mInterceptTouchListener = listener; 186 } 187 188 int getCountX() { 189 return mCountX; 190 } 191 192 int getCountY() { 193 return mCountY; 194 } 195 196 // Takes canonical layout parameters 197 public boolean addViewToCellLayout(View child, int index, int childId, LayoutParams params) { 198 final LayoutParams lp = params; 199 200 // Generate an id for each view, this assumes we have at most 256x256 cells 201 // per workspace screen 202 if (lp.cellX >= 0 && lp.cellX <= mCountX - 1 && lp.cellY >= 0 && lp.cellY <= mCountY - 1) { 203 // If the horizontal or vertical span is set to -1, it is taken to 204 // mean that it spans the extent of the CellLayout 205 if (lp.cellHSpan < 0) lp.cellHSpan = mCountX; 206 if (lp.cellVSpan < 0) lp.cellVSpan = mCountY; 207 208 child.setId(childId); 209 210 // We might be in the middle or end of shrinking/fading to a dimmed view 211 // Make sure this view's alpha is set the same as all the rest of the views 212 child.setAlpha(getAlpha()); 213 addView(child, index, lp); 214 215 markCellsAsOccupiedForView(child); 216 217 return true; 218 } 219 return false; 220 } 221 222 @Override 223 public void removeAllViews() { 224 clearOccupiedCells(); 225 } 226 227 @Override 228 public void removeAllViewsInLayout() { 229 clearOccupiedCells(); 230 } 231 232 @Override 233 public void removeView(View view) { 234 markCellsAsUnoccupiedForView(view); 235 super.removeView(view); 236 } 237 238 @Override 239 public void removeViewAt(int index) { 240 markCellsAsUnoccupiedForView(getChildAt(index)); 241 super.removeViewAt(index); 242 } 243 244 @Override 245 public void removeViewInLayout(View view) { 246 markCellsAsUnoccupiedForView(view); 247 super.removeViewInLayout(view); 248 } 249 250 @Override 251 public void removeViews(int start, int count) { 252 for (int i = start; i < start + count; i++) { 253 markCellsAsUnoccupiedForView(getChildAt(i)); 254 } 255 super.removeViews(start, count); 256 } 257 258 @Override 259 public void removeViewsInLayout(int start, int count) { 260 for (int i = start; i < start + count; i++) { 261 markCellsAsUnoccupiedForView(getChildAt(i)); 262 } 263 super.removeViewsInLayout(start, count); 264 } 265 266 @Override 267 public void requestChildFocus(View child, View focused) { 268 super.requestChildFocus(child, focused); 269 if (child != null) { 270 Rect r = new Rect(); 271 child.getDrawingRect(r); 272 requestRectangleOnScreen(r); 273 } 274 } 275 276 @Override 277 protected void onAttachedToWindow() { 278 super.onAttachedToWindow(); 279 mCellInfo.screen = ((ViewGroup) getParent()).indexOfChild(this); 280 } 281 282 public void setTagToCellInfoForPoint(int touchX, int touchY) { 283 final CellInfo cellInfo = mCellInfo; 284 final Rect frame = mRect; 285 final int x = touchX + mScrollX; 286 final int y = touchY + mScrollY; 287 final int count = getChildCount(); 288 289 boolean found = false; 290 for (int i = count - 1; i >= 0; i--) { 291 final View child = getChildAt(i); 292 293 if ((child.getVisibility()) == VISIBLE || child.getAnimation() != null) { 294 child.getHitRect(frame); 295 if (frame.contains(x, y)) { 296 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 297 cellInfo.cell = child; 298 cellInfo.cellX = lp.cellX; 299 cellInfo.cellY = lp.cellY; 300 cellInfo.spanX = lp.cellHSpan; 301 cellInfo.spanY = lp.cellVSpan; 302 cellInfo.valid = true; 303 found = true; 304 break; 305 } 306 } 307 } 308 309 if (!found) { 310 final int cellXY[] = mTmpCellXY; 311 pointToCellExact(x, y, cellXY); 312 313 cellInfo.cell = null; 314 cellInfo.cellX = cellXY[0]; 315 cellInfo.cellY = cellXY[1]; 316 cellInfo.spanX = 1; 317 cellInfo.spanY = 1; 318 cellInfo.valid = cellXY[0] >= 0 && cellXY[1] >= 0 && cellXY[0] < mCountX && 319 cellXY[1] < mCountY && !mOccupied[cellXY[0]][cellXY[1]]; 320 } 321 setTag(cellInfo); 322 } 323 324 325 @Override 326 public boolean onInterceptTouchEvent(MotionEvent ev) { 327 if (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev)) { 328 return true; 329 } 330 final int action = ev.getAction(); 331 final CellInfo cellInfo = mCellInfo; 332 333 if (action == MotionEvent.ACTION_DOWN) { 334 setTagToCellInfoForPoint((int) ev.getX(), (int) ev.getY()); 335 } else if (action == MotionEvent.ACTION_UP) { 336 cellInfo.cell = null; 337 cellInfo.cellX = -1; 338 cellInfo.cellY = -1; 339 cellInfo.spanX = 0; 340 cellInfo.spanY = 0; 341 cellInfo.valid = false; 342 setTag(cellInfo); 343 } 344 345 return false; 346 } 347 348 @Override 349 public CellInfo getTag() { 350 return (CellInfo) super.getTag(); 351 } 352 353 /** 354 * Check if the row 'y' is empty from columns 'left' to 'right', inclusive. 355 */ 356 private static boolean isRowEmpty(int y, int left, int right, boolean[][] occupied) { 357 for (int x = left; x <= right; x++) { 358 if (occupied[x][y]) { 359 return false; 360 } 361 } 362 return true; 363 } 364 365 /** 366 * Given a point, return the cell that strictly encloses that point 367 * @param x X coordinate of the point 368 * @param y Y coordinate of the point 369 * @param result Array of 2 ints to hold the x and y coordinate of the cell 370 */ 371 void pointToCellExact(int x, int y, int[] result) { 372 final int hStartPadding = getLeftPadding(); 373 final int vStartPadding = getTopPadding(); 374 375 result[0] = (x - hStartPadding) / (mCellWidth + mWidthGap); 376 result[1] = (y - vStartPadding) / (mCellHeight + mHeightGap); 377 378 final int xAxis = mCountX; 379 final int yAxis = mCountY; 380 381 if (result[0] < 0) result[0] = 0; 382 if (result[0] >= xAxis) result[0] = xAxis - 1; 383 if (result[1] < 0) result[1] = 0; 384 if (result[1] >= yAxis) result[1] = yAxis - 1; 385 } 386 387 /** 388 * Given a point, return the cell that most closely encloses that point 389 * @param x X coordinate of the point 390 * @param y Y coordinate of the point 391 * @param result Array of 2 ints to hold the x and y coordinate of the cell 392 */ 393 void pointToCellRounded(int x, int y, int[] result) { 394 pointToCellExact(x + (mCellWidth / 2), y + (mCellHeight / 2), result); 395 } 396 397 /** 398 * Given a cell coordinate, return the point that represents the upper left corner of that cell 399 * 400 * @param cellX X coordinate of the cell 401 * @param cellY Y coordinate of the cell 402 * 403 * @param result Array of 2 ints to hold the x and y coordinate of the point 404 */ 405 void cellToPoint(int cellX, int cellY, int[] result) { 406 final int hStartPadding = getLeftPadding(); 407 final int vStartPadding = getTopPadding(); 408 409 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap); 410 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap); 411 } 412 413 int getCellWidth() { 414 return mCellWidth; 415 } 416 417 int getCellHeight() { 418 return mCellHeight; 419 } 420 421 int getLeftPadding() { 422 return mLeftPadding; 423 } 424 425 int getTopPadding() { 426 return mTopPadding; 427 } 428 429 int getRightPadding() { 430 return mRightPadding; 431 } 432 433 int getBottomPadding() { 434 return mBottomPadding; 435 } 436 437 @Override 438 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 439 // TODO: currently ignoring padding 440 441 int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); 442 int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); 443 444 int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); 445 int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); 446 447 if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) { 448 throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions"); 449 } 450 451 final int cellWidth = mCellWidth; 452 final int cellHeight = mCellHeight; 453 454 int numWidthGaps = mCountX - 1; 455 int numHeightGaps = mCountY - 1; 456 457 int vSpaceLeft = heightSpecSize - mTopPadding - mBottomPadding - (cellHeight * mCountY); 458 mHeightGap = vSpaceLeft / numHeightGaps; 459 460 int hSpaceLeft = widthSpecSize - mLeftPadding - mRightPadding - (cellWidth * mCountX); 461 mWidthGap = hSpaceLeft / numWidthGaps; 462 463 // center it around the min gaps 464 int minGap = Math.min(mWidthGap, mHeightGap); 465 mWidthGap = mHeightGap = minGap; 466 467 int count = getChildCount(); 468 469 for (int i = 0; i < count; i++) { 470 View child = getChildAt(i); 471 LayoutParams lp = (LayoutParams) child.getLayoutParams(); 472 lp.setup(cellWidth, cellHeight, mWidthGap, mHeightGap, 473 mLeftPadding, mTopPadding); 474 475 int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY); 476 int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(lp.height, 477 MeasureSpec.EXACTLY); 478 479 child.measure(childWidthMeasureSpec, childheightMeasureSpec); 480 } 481 if (widthSpecMode == MeasureSpec.AT_MOST) { 482 int newWidth = mLeftPadding + mRightPadding + (mCountX * cellWidth) + 483 ((mCountX - 1) * minGap); 484 int newHeight = mTopPadding + mBottomPadding + (mCountY * cellHeight) + 485 ((mCountY - 1) * minGap); 486 setMeasuredDimension(newWidth, newHeight); 487 } else if (widthSpecMode == MeasureSpec.EXACTLY) { 488 setMeasuredDimension(widthSpecSize, heightSpecSize); 489 } 490 } 491 492 @Override 493 public void onLayout(boolean changed, int l, int t, int r, int b) { 494 int count = getChildCount(); 495 496 for (int i = 0; i < count; i++) { 497 View child = getChildAt(i); 498 if (child.getVisibility() != GONE) { 499 500 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams(); 501 502 int childLeft = lp.x; 503 int childTop = lp.y; 504 child.layout(childLeft, childTop, childLeft + lp.width, childTop + lp.height); 505 506 if (lp.dropped) { 507 lp.dropped = false; 508 509 final int[] cellXY = mTmpCellXY; 510 getLocationOnScreen(cellXY); 511 mWallpaperManager.sendWallpaperCommand(getWindowToken(), "android.home.drop", 512 cellXY[0] + childLeft + lp.width / 2, 513 cellXY[1] + childTop + lp.height / 2, 0, null); 514 } 515 } 516 } 517 } 518 519 @Override 520 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 521 super.onSizeChanged(w, h, oldw, oldh); 522 mBackgroundLayoutRect.set(0, 0, w, h); 523 if (mBackground != null) { 524 mBackground.setBounds(mBackgroundLayoutRect); 525 } 526 if (mBackgroundHover != null) { 527 mBackgroundHover.setBounds(mBackgroundLayoutRect); 528 } 529 } 530 531 @Override 532 protected void setChildrenDrawingCacheEnabled(boolean enabled) { 533 final int count = getChildCount(); 534 for (int i = 0; i < count; i++) { 535 final View view = getChildAt(i); 536 view.setDrawingCacheEnabled(enabled); 537 // Update the drawing caches 538 view.buildDrawingCache(true); 539 } 540 } 541 542 @Override 543 protected void setChildrenDrawnWithCacheEnabled(boolean enabled) { 544 super.setChildrenDrawnWithCacheEnabled(enabled); 545 } 546 547 public float getBackgroundAlpha() { 548 return mBackgroundAlpha; 549 } 550 551 public void setBackgroundAlpha(float alpha) { 552 mBackgroundAlpha = alpha; 553 invalidate(); 554 } 555 556 // Need to return true to let the view system know we know how to handle alpha-- this is 557 // because when our children have an alpha of 0.0f, they are still rendering their "dimmed" 558 // versions 559 @Override 560 protected boolean onSetAlpha(int alpha) { 561 return true; 562 } 563 564 public void setAlpha(float alpha) { 565 setChildrenAlpha(alpha); 566 super.setAlpha(alpha); 567 } 568 569 private void setChildrenAlpha(float alpha) { 570 final int childCount = getChildCount(); 571 for (int i = 0; i < childCount; i++) { 572 getChildAt(i).setAlpha(alpha); 573 } 574 } 575 576 private boolean isVacantIgnoring( 577 int originX, int originY, int spanX, int spanY, View ignoreView) { 578 if (ignoreView != null) { 579 markCellsAsUnoccupiedForView(ignoreView); 580 } 581 for (int i = 0; i < spanY; i++) { 582 if (!isRowEmpty(originY + i, originX, originX + spanX - 1, mOccupied)) { 583 if (ignoreView != null) { 584 markCellsAsOccupiedForView(ignoreView); 585 } 586 return false; 587 } 588 } 589 if (ignoreView != null) { 590 markCellsAsOccupiedForView(ignoreView); 591 } 592 return true; 593 } 594 595 private boolean isVacant(int originX, int originY, int spanX, int spanY) { 596 return isVacantIgnoring(originX, originY, spanX, spanY, null); 597 } 598 599 public View getChildAt(int x, int y) { 600 final int count = getChildCount(); 601 for (int i = 0; i < count; i++) { 602 View child = getChildAt(i); 603 LayoutParams lp = (LayoutParams) child.getLayoutParams(); 604 605 if ((lp.cellX <= x) && (x < lp.cellX + lp.cellHSpan) && 606 (lp.cellY <= y) && (y < lp.cellY + lp.cellHSpan)) { 607 return child; 608 } 609 } 610 return null; 611 } 612 613 /** 614 * Estimate the size that a child with the given dimensions will take in the layout. 615 */ 616 void estimateChildSize(int minWidth, int minHeight, int[] result) { 617 // Assuming it's placed at 0, 0, find where the bottom right cell will land 618 rectToCell(minWidth, minHeight, result); 619 620 // Then figure out the rect it will occupy 621 cellToRect(0, 0, result[0], result[1], mRectF); 622 result[0] = (int)mRectF.width(); 623 result[1] = (int)mRectF.height(); 624 } 625 626 /** 627 * Estimate where the top left cell of the dragged item will land if it is dropped. 628 * 629 * @param originX The X value of the top left corner of the item 630 * @param originY The Y value of the top left corner of the item 631 * @param spanX The number of horizontal cells that the item spans 632 * @param spanY The number of vertical cells that the item spans 633 * @param result The estimated drop cell X and Y. 634 */ 635 void estimateDropCell(int originX, int originY, int spanX, int spanY, int[] result) { 636 final int countX = mCountX; 637 final int countY = mCountY; 638 639 // pointToCellRounded takes the top left of a cell but will pad that with 640 // cellWidth/2 and cellHeight/2 when finding the matching cell 641 pointToCellRounded(originX, originY, result); 642 643 // If the item isn't fully on this screen, snap to the edges 644 int rightOverhang = result[0] + spanX - countX; 645 if (rightOverhang > 0) { 646 result[0] -= rightOverhang; // Snap to right 647 } 648 result[0] = Math.max(0, result[0]); // Snap to left 649 int bottomOverhang = result[1] + spanY - countY; 650 if (bottomOverhang > 0) { 651 result[1] -= bottomOverhang; // Snap to bottom 652 } 653 result[1] = Math.max(0, result[1]); // Snap to top 654 } 655 656 void visualizeDropLocation( 657 View view, int originX, int originY, int spanX, int spanY, View draggedItem) { 658 final int[] originCell = mDragCell; 659 final int[] cellXY = mTmpCellXY; 660 estimateDropCell(originX, originY, spanX, spanY, cellXY); 661 662 // Only recalculate the bounding rect when necessary 663 if (!Arrays.equals(cellXY, originCell)) { 664 originCell[0] = cellXY[0]; 665 originCell[1] = cellXY[1]; 666 667 // Find the top left corner of the rect the object will occupy 668 final int[] topLeft = mTmpCellXY; 669 cellToPoint(originCell[0], originCell[1], topLeft); 670 final int left = topLeft[0]; 671 final int top = topLeft[1]; 672 673 // Now find the bottom right 674 final int[] bottomRight = mTmpCellXY; 675 cellToPoint(originCell[0] + spanX - 1, originCell[1] + spanY - 1, bottomRight); 676 bottomRight[0] += mCellWidth; 677 bottomRight[1] += mCellHeight; 678 679 boolean vacant = 680 isVacantIgnoring(originCell[0], originCell[1], spanX, spanY, draggedItem); 681 mDragRectDrawable = vacant ? mVacantDrawable : mOccupiedDrawable; 682 683 // mDragRect will be rendered in onDraw() 684 mDragRect.set(left, top, bottomRight[0], bottomRight[1]); 685 invalidate(); 686 } 687 } 688 689 /** 690 * Find a vacant area that will fit the given bounds nearest the requested 691 * cell location. Uses Euclidean distance to score multiple vacant areas. 692 * 693 * @param pixelX The X location at which you want to search for a vacant area. 694 * @param pixelY The Y location at which you want to search for a vacant area. 695 * @param spanX Horizontal span of the object. 696 * @param spanY Vertical span of the object. 697 * @param vacantCells Pre-computed set of vacant cells to search. 698 * @param recycle Previously returned value to possibly recycle. 699 * @return The X, Y cell of a vacant area that can contain this object, 700 * nearest the requested location. 701 */ 702 int[] findNearestVacantArea(int pixelX, int pixelY, int spanX, int spanY, int[] recycle) { 703 704 // Keep track of best-scoring drop area 705 final int[] bestXY = recycle != null ? recycle : new int[2]; 706 double bestDistance = Double.MAX_VALUE; 707 708 for (int x = 0; x < mCountX - (spanX - 1); x++) { 709 inner: 710 for (int y = 0; y < mCountY - (spanY - 1); y++) { 711 for (int i = 0; i < spanX; i++) { 712 for (int j = 0; j < spanY; j++) { 713 if (mOccupied[x + i][y + j]) { 714 // small optimization: we can skip to below the row we just found 715 // an occupied cell 716 y += j; 717 continue inner; 718 } 719 } 720 } 721 final int[] cellXY = mTmpCellXY; 722 cellToPoint(x, y, cellXY); 723 724 double distance = Math.sqrt(Math.pow(cellXY[0] - pixelX, 2) 725 + Math.pow(cellXY[1] - pixelY, 2)); 726 if (distance <= bestDistance) { 727 bestDistance = distance; 728 bestXY[0] = x; 729 bestXY[1] = y; 730 } 731 } 732 } 733 734 // Return null if no suitable location found 735 if (bestDistance < Double.MAX_VALUE) { 736 return bestXY; 737 } else { 738 return null; 739 } 740 } 741 742 boolean existsEmptyCell() { 743 return findCellForSpan(null, 1, 1); 744 } 745 746 /** 747 * Finds the upper-left coordinate of the first rectangle in the grid that can 748 * hold a cell of the specified dimensions. If intersectX and intersectY are not -1, 749 * then this method will only return coordinates for rectangles that contain the cell 750 * (intersectX, intersectY) 751 * 752 * @param cellXY The array that will contain the position of a vacant cell if such a cell 753 * can be found. 754 * @param spanX The horizontal span of the cell we want to find. 755 * @param spanY The vertical span of the cell we want to find. 756 * 757 * @return True if a vacant cell of the specified dimension was found, false otherwise. 758 */ 759 boolean findCellForSpan(int[] cellXY, int spanX, int spanY) { 760 return findCellForSpanThatIntersectsIgnoring(cellXY, spanX, spanY, -1, -1, null); 761 } 762 763 /** 764 * Like above, but ignores any cells occupied by the item "ignoreView" 765 * 766 * @param cellXY The array that will contain the position of a vacant cell if such a cell 767 * can be found. 768 * @param spanX The horizontal span of the cell we want to find. 769 * @param spanY The vertical span of the cell we want to find. 770 * @param ignoreView The home screen item we should treat as not occupying any space 771 * @return 772 */ 773 boolean findCellForSpanIgnoring(int[] cellXY, int spanX, int spanY, View ignoreView) { 774 return findCellForSpanThatIntersectsIgnoring(cellXY, spanX, spanY, -1, -1, ignoreView); 775 } 776 777 /** 778 * Like above, but if intersectX and intersectY are not -1, then this method will try to 779 * return coordinates for rectangles that contain the cell [intersectX, intersectY] 780 * 781 * @param spanX The horizontal span of the cell we want to find. 782 * @param spanY The vertical span of the cell we want to find. 783 * @param ignoreView The home screen item we should treat as not occupying any space 784 * @param intersectX The X coordinate of the cell that we should try to overlap 785 * @param intersectX The Y coordinate of the cell that we should try to overlap 786 * 787 * @return True if a vacant cell of the specified dimension was found, false otherwise. 788 */ 789 boolean findCellForSpanThatIntersects(int[] cellXY, int spanX, int spanY, 790 int intersectX, int intersectY) { 791 return findCellForSpanThatIntersectsIgnoring( 792 cellXY, spanX, spanY, intersectX, intersectY, null); 793 } 794 795 /** 796 * The superset of the above two methods 797 */ 798 boolean findCellForSpanThatIntersectsIgnoring(int[] cellXY, int spanX, int spanY, 799 int intersectX, int intersectY, View ignoreView) { 800 if (ignoreView != null) { 801 markCellsAsUnoccupiedForView(ignoreView); 802 } 803 804 while (true) { 805 int startX = 0; 806 if (intersectX >= 0) { 807 startX = Math.max(startX, intersectX - (spanX - 1)); 808 } 809 int endX = mCountX - (spanX - 1); 810 if (intersectX >= 0) { 811 endX = Math.min(endX, intersectX + (spanX - 1) + (spanX == 1 ? 1 : 0)); 812 } 813 int startY = 0; 814 if (intersectY >= 0) { 815 startY = Math.max(startY, intersectY - (spanY - 1)); 816 } 817 int endY = mCountY - (spanY - 1); 818 if (intersectY >= 0) { 819 endY = Math.min(endY, intersectY + (spanY - 1) + (spanY == 1 ? 1 : 0)); 820 } 821 822 for (int x = startX; x < endX; x++) { 823 inner: 824 for (int y = startY; y < endY; y++) { 825 for (int i = 0; i < spanX; i++) { 826 for (int j = 0; j < spanY; j++) { 827 if (mOccupied[x + i][y + j]) { 828 // small optimization: we can skip to below the row we just found 829 // an occupied cell 830 y += j; 831 continue inner; 832 } 833 } 834 } 835 if (cellXY != null) { 836 cellXY[0] = x; 837 cellXY[1] = y; 838 } 839 if (ignoreView != null) { 840 markCellsAsOccupiedForView(ignoreView); 841 } 842 return true; 843 } 844 } 845 if (intersectX == -1 && intersectY == -1) { 846 break; 847 } else { 848 // if we failed to find anything, try again but without any requirements of 849 // intersecting 850 intersectX = -1; 851 intersectY = -1; 852 continue; 853 } 854 } 855 856 if (ignoreView != null) { 857 markCellsAsOccupiedForView(ignoreView); 858 } 859 return false; 860 } 861 862 /** 863 * Called when drag has left this CellLayout or has been completed (successfully or not) 864 */ 865 void onDragExit() { 866 // Invalidate the drag data 867 mDragCell[0] = -1; 868 mDragCell[1] = -1; 869 870 setHover(false); 871 mDragRect.setEmpty(); 872 invalidate(); 873 } 874 875 /** 876 * Mark a child as having been dropped. 877 * 878 * @param child The child that is being dropped 879 */ 880 void onDropChild(View child) { 881 if (child != null) { 882 LayoutParams lp = (LayoutParams) child.getLayoutParams(); 883 lp.isDragging = false; 884 lp.dropped = true; 885 mDragRect.setEmpty(); 886 child.requestLayout(); 887 } 888 onDragExit(); 889 } 890 891 void onDropAborted(View child) { 892 if (child != null) { 893 ((LayoutParams) child.getLayoutParams()).isDragging = false; 894 } 895 onDragExit(); 896 } 897 898 /** 899 * Start dragging the specified child 900 * 901 * @param child The child that is being dragged 902 */ 903 void onDragChild(View child) { 904 LayoutParams lp = (LayoutParams) child.getLayoutParams(); 905 lp.isDragging = true; 906 mDragRect.setEmpty(); 907 } 908 909 /** 910 * Computes a bounding rectangle for a range of cells 911 * 912 * @param cellX X coordinate of upper left corner expressed as a cell position 913 * @param cellY Y coordinate of upper left corner expressed as a cell position 914 * @param cellHSpan Width in cells 915 * @param cellVSpan Height in cells 916 * @param resultRect Rect into which to put the results 917 */ 918 public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, RectF resultRect) { 919 final int cellWidth = mCellWidth; 920 final int cellHeight = mCellHeight; 921 final int widthGap = mWidthGap; 922 final int heightGap = mHeightGap; 923 924 final int hStartPadding = getLeftPadding(); 925 final int vStartPadding = getTopPadding(); 926 927 int width = cellHSpan * cellWidth + ((cellHSpan - 1) * widthGap); 928 int height = cellVSpan * cellHeight + ((cellVSpan - 1) * heightGap); 929 930 int x = hStartPadding + cellX * (cellWidth + widthGap); 931 int y = vStartPadding + cellY * (cellHeight + heightGap); 932 933 resultRect.set(x, y, x + width, y + height); 934 } 935 936 /** 937 * Computes the required horizontal and vertical cell spans to always 938 * fit the given rectangle. 939 * 940 * @param width Width in pixels 941 * @param height Height in pixels 942 * @param result An array of length 2 in which to store the result (may be null). 943 */ 944 public int[] rectToCell(int width, int height, int[] result) { 945 // Always assume we're working with the smallest span to make sure we 946 // reserve enough space in both orientations. 947 final Resources resources = getResources(); 948 int actualWidth = resources.getDimensionPixelSize(R.dimen.workspace_cell_width); 949 int actualHeight = resources.getDimensionPixelSize(R.dimen.workspace_cell_height); 950 int smallerSize = Math.min(actualWidth, actualHeight); 951 952 // Always round up to next largest cell 953 int spanX = (width + smallerSize) / smallerSize; 954 int spanY = (height + smallerSize) / smallerSize; 955 956 if (result == null) { 957 return new int[] { spanX, spanY }; 958 } 959 result[0] = spanX; 960 result[1] = spanY; 961 return result; 962 } 963 964 /** 965 * Find the first vacant cell, if there is one. 966 * 967 * @param vacant Holds the x and y coordinate of the vacant cell 968 * @param spanX Horizontal cell span. 969 * @param spanY Vertical cell span. 970 * 971 * @return True if a vacant cell was found 972 */ 973 public boolean getVacantCell(int[] vacant, int spanX, int spanY) { 974 975 return findVacantCell(vacant, spanX, spanY, mCountX, mCountY, mOccupied); 976 } 977 978 static boolean findVacantCell(int[] vacant, int spanX, int spanY, 979 int xCount, int yCount, boolean[][] occupied) { 980 981 for (int x = 0; x < xCount; x++) { 982 for (int y = 0; y < yCount; y++) { 983 boolean available = !occupied[x][y]; 984out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) { 985 for (int j = y; j < y + spanY - 1 && y < yCount; j++) { 986 available = available && !occupied[i][j]; 987 if (!available) break out; 988 } 989 } 990 991 if (available) { 992 vacant[0] = x; 993 vacant[1] = y; 994 return true; 995 } 996 } 997 } 998 999 return false; 1000 } 1001 1002 /** 1003 * Update the array of occupied cells (mOccupied), and return a flattened copy of the array. 1004 */ 1005 boolean[] getOccupiedCellsFlattened() { 1006 final int xCount = mCountX; 1007 final int yCount = mCountY; 1008 final boolean[][] occupied = mOccupied; 1009 1010 final boolean[] flat = new boolean[xCount * yCount]; 1011 for (int y = 0; y < yCount; y++) { 1012 for (int x = 0; x < xCount; x++) { 1013 flat[y * xCount + x] = occupied[x][y]; 1014 } 1015 } 1016 1017 return flat; 1018 } 1019 1020 private void clearOccupiedCells() { 1021 for (int x = 0; x < mCountX; x++) { 1022 for (int y = 0; y < mCountY; y++) { 1023 mOccupied[x][y] = false; 1024 } 1025 } 1026 } 1027 1028 public void onMove(View view, int newCellX, int newCellY) { 1029 LayoutParams lp = (LayoutParams) view.getLayoutParams(); 1030 markCellsAsUnoccupiedForView(view); 1031 markCellsForView(newCellX, newCellY, lp.cellHSpan, lp.cellVSpan, true); 1032 } 1033 1034 private void markCellsAsOccupiedForView(View view) { 1035 LayoutParams lp = (LayoutParams) view.getLayoutParams(); 1036 markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, true); 1037 } 1038 1039 private void markCellsAsUnoccupiedForView(View view) { 1040 LayoutParams lp = (LayoutParams) view.getLayoutParams(); 1041 markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, false); 1042 } 1043 1044 private void markCellsForView(int cellX, int cellY, int spanX, int spanY, boolean value) { 1045 for (int x = cellX; x < cellX + spanX && x < mCountX; x++) { 1046 for (int y = cellY; y < cellY + spanY && y < mCountY; y++) { 1047 mOccupied[x][y] = value; 1048 } 1049 } 1050 } 1051 1052 @Override 1053 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { 1054 return new CellLayout.LayoutParams(getContext(), attrs); 1055 } 1056 1057 @Override 1058 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 1059 return p instanceof CellLayout.LayoutParams; 1060 } 1061 1062 @Override 1063 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { 1064 return new CellLayout.LayoutParams(p); 1065 } 1066 1067 public static class CellLayoutAnimationController extends LayoutAnimationController { 1068 public CellLayoutAnimationController(Animation animation, float delay) { 1069 super(animation, delay); 1070 } 1071 1072 @Override 1073 protected long getDelayForView(View view) { 1074 return (int) (Math.random() * 150); 1075 } 1076 } 1077 1078 public static class LayoutParams extends ViewGroup.MarginLayoutParams { 1079 /** 1080 * Horizontal location of the item in the grid. 1081 */ 1082 @ViewDebug.ExportedProperty 1083 public int cellX; 1084 1085 /** 1086 * Vertical location of the item in the grid. 1087 */ 1088 @ViewDebug.ExportedProperty 1089 public int cellY; 1090 1091 /** 1092 * Number of cells spanned horizontally by the item. 1093 */ 1094 @ViewDebug.ExportedProperty 1095 public int cellHSpan; 1096 1097 /** 1098 * Number of cells spanned vertically by the item. 1099 */ 1100 @ViewDebug.ExportedProperty 1101 public int cellVSpan; 1102 1103 /** 1104 * Is this item currently being dragged 1105 */ 1106 public boolean isDragging; 1107 1108 // X coordinate of the view in the layout. 1109 @ViewDebug.ExportedProperty 1110 int x; 1111 // Y coordinate of the view in the layout. 1112 @ViewDebug.ExportedProperty 1113 int y; 1114 1115 boolean dropped; 1116 1117 public LayoutParams(Context c, AttributeSet attrs) { 1118 super(c, attrs); 1119 cellHSpan = 1; 1120 cellVSpan = 1; 1121 } 1122 1123 public LayoutParams(ViewGroup.LayoutParams source) { 1124 super(source); 1125 cellHSpan = 1; 1126 cellVSpan = 1; 1127 } 1128 1129 public LayoutParams(LayoutParams source) { 1130 super(source); 1131 this.cellX = source.cellX; 1132 this.cellY = source.cellY; 1133 this.cellHSpan = source.cellHSpan; 1134 this.cellVSpan = source.cellVSpan; 1135 } 1136 1137 public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) { 1138 super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); 1139 this.cellX = cellX; 1140 this.cellY = cellY; 1141 this.cellHSpan = cellHSpan; 1142 this.cellVSpan = cellVSpan; 1143 } 1144 1145 public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap, 1146 int hStartPadding, int vStartPadding) { 1147 1148 final int myCellHSpan = cellHSpan; 1149 final int myCellVSpan = cellVSpan; 1150 final int myCellX = cellX; 1151 final int myCellY = cellY; 1152 1153 width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) - 1154 leftMargin - rightMargin; 1155 height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) - 1156 topMargin - bottomMargin; 1157 1158 x = hStartPadding + myCellX * (cellWidth + widthGap) + leftMargin; 1159 y = vStartPadding + myCellY * (cellHeight + heightGap) + topMargin; 1160 } 1161 1162 public String toString() { 1163 return "(" + this.cellX + ", " + this.cellY + ")"; 1164 } 1165 } 1166 1167 // This class stores info for two purposes: 1168 // 1. When dragging items (mDragInfo in Workspace), we store the View, its cellX & cellY, 1169 // its spanX, spanY, and the screen it is on 1170 // 2. When long clicking on an empty cell in a CellLayout, we save information about the 1171 // cellX and cellY coordinates and which page was clicked. We then set this as a tag on 1172 // the CellLayout that was long clicked 1173 static final class CellInfo implements ContextMenu.ContextMenuInfo { 1174 View cell; 1175 int cellX = -1; 1176 int cellY = -1; 1177 int spanX; 1178 int spanY; 1179 int screen; 1180 boolean valid; 1181 1182 @Override 1183 public String toString() { 1184 return "Cell[view=" + (cell == null ? "null" : cell.getClass()) 1185 + ", x=" + cellX + ", y=" + cellY + "]"; 1186 } 1187 } 1188} 1189