CellLayout.java revision 1e4359c54e82910e3c8bfba3d520ed27c91367b9
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.launcher3; 18 19import android.animation.Animator; 20import android.animation.AnimatorListenerAdapter; 21import android.animation.AnimatorSet; 22import android.animation.TimeInterpolator; 23import android.animation.ValueAnimator; 24import android.animation.ValueAnimator.AnimatorUpdateListener; 25import android.content.Context; 26import android.content.res.Resources; 27import android.content.res.TypedArray; 28import android.graphics.Bitmap; 29import android.graphics.Canvas; 30import android.graphics.Color; 31import android.graphics.Paint; 32import android.graphics.Point; 33import android.graphics.Rect; 34import android.graphics.drawable.ColorDrawable; 35import android.graphics.drawable.Drawable; 36import android.os.Parcelable; 37import android.util.AttributeSet; 38import android.util.Log; 39import android.util.SparseArray; 40import android.view.MotionEvent; 41import android.view.View; 42import android.view.ViewDebug; 43import android.view.ViewGroup; 44import android.view.animation.Animation; 45import android.view.animation.DecelerateInterpolator; 46import android.view.animation.LayoutAnimationController; 47 48import com.android.launcher3.FolderIcon.FolderRingAnimator; 49 50import java.util.ArrayList; 51import java.util.Arrays; 52import java.util.Collections; 53import java.util.Comparator; 54import java.util.HashMap; 55import java.util.Stack; 56 57public class CellLayout extends ViewGroup { 58 static final String TAG = "CellLayout"; 59 60 private Launcher mLauncher; 61 private int mCellWidth; 62 private int mCellHeight; 63 private int mFixedCellWidth; 64 private int mFixedCellHeight; 65 66 private int mCountX; 67 private int mCountY; 68 69 private int mOriginalWidthGap; 70 private int mOriginalHeightGap; 71 private int mWidthGap; 72 private int mHeightGap; 73 private int mMaxGap; 74 private boolean mDropPending = false; 75 76 // These are temporary variables to prevent having to allocate a new object just to 77 // return an (x, y) value from helper functions. Do NOT use them to maintain other state. 78 private final int[] mTmpXY = new int[2]; 79 private final int[] mTmpPoint = new int[2]; 80 int[] mTempLocation = new int[2]; 81 82 boolean[][] mOccupied; 83 boolean[][] mTmpOccupied; 84 private boolean mLastDownOnOccupiedCell = false; 85 86 private OnTouchListener mInterceptTouchListener; 87 88 private ArrayList<FolderRingAnimator> mFolderOuterRings = new ArrayList<FolderRingAnimator>(); 89 private int[] mFolderLeaveBehindCell = {-1, -1}; 90 91 private float FOREGROUND_ALPHA_DAMPER = 0.65f; 92 private int mForegroundAlpha = 0; 93 private float mBackgroundAlpha; 94 private float mBackgroundAlphaMultiplier = 1.0f; 95 private boolean mDrawBackground = true; 96 97 private Drawable mNormalBackground; 98 private Drawable mActiveGlowBackground; 99 private Drawable mOverScrollForegroundDrawable; 100 private Drawable mOverScrollLeft; 101 private Drawable mOverScrollRight; 102 private Rect mBackgroundRect; 103 private Rect mForegroundRect; 104 private int mForegroundPadding; 105 106 // These values allow a fixed measurement to be set on the CellLayout. 107 private int mFixedWidth = -1; 108 private int mFixedHeight = -1; 109 110 // If we're actively dragging something over this screen, mIsDragOverlapping is true 111 private boolean mIsDragOverlapping = false; 112 boolean mUseActiveGlowBackground = false; 113 114 // These arrays are used to implement the drag visualization on x-large screens. 115 // They are used as circular arrays, indexed by mDragOutlineCurrent. 116 private Rect[] mDragOutlines = new Rect[4]; 117 private float[] mDragOutlineAlphas = new float[mDragOutlines.length]; 118 private InterruptibleInOutAnimator[] mDragOutlineAnims = 119 new InterruptibleInOutAnimator[mDragOutlines.length]; 120 121 // Used as an index into the above 3 arrays; indicates which is the most current value. 122 private int mDragOutlineCurrent = 0; 123 private final Paint mDragOutlinePaint = new Paint(); 124 125 private BubbleTextView mPressedOrFocusedIcon; 126 127 private HashMap<CellLayout.LayoutParams, Animator> mReorderAnimators = new 128 HashMap<CellLayout.LayoutParams, Animator>(); 129 private HashMap<View, ReorderPreviewAnimation> 130 mShakeAnimators = new HashMap<View, ReorderPreviewAnimation>(); 131 132 private boolean mItemPlacementDirty = false; 133 134 // When a drag operation is in progress, holds the nearest cell to the touch point 135 private final int[] mDragCell = new int[2]; 136 137 private boolean mDragging = false; 138 139 private TimeInterpolator mEaseOutInterpolator; 140 private ShortcutAndWidgetContainer mShortcutsAndWidgets; 141 142 private boolean mIsHotseat = false; 143 private float mHotseatScale = 1f; 144 145 public static final int MODE_SHOW_REORDER_HINT = 0; 146 public static final int MODE_DRAG_OVER = 1; 147 public static final int MODE_ON_DROP = 2; 148 public static final int MODE_ON_DROP_EXTERNAL = 3; 149 public static final int MODE_ACCEPT_DROP = 4; 150 private static final boolean DESTRUCTIVE_REORDER = false; 151 private static final boolean DEBUG_VISUALIZE_OCCUPIED = false; 152 153 static final int LANDSCAPE = 0; 154 static final int PORTRAIT = 1; 155 156 private static final float REORDER_PREVIEW_MAGNITUDE = 0.12f; 157 private static final int REORDER_ANIMATION_DURATION = 150; 158 private float mReorderPreviewAnimationMagnitude; 159 160 private ArrayList<View> mIntersectingViews = new ArrayList<View>(); 161 private Rect mOccupiedRect = new Rect(); 162 private int[] mDirectionVector = new int[2]; 163 int[] mPreviousReorderDirection = new int[2]; 164 private static final int INVALID_DIRECTION = -100; 165 private DropTarget.DragEnforcer mDragEnforcer; 166 167 private Rect mTempRect = new Rect(); 168 169 private final static Paint sPaint = new Paint(); 170 171 public CellLayout(Context context) { 172 this(context, null); 173 } 174 175 public CellLayout(Context context, AttributeSet attrs) { 176 this(context, attrs, 0); 177 } 178 179 public CellLayout(Context context, AttributeSet attrs, int defStyle) { 180 super(context, attrs, defStyle); 181 mDragEnforcer = new DropTarget.DragEnforcer(context); 182 183 // A ViewGroup usually does not draw, but CellLayout needs to draw a rectangle to show 184 // the user where a dragged item will land when dropped. 185 setWillNotDraw(false); 186 setClipToPadding(false); 187 mLauncher = (Launcher) context; 188 189 LauncherAppState app = LauncherAppState.getInstance(); 190 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile(); 191 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0); 192 193 mCellWidth = mCellHeight = -1; 194 mFixedCellWidth = mFixedCellHeight = -1; 195 mWidthGap = mOriginalWidthGap = 0; 196 mHeightGap = mOriginalHeightGap = 0; 197 mMaxGap = Integer.MAX_VALUE; 198 mCountX = (int) grid.numColumns; 199 mCountY = (int) grid.numRows; 200 mOccupied = new boolean[mCountX][mCountY]; 201 mTmpOccupied = new boolean[mCountX][mCountY]; 202 mPreviousReorderDirection[0] = INVALID_DIRECTION; 203 mPreviousReorderDirection[1] = INVALID_DIRECTION; 204 205 a.recycle(); 206 207 setAlwaysDrawnWithCacheEnabled(false); 208 209 final Resources res = getResources(); 210 mHotseatScale = (float) grid.hotseatIconSizePx / grid.iconSizePx; 211 212 mNormalBackground = res.getDrawable(R.drawable.screenpanel); 213 mActiveGlowBackground = res.getDrawable(R.drawable.screenpanel_hover); 214 215 mOverScrollLeft = res.getDrawable(R.drawable.overscroll_glow_left); 216 mOverScrollRight = res.getDrawable(R.drawable.overscroll_glow_right); 217 mForegroundPadding = 218 res.getDimensionPixelSize(R.dimen.workspace_overscroll_drawable_padding); 219 220 mReorderPreviewAnimationMagnitude = (REORDER_PREVIEW_MAGNITUDE * 221 grid.iconSizePx); 222 223 mNormalBackground.setFilterBitmap(true); 224 mActiveGlowBackground.setFilterBitmap(true); 225 226 // Initialize the data structures used for the drag visualization. 227 mEaseOutInterpolator = new DecelerateInterpolator(2.5f); // Quint ease out 228 mDragCell[0] = mDragCell[1] = -1; 229 for (int i = 0; i < mDragOutlines.length; i++) { 230 mDragOutlines[i] = new Rect(-1, -1, -1, -1); 231 } 232 233 // When dragging things around the home screens, we show a green outline of 234 // where the item will land. The outlines gradually fade out, leaving a trail 235 // behind the drag path. 236 // Set up all the animations that are used to implement this fading. 237 final int duration = res.getInteger(R.integer.config_dragOutlineFadeTime); 238 final float fromAlphaValue = 0; 239 final float toAlphaValue = (float)res.getInteger(R.integer.config_dragOutlineMaxAlpha); 240 241 Arrays.fill(mDragOutlineAlphas, fromAlphaValue); 242 243 for (int i = 0; i < mDragOutlineAnims.length; i++) { 244 final InterruptibleInOutAnimator anim = 245 new InterruptibleInOutAnimator(this, duration, fromAlphaValue, toAlphaValue); 246 anim.getAnimator().setInterpolator(mEaseOutInterpolator); 247 final int thisIndex = i; 248 anim.getAnimator().addUpdateListener(new AnimatorUpdateListener() { 249 public void onAnimationUpdate(ValueAnimator animation) { 250 final Bitmap outline = (Bitmap)anim.getTag(); 251 252 // If an animation is started and then stopped very quickly, we can still 253 // get spurious updates we've cleared the tag. Guard against this. 254 if (outline == null) { 255 @SuppressWarnings("all") // suppress dead code warning 256 final boolean debug = false; 257 if (debug) { 258 Object val = animation.getAnimatedValue(); 259 Log.d(TAG, "anim " + thisIndex + " update: " + val + 260 ", isStopped " + anim.isStopped()); 261 } 262 // Try to prevent it from continuing to run 263 animation.cancel(); 264 } else { 265 mDragOutlineAlphas[thisIndex] = (Float) animation.getAnimatedValue(); 266 CellLayout.this.invalidate(mDragOutlines[thisIndex]); 267 } 268 } 269 }); 270 // The animation holds a reference to the drag outline bitmap as long is it's 271 // running. This way the bitmap can be GCed when the animations are complete. 272 anim.getAnimator().addListener(new AnimatorListenerAdapter() { 273 @Override 274 public void onAnimationEnd(Animator animation) { 275 if ((Float) ((ValueAnimator) animation).getAnimatedValue() == 0f) { 276 anim.setTag(null); 277 } 278 } 279 }); 280 mDragOutlineAnims[i] = anim; 281 } 282 283 mBackgroundRect = new Rect(); 284 mForegroundRect = new Rect(); 285 286 mShortcutsAndWidgets = new ShortcutAndWidgetContainer(context); 287 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap, 288 mCountX, mCountY); 289 290 addView(mShortcutsAndWidgets); 291 } 292 293 public void enableHardwareLayer(boolean hasLayer) { 294 mShortcutsAndWidgets.setLayerType(hasLayer ? LAYER_TYPE_HARDWARE : LAYER_TYPE_NONE, sPaint); 295 } 296 297 public void buildHardwareLayer() { 298 mShortcutsAndWidgets.buildLayer(); 299 } 300 301 public float getChildrenScale() { 302 return mIsHotseat ? mHotseatScale : 1.0f; 303 } 304 305 public void setCellDimensions(int width, int height) { 306 mFixedCellWidth = mCellWidth = width; 307 mFixedCellHeight = mCellHeight = height; 308 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap, 309 mCountX, mCountY); 310 } 311 312 public void setGridSize(int x, int y) { 313 mCountX = x; 314 mCountY = y; 315 mOccupied = new boolean[mCountX][mCountY]; 316 mTmpOccupied = new boolean[mCountX][mCountY]; 317 mTempRectStack.clear(); 318 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap, 319 mCountX, mCountY); 320 requestLayout(); 321 } 322 323 // Set whether or not to invert the layout horizontally if the layout is in RTL mode. 324 public void setInvertIfRtl(boolean invert) { 325 mShortcutsAndWidgets.setInvertIfRtl(invert); 326 } 327 328 public void setDropPending(boolean pending) { 329 mDropPending = pending; 330 } 331 332 public boolean isDropPending() { 333 return mDropPending; 334 } 335 336 private void invalidateBubbleTextView(BubbleTextView icon) { 337 final int padding = icon.getPressedOrFocusedBackgroundPadding(); 338 invalidate(icon.getLeft() + getPaddingLeft() - padding, 339 icon.getTop() + getPaddingTop() - padding, 340 icon.getRight() + getPaddingLeft() + padding, 341 icon.getBottom() + getPaddingTop() + padding); 342 } 343 344 void setOverScrollAmount(float r, boolean left) { 345 if (left && mOverScrollForegroundDrawable != mOverScrollLeft) { 346 mOverScrollForegroundDrawable = mOverScrollLeft; 347 } else if (!left && mOverScrollForegroundDrawable != mOverScrollRight) { 348 mOverScrollForegroundDrawable = mOverScrollRight; 349 } 350 351 r *= FOREGROUND_ALPHA_DAMPER; 352 mForegroundAlpha = (int) Math.round((r * 255)); 353 mOverScrollForegroundDrawable.setAlpha(mForegroundAlpha); 354 invalidate(); 355 } 356 357 void setPressedOrFocusedIcon(BubbleTextView icon) { 358 // We draw the pressed or focused BubbleTextView's background in CellLayout because it 359 // requires an expanded clip rect (due to the glow's blur radius) 360 BubbleTextView oldIcon = mPressedOrFocusedIcon; 361 mPressedOrFocusedIcon = icon; 362 if (oldIcon != null) { 363 invalidateBubbleTextView(oldIcon); 364 } 365 if (mPressedOrFocusedIcon != null) { 366 invalidateBubbleTextView(mPressedOrFocusedIcon); 367 } 368 } 369 370 void setIsDragOverlapping(boolean isDragOverlapping) { 371 if (mIsDragOverlapping != isDragOverlapping) { 372 mIsDragOverlapping = isDragOverlapping; 373 setUseActiveGlowBackground(mIsDragOverlapping); 374 invalidate(); 375 } 376 } 377 378 void setUseActiveGlowBackground(boolean use) { 379 mUseActiveGlowBackground = use; 380 } 381 382 void disableBackground() { 383 mDrawBackground = false; 384 } 385 386 boolean getIsDragOverlapping() { 387 return mIsDragOverlapping; 388 } 389 390 @Override 391 protected void onDraw(Canvas canvas) { 392 // When we're large, we are either drawn in a "hover" state (ie when dragging an item to 393 // a neighboring page) or with just a normal background (if backgroundAlpha > 0.0f) 394 // When we're small, we are either drawn normally or in the "accepts drops" state (during 395 // a drag). However, we also drag the mini hover background *over* one of those two 396 // backgrounds 397 if (mDrawBackground && mBackgroundAlpha > 0.0f) { 398 Drawable bg; 399 400 if (mUseActiveGlowBackground) { 401 // In the mini case, we draw the active_glow bg *over* the active background 402 bg = mActiveGlowBackground; 403 } else { 404 bg = mNormalBackground; 405 } 406 407 bg.setAlpha((int) (mBackgroundAlpha * mBackgroundAlphaMultiplier * 255)); 408 bg.setBounds(mBackgroundRect); 409 bg.draw(canvas); 410 } 411 412 final Paint paint = mDragOutlinePaint; 413 for (int i = 0; i < mDragOutlines.length; i++) { 414 final float alpha = mDragOutlineAlphas[i]; 415 if (alpha > 0) { 416 final Rect r = mDragOutlines[i]; 417 mTempRect.set(r); 418 Utilities.scaleRectAboutCenter(mTempRect, getChildrenScale()); 419 final Bitmap b = (Bitmap) mDragOutlineAnims[i].getTag(); 420 paint.setAlpha((int)(alpha + .5f)); 421 canvas.drawBitmap(b, null, mTempRect, paint); 422 } 423 } 424 425 // We draw the pressed or focused BubbleTextView's background in CellLayout because it 426 // requires an expanded clip rect (due to the glow's blur radius) 427 if (mPressedOrFocusedIcon != null) { 428 final int padding = mPressedOrFocusedIcon.getPressedOrFocusedBackgroundPadding(); 429 final Bitmap b = mPressedOrFocusedIcon.getPressedOrFocusedBackground(); 430 if (b != null) { 431 int offset = getMeasuredWidth() - getPaddingLeft() - getPaddingRight() - 432 (mCountX * mCellWidth); 433 int left = getPaddingLeft() + (int) Math.ceil(offset / 2f); 434 int top = getPaddingTop(); 435 canvas.drawBitmap(b, 436 mPressedOrFocusedIcon.getLeft() + left - padding, 437 mPressedOrFocusedIcon.getTop() + top - padding, 438 null); 439 } 440 } 441 442 if (DEBUG_VISUALIZE_OCCUPIED) { 443 int[] pt = new int[2]; 444 ColorDrawable cd = new ColorDrawable(Color.RED); 445 cd.setBounds(0, 0, mCellWidth, mCellHeight); 446 for (int i = 0; i < mCountX; i++) { 447 for (int j = 0; j < mCountY; j++) { 448 if (mOccupied[i][j]) { 449 cellToPoint(i, j, pt); 450 canvas.save(); 451 canvas.translate(pt[0], pt[1]); 452 cd.draw(canvas); 453 canvas.restore(); 454 } 455 } 456 } 457 } 458 459 int previewOffset = FolderRingAnimator.sPreviewSize; 460 461 // The folder outer / inner ring image(s) 462 LauncherAppState app = LauncherAppState.getInstance(); 463 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile(); 464 for (int i = 0; i < mFolderOuterRings.size(); i++) { 465 FolderRingAnimator fra = mFolderOuterRings.get(i); 466 467 Drawable d; 468 int width, height; 469 cellToPoint(fra.mCellX, fra.mCellY, mTempLocation); 470 View child = getChildAt(fra.mCellX, fra.mCellY); 471 472 if (child != null) { 473 int centerX = mTempLocation[0] + mCellWidth / 2; 474 int centerY = mTempLocation[1] + previewOffset / 2 + 475 child.getPaddingTop() + grid.folderBackgroundOffset; 476 477 // Draw outer ring, if it exists 478 if (FolderIcon.HAS_OUTER_RING) { 479 d = FolderRingAnimator.sSharedOuterRingDrawable; 480 width = (int) (fra.getOuterRingSize() * getChildrenScale()); 481 height = width; 482 canvas.save(); 483 canvas.translate(centerX - width / 2, centerY - height / 2); 484 d.setBounds(0, 0, width, height); 485 d.draw(canvas); 486 canvas.restore(); 487 } 488 489 // Draw inner ring 490 d = FolderRingAnimator.sSharedInnerRingDrawable; 491 width = (int) (fra.getInnerRingSize() * getChildrenScale()); 492 height = width; 493 canvas.save(); 494 canvas.translate(centerX - width / 2, centerY - width / 2); 495 d.setBounds(0, 0, width, height); 496 d.draw(canvas); 497 canvas.restore(); 498 } 499 } 500 501 if (mFolderLeaveBehindCell[0] >= 0 && mFolderLeaveBehindCell[1] >= 0) { 502 Drawable d = FolderIcon.sSharedFolderLeaveBehind; 503 int width = d.getIntrinsicWidth(); 504 int height = d.getIntrinsicHeight(); 505 506 cellToPoint(mFolderLeaveBehindCell[0], mFolderLeaveBehindCell[1], mTempLocation); 507 View child = getChildAt(mFolderLeaveBehindCell[0], mFolderLeaveBehindCell[1]); 508 if (child != null) { 509 int centerX = mTempLocation[0] + mCellWidth / 2; 510 int centerY = mTempLocation[1] + previewOffset / 2 + 511 child.getPaddingTop() + grid.folderBackgroundOffset; 512 513 canvas.save(); 514 canvas.translate(centerX - width / 2, centerY - width / 2); 515 d.setBounds(0, 0, width, height); 516 d.draw(canvas); 517 canvas.restore(); 518 } 519 } 520 } 521 522 @Override 523 protected void dispatchDraw(Canvas canvas) { 524 super.dispatchDraw(canvas); 525 if (mForegroundAlpha > 0) { 526 mOverScrollForegroundDrawable.setBounds(mForegroundRect); 527 mOverScrollForegroundDrawable.draw(canvas); 528 } 529 } 530 531 public void showFolderAccept(FolderRingAnimator fra) { 532 mFolderOuterRings.add(fra); 533 } 534 535 public void hideFolderAccept(FolderRingAnimator fra) { 536 if (mFolderOuterRings.contains(fra)) { 537 mFolderOuterRings.remove(fra); 538 } 539 invalidate(); 540 } 541 542 public void setFolderLeaveBehindCell(int x, int y) { 543 mFolderLeaveBehindCell[0] = x; 544 mFolderLeaveBehindCell[1] = y; 545 invalidate(); 546 } 547 548 public void clearFolderLeaveBehind() { 549 mFolderLeaveBehindCell[0] = -1; 550 mFolderLeaveBehindCell[1] = -1; 551 invalidate(); 552 } 553 554 @Override 555 public boolean shouldDelayChildPressedState() { 556 return false; 557 } 558 559 public void restoreInstanceState(SparseArray<Parcelable> states) { 560 try { 561 dispatchRestoreInstanceState(states); 562 } catch (IllegalArgumentException ex) { 563 if (LauncherAppState.isDogfoodBuild()) { 564 throw ex; 565 } 566 // Mismatched viewId / viewType preventing restore. Skip restore on production builds. 567 Log.e(TAG, "Ignoring an error while restoring a view instance state", ex); 568 } 569 } 570 571 @Override 572 public void cancelLongPress() { 573 super.cancelLongPress(); 574 575 // Cancel long press for all children 576 final int count = getChildCount(); 577 for (int i = 0; i < count; i++) { 578 final View child = getChildAt(i); 579 child.cancelLongPress(); 580 } 581 } 582 583 public void setOnInterceptTouchListener(View.OnTouchListener listener) { 584 mInterceptTouchListener = listener; 585 } 586 587 int getCountX() { 588 return mCountX; 589 } 590 591 int getCountY() { 592 return mCountY; 593 } 594 595 public void setIsHotseat(boolean isHotseat) { 596 mIsHotseat = isHotseat; 597 mShortcutsAndWidgets.setIsHotseat(isHotseat); 598 } 599 600 public boolean addViewToCellLayout(View child, int index, int childId, LayoutParams params, 601 boolean markCells) { 602 final LayoutParams lp = params; 603 604 // Hotseat icons - remove text 605 if (child instanceof BubbleTextView) { 606 BubbleTextView bubbleChild = (BubbleTextView) child; 607 bubbleChild.setTextVisibility(!mIsHotseat); 608 } 609 610 child.setScaleX(getChildrenScale()); 611 child.setScaleY(getChildrenScale()); 612 613 // Generate an id for each view, this assumes we have at most 256x256 cells 614 // per workspace screen 615 if (lp.cellX >= 0 && lp.cellX <= mCountX - 1 && lp.cellY >= 0 && lp.cellY <= mCountY - 1) { 616 // If the horizontal or vertical span is set to -1, it is taken to 617 // mean that it spans the extent of the CellLayout 618 if (lp.cellHSpan < 0) lp.cellHSpan = mCountX; 619 if (lp.cellVSpan < 0) lp.cellVSpan = mCountY; 620 621 child.setId(childId); 622 623 mShortcutsAndWidgets.addView(child, index, lp); 624 625 if (markCells) markCellsAsOccupiedForView(child); 626 627 return true; 628 } 629 return false; 630 } 631 632 @Override 633 public void removeAllViews() { 634 clearOccupiedCells(); 635 mShortcutsAndWidgets.removeAllViews(); 636 } 637 638 @Override 639 public void removeAllViewsInLayout() { 640 if (mShortcutsAndWidgets.getChildCount() > 0) { 641 clearOccupiedCells(); 642 mShortcutsAndWidgets.removeAllViewsInLayout(); 643 } 644 } 645 646 public void removeViewWithoutMarkingCells(View view) { 647 mShortcutsAndWidgets.removeView(view); 648 } 649 650 @Override 651 public void removeView(View view) { 652 markCellsAsUnoccupiedForView(view); 653 mShortcutsAndWidgets.removeView(view); 654 } 655 656 @Override 657 public void removeViewAt(int index) { 658 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(index)); 659 mShortcutsAndWidgets.removeViewAt(index); 660 } 661 662 @Override 663 public void removeViewInLayout(View view) { 664 markCellsAsUnoccupiedForView(view); 665 mShortcutsAndWidgets.removeViewInLayout(view); 666 } 667 668 @Override 669 public void removeViews(int start, int count) { 670 for (int i = start; i < start + count; i++) { 671 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i)); 672 } 673 mShortcutsAndWidgets.removeViews(start, count); 674 } 675 676 @Override 677 public void removeViewsInLayout(int start, int count) { 678 for (int i = start; i < start + count; i++) { 679 markCellsAsUnoccupiedForView(mShortcutsAndWidgets.getChildAt(i)); 680 } 681 mShortcutsAndWidgets.removeViewsInLayout(start, count); 682 } 683 684 @Override 685 public boolean onInterceptTouchEvent(MotionEvent ev) { 686 // First we clear the tag to ensure that on every touch down we start with a fresh slate, 687 // even in the case where we return early. Not clearing here was causing bugs whereby on 688 // long-press we'd end up picking up an item from a previous drag operation. 689 if (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev)) { 690 return true; 691 } 692 693 return false; 694 } 695 696 /** 697 * Given a point, return the cell that strictly encloses that point 698 * @param x X coordinate of the point 699 * @param y Y coordinate of the point 700 * @param result Array of 2 ints to hold the x and y coordinate of the cell 701 */ 702 void pointToCellExact(int x, int y, int[] result) { 703 final int hStartPadding = getPaddingLeft(); 704 final int vStartPadding = getPaddingTop(); 705 706 result[0] = (x - hStartPadding) / (mCellWidth + mWidthGap); 707 result[1] = (y - vStartPadding) / (mCellHeight + mHeightGap); 708 709 final int xAxis = mCountX; 710 final int yAxis = mCountY; 711 712 if (result[0] < 0) result[0] = 0; 713 if (result[0] >= xAxis) result[0] = xAxis - 1; 714 if (result[1] < 0) result[1] = 0; 715 if (result[1] >= yAxis) result[1] = yAxis - 1; 716 } 717 718 /** 719 * Given a point, return the cell that most closely encloses that point 720 * @param x X coordinate of the point 721 * @param y Y coordinate of the point 722 * @param result Array of 2 ints to hold the x and y coordinate of the cell 723 */ 724 void pointToCellRounded(int x, int y, int[] result) { 725 pointToCellExact(x + (mCellWidth / 2), y + (mCellHeight / 2), result); 726 } 727 728 /** 729 * Given a cell coordinate, return the point that represents the upper left corner of that cell 730 * 731 * @param cellX X coordinate of the cell 732 * @param cellY Y coordinate of the cell 733 * 734 * @param result Array of 2 ints to hold the x and y coordinate of the point 735 */ 736 void cellToPoint(int cellX, int cellY, int[] result) { 737 final int hStartPadding = getPaddingLeft(); 738 final int vStartPadding = getPaddingTop(); 739 740 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap); 741 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap); 742 } 743 744 /** 745 * Given a cell coordinate, return the point that represents the center of the cell 746 * 747 * @param cellX X coordinate of the cell 748 * @param cellY Y coordinate of the cell 749 * 750 * @param result Array of 2 ints to hold the x and y coordinate of the point 751 */ 752 void cellToCenterPoint(int cellX, int cellY, int[] result) { 753 regionToCenterPoint(cellX, cellY, 1, 1, result); 754 } 755 756 /** 757 * Given a cell coordinate and span return the point that represents the center of the regio 758 * 759 * @param cellX X coordinate of the cell 760 * @param cellY Y coordinate of the cell 761 * 762 * @param result Array of 2 ints to hold the x and y coordinate of the point 763 */ 764 void regionToCenterPoint(int cellX, int cellY, int spanX, int spanY, int[] result) { 765 final int hStartPadding = getPaddingLeft(); 766 final int vStartPadding = getPaddingTop(); 767 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap) + 768 (spanX * mCellWidth + (spanX - 1) * mWidthGap) / 2; 769 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap) + 770 (spanY * mCellHeight + (spanY - 1) * mHeightGap) / 2; 771 } 772 773 /** 774 * Given a cell coordinate and span fills out a corresponding pixel rect 775 * 776 * @param cellX X coordinate of the cell 777 * @param cellY Y coordinate of the cell 778 * @param result Rect in which to write the result 779 */ 780 void regionToRect(int cellX, int cellY, int spanX, int spanY, Rect result) { 781 final int hStartPadding = getPaddingLeft(); 782 final int vStartPadding = getPaddingTop(); 783 final int left = hStartPadding + cellX * (mCellWidth + mWidthGap); 784 final int top = vStartPadding + cellY * (mCellHeight + mHeightGap); 785 result.set(left, top, left + (spanX * mCellWidth + (spanX - 1) * mWidthGap), 786 top + (spanY * mCellHeight + (spanY - 1) * mHeightGap)); 787 } 788 789 public float getDistanceFromCell(float x, float y, int[] cell) { 790 cellToCenterPoint(cell[0], cell[1], mTmpPoint); 791 float distance = (float) Math.sqrt( Math.pow(x - mTmpPoint[0], 2) + 792 Math.pow(y - mTmpPoint[1], 2)); 793 return distance; 794 } 795 796 int getCellWidth() { 797 return mCellWidth; 798 } 799 800 int getCellHeight() { 801 return mCellHeight; 802 } 803 804 int getWidthGap() { 805 return mWidthGap; 806 } 807 808 int getHeightGap() { 809 return mHeightGap; 810 } 811 812 Rect getContentRect(Rect r) { 813 if (r == null) { 814 r = new Rect(); 815 } 816 int left = getPaddingLeft(); 817 int top = getPaddingTop(); 818 int right = left + getWidth() - getPaddingLeft() - getPaddingRight(); 819 int bottom = top + getHeight() - getPaddingTop() - getPaddingBottom(); 820 r.set(left, top, right, bottom); 821 return r; 822 } 823 824 /** Return a rect that has the cellWidth/cellHeight (left, top), and 825 * widthGap/heightGap (right, bottom) */ 826 static void getMetrics(Rect metrics, int paddedMeasureWidth, 827 int paddedMeasureHeight, int countX, int countY) { 828 LauncherAppState app = LauncherAppState.getInstance(); 829 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile(); 830 metrics.set(grid.calculateCellWidth(paddedMeasureWidth, countX), 831 grid.calculateCellHeight(paddedMeasureHeight, countY), 0, 0); 832 } 833 834 public void setFixedSize(int width, int height) { 835 mFixedWidth = width; 836 mFixedHeight = height; 837 } 838 839 @Override 840 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 841 LauncherAppState app = LauncherAppState.getInstance(); 842 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile(); 843 844 int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); 845 int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); 846 int widthSize = MeasureSpec.getSize(widthMeasureSpec); 847 int heightSize = MeasureSpec.getSize(heightMeasureSpec); 848 int childWidthSize = widthSize - (getPaddingLeft() + getPaddingRight()); 849 int childHeightSize = heightSize - (getPaddingTop() + getPaddingBottom()); 850 if (mFixedCellWidth < 0 || mFixedCellHeight < 0) { 851 int cw = grid.calculateCellWidth(childWidthSize, mCountX); 852 int ch = grid.calculateCellHeight(childHeightSize, mCountY); 853 if (cw != mCellWidth || ch != mCellHeight) { 854 mCellWidth = cw; 855 mCellHeight = ch; 856 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, 857 mHeightGap, mCountX, mCountY); 858 } 859 } 860 861 int newWidth = childWidthSize; 862 int newHeight = childHeightSize; 863 if (mFixedWidth > 0 && mFixedHeight > 0) { 864 newWidth = mFixedWidth; 865 newHeight = mFixedHeight; 866 } else if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) { 867 throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions"); 868 } 869 870 int numWidthGaps = mCountX - 1; 871 int numHeightGaps = mCountY - 1; 872 873 if (mOriginalWidthGap < 0 || mOriginalHeightGap < 0) { 874 int hSpace = childWidthSize; 875 int vSpace = childHeightSize; 876 int hFreeSpace = hSpace - (mCountX * mCellWidth); 877 int vFreeSpace = vSpace - (mCountY * mCellHeight); 878 mWidthGap = Math.min(mMaxGap, numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0); 879 mHeightGap = Math.min(mMaxGap,numHeightGaps > 0 ? (vFreeSpace / numHeightGaps) : 0); 880 mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, 881 mHeightGap, mCountX, mCountY); 882 } else { 883 mWidthGap = mOriginalWidthGap; 884 mHeightGap = mOriginalHeightGap; 885 } 886 int count = getChildCount(); 887 int maxWidth = 0; 888 int maxHeight = 0; 889 for (int i = 0; i < count; i++) { 890 View child = getChildAt(i); 891 int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(newWidth, 892 MeasureSpec.EXACTLY); 893 int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(newHeight, 894 MeasureSpec.EXACTLY); 895 child.measure(childWidthMeasureSpec, childheightMeasureSpec); 896 maxWidth = Math.max(maxWidth, child.getMeasuredWidth()); 897 maxHeight = Math.max(maxHeight, child.getMeasuredHeight()); 898 } 899 if (mFixedWidth > 0 && mFixedHeight > 0) { 900 setMeasuredDimension(maxWidth, maxHeight); 901 } else { 902 setMeasuredDimension(widthSize, heightSize); 903 } 904 } 905 906 @Override 907 protected void onLayout(boolean changed, int l, int t, int r, int b) { 908 int offset = getMeasuredWidth() - getPaddingLeft() - getPaddingRight() - 909 (mCountX * mCellWidth); 910 int left = getPaddingLeft() + (int) Math.ceil(offset / 2f); 911 int top = getPaddingTop(); 912 int count = getChildCount(); 913 for (int i = 0; i < count; i++) { 914 View child = getChildAt(i); 915 child.layout(left, top, 916 left + r - l, 917 top + b - t); 918 } 919 } 920 921 @Override 922 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 923 super.onSizeChanged(w, h, oldw, oldh); 924 925 // Expand the background drawing bounds by the padding baked into the background drawable 926 Rect padding = new Rect(); 927 mNormalBackground.getPadding(padding); 928 mBackgroundRect.set(-padding.left, -padding.top, w + padding.right, h + padding.bottom); 929 930 mForegroundRect.set(mForegroundPadding, mForegroundPadding, 931 w - mForegroundPadding, h - mForegroundPadding); 932 } 933 934 @Override 935 protected void setChildrenDrawingCacheEnabled(boolean enabled) { 936 mShortcutsAndWidgets.setChildrenDrawingCacheEnabled(enabled); 937 } 938 939 @Override 940 protected void setChildrenDrawnWithCacheEnabled(boolean enabled) { 941 mShortcutsAndWidgets.setChildrenDrawnWithCacheEnabled(enabled); 942 } 943 944 public float getBackgroundAlpha() { 945 return mBackgroundAlpha; 946 } 947 948 public void setBackgroundAlphaMultiplier(float multiplier) { 949 if (mBackgroundAlphaMultiplier != multiplier) { 950 mBackgroundAlphaMultiplier = multiplier; 951 invalidate(); 952 } 953 } 954 955 public float getBackgroundAlphaMultiplier() { 956 return mBackgroundAlphaMultiplier; 957 } 958 959 public void setBackgroundAlpha(float alpha) { 960 if (mBackgroundAlpha != alpha) { 961 mBackgroundAlpha = alpha; 962 invalidate(); 963 } 964 } 965 966 public void setShortcutAndWidgetAlpha(float alpha) { 967 final int childCount = getChildCount(); 968 for (int i = 0; i < childCount; i++) { 969 getChildAt(i).setAlpha(alpha); 970 } 971 } 972 973 public ShortcutAndWidgetContainer getShortcutsAndWidgets() { 974 if (getChildCount() > 0) { 975 return (ShortcutAndWidgetContainer) getChildAt(0); 976 } 977 return null; 978 } 979 980 public View getChildAt(int x, int y) { 981 return mShortcutsAndWidgets.getChildAt(x, y); 982 } 983 984 public boolean animateChildToPosition(final View child, int cellX, int cellY, int duration, 985 int delay, boolean permanent, boolean adjustOccupied) { 986 ShortcutAndWidgetContainer clc = getShortcutsAndWidgets(); 987 boolean[][] occupied = mOccupied; 988 if (!permanent) { 989 occupied = mTmpOccupied; 990 } 991 992 if (clc.indexOfChild(child) != -1) { 993 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 994 final ItemInfo info = (ItemInfo) child.getTag(); 995 996 // We cancel any existing animations 997 if (mReorderAnimators.containsKey(lp)) { 998 mReorderAnimators.get(lp).cancel(); 999 mReorderAnimators.remove(lp); 1000 } 1001 1002 final int oldX = lp.x; 1003 final int oldY = lp.y; 1004 if (adjustOccupied) { 1005 occupied[lp.cellX][lp.cellY] = false; 1006 occupied[cellX][cellY] = true; 1007 } 1008 lp.isLockedToGrid = true; 1009 if (permanent) { 1010 lp.cellX = info.cellX = cellX; 1011 lp.cellY = info.cellY = cellY; 1012 } else { 1013 lp.tmpCellX = cellX; 1014 lp.tmpCellY = cellY; 1015 } 1016 clc.setupLp(lp); 1017 lp.isLockedToGrid = false; 1018 final int newX = lp.x; 1019 final int newY = lp.y; 1020 1021 lp.x = oldX; 1022 lp.y = oldY; 1023 1024 // Exit early if we're not actually moving the view 1025 if (oldX == newX && oldY == newY) { 1026 lp.isLockedToGrid = true; 1027 return true; 1028 } 1029 1030 ValueAnimator va = LauncherAnimUtils.ofFloat(child, 0f, 1f); 1031 va.setDuration(duration); 1032 mReorderAnimators.put(lp, va); 1033 1034 va.addUpdateListener(new AnimatorUpdateListener() { 1035 @Override 1036 public void onAnimationUpdate(ValueAnimator animation) { 1037 float r = ((Float) animation.getAnimatedValue()).floatValue(); 1038 lp.x = (int) ((1 - r) * oldX + r * newX); 1039 lp.y = (int) ((1 - r) * oldY + r * newY); 1040 child.requestLayout(); 1041 } 1042 }); 1043 va.addListener(new AnimatorListenerAdapter() { 1044 boolean cancelled = false; 1045 public void onAnimationEnd(Animator animation) { 1046 // If the animation was cancelled, it means that another animation 1047 // has interrupted this one, and we don't want to lock the item into 1048 // place just yet. 1049 if (!cancelled) { 1050 lp.isLockedToGrid = true; 1051 child.requestLayout(); 1052 } 1053 if (mReorderAnimators.containsKey(lp)) { 1054 mReorderAnimators.remove(lp); 1055 } 1056 } 1057 public void onAnimationCancel(Animator animation) { 1058 cancelled = true; 1059 } 1060 }); 1061 va.setStartDelay(delay); 1062 va.start(); 1063 return true; 1064 } 1065 return false; 1066 } 1067 1068 /** 1069 * Estimate where the top left cell of the dragged item will land if it is dropped. 1070 * 1071 * @param originX The X value of the top left corner of the item 1072 * @param originY The Y value of the top left corner of the item 1073 * @param spanX The number of horizontal cells that the item spans 1074 * @param spanY The number of vertical cells that the item spans 1075 * @param result The estimated drop cell X and Y. 1076 */ 1077 void estimateDropCell(int originX, int originY, int spanX, int spanY, int[] result) { 1078 final int countX = mCountX; 1079 final int countY = mCountY; 1080 1081 // pointToCellRounded takes the top left of a cell but will pad that with 1082 // cellWidth/2 and cellHeight/2 when finding the matching cell 1083 pointToCellRounded(originX, originY, result); 1084 1085 // If the item isn't fully on this screen, snap to the edges 1086 int rightOverhang = result[0] + spanX - countX; 1087 if (rightOverhang > 0) { 1088 result[0] -= rightOverhang; // Snap to right 1089 } 1090 result[0] = Math.max(0, result[0]); // Snap to left 1091 int bottomOverhang = result[1] + spanY - countY; 1092 if (bottomOverhang > 0) { 1093 result[1] -= bottomOverhang; // Snap to bottom 1094 } 1095 result[1] = Math.max(0, result[1]); // Snap to top 1096 } 1097 1098 void visualizeDropLocation(View v, Bitmap dragOutline, int originX, int originY, int cellX, 1099 int cellY, int spanX, int spanY, boolean resize, Point dragOffset, Rect dragRegion) { 1100 final int oldDragCellX = mDragCell[0]; 1101 final int oldDragCellY = mDragCell[1]; 1102 1103 if (dragOutline == null && v == null) { 1104 return; 1105 } 1106 1107 if (cellX != oldDragCellX || cellY != oldDragCellY) { 1108 mDragCell[0] = cellX; 1109 mDragCell[1] = cellY; 1110 // Find the top left corner of the rect the object will occupy 1111 final int[] topLeft = mTmpPoint; 1112 cellToPoint(cellX, cellY, topLeft); 1113 1114 int left = topLeft[0]; 1115 int top = topLeft[1]; 1116 1117 if (v != null && dragOffset == null) { 1118 // When drawing the drag outline, it did not account for margin offsets 1119 // added by the view's parent. 1120 MarginLayoutParams lp = (MarginLayoutParams) v.getLayoutParams(); 1121 left += lp.leftMargin; 1122 top += lp.topMargin; 1123 1124 // Offsets due to the size difference between the View and the dragOutline. 1125 // There is a size difference to account for the outer blur, which may lie 1126 // outside the bounds of the view. 1127 top += (v.getHeight() - dragOutline.getHeight()) / 2; 1128 // We center about the x axis 1129 left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap) 1130 - dragOutline.getWidth()) / 2; 1131 } else { 1132 if (dragOffset != null && dragRegion != null) { 1133 // Center the drag region *horizontally* in the cell and apply a drag 1134 // outline offset 1135 left += dragOffset.x + ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap) 1136 - dragRegion.width()) / 2; 1137 int cHeight = getShortcutsAndWidgets().getCellContentHeight(); 1138 int cellPaddingY = (int) Math.max(0, ((mCellHeight - cHeight) / 2f)); 1139 top += dragOffset.y + cellPaddingY; 1140 } else { 1141 // Center the drag outline in the cell 1142 left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap) 1143 - dragOutline.getWidth()) / 2; 1144 top += ((mCellHeight * spanY) + ((spanY - 1) * mHeightGap) 1145 - dragOutline.getHeight()) / 2; 1146 } 1147 } 1148 final int oldIndex = mDragOutlineCurrent; 1149 mDragOutlineAnims[oldIndex].animateOut(); 1150 mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length; 1151 Rect r = mDragOutlines[mDragOutlineCurrent]; 1152 r.set(left, top, left + dragOutline.getWidth(), top + dragOutline.getHeight()); 1153 if (resize) { 1154 cellToRect(cellX, cellY, spanX, spanY, r); 1155 } 1156 1157 mDragOutlineAnims[mDragOutlineCurrent].setTag(dragOutline); 1158 mDragOutlineAnims[mDragOutlineCurrent].animateIn(); 1159 } 1160 } 1161 1162 public void clearDragOutlines() { 1163 final int oldIndex = mDragOutlineCurrent; 1164 mDragOutlineAnims[oldIndex].animateOut(); 1165 mDragCell[0] = mDragCell[1] = -1; 1166 } 1167 1168 /** 1169 * Find a vacant area that will fit the given bounds nearest the requested 1170 * cell location. Uses Euclidean distance to score multiple vacant areas. 1171 * 1172 * @param pixelX The X location at which you want to search for a vacant area. 1173 * @param pixelY The Y location at which you want to search for a vacant area. 1174 * @param spanX Horizontal span of the object. 1175 * @param spanY Vertical span of the object. 1176 * @param result Array in which to place the result, or null (in which case a new array will 1177 * be allocated) 1178 * @return The X, Y cell of a vacant area that can contain this object, 1179 * nearest the requested location. 1180 */ 1181 int[] findNearestVacantArea(int pixelX, int pixelY, int spanX, int spanY, 1182 int[] result) { 1183 return findNearestVacantArea(pixelX, pixelY, spanX, spanY, null, result); 1184 } 1185 1186 /** 1187 * Find a vacant area that will fit the given bounds nearest the requested 1188 * cell location. Uses Euclidean distance to score multiple vacant areas. 1189 * 1190 * @param pixelX The X location at which you want to search for a vacant area. 1191 * @param pixelY The Y location at which you want to search for a vacant area. 1192 * @param minSpanX The minimum horizontal span required 1193 * @param minSpanY The minimum vertical span required 1194 * @param spanX Horizontal span of the object. 1195 * @param spanY Vertical span of the object. 1196 * @param result Array in which to place the result, or null (in which case a new array will 1197 * be allocated) 1198 * @return The X, Y cell of a vacant area that can contain this object, 1199 * nearest the requested location. 1200 */ 1201 int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, 1202 int spanY, int[] result, int[] resultSpan) { 1203 return findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, null, 1204 result, resultSpan); 1205 } 1206 1207 /** 1208 * Find a vacant area that will fit the given bounds nearest the requested 1209 * cell location. Uses Euclidean distance to score multiple vacant areas. 1210 * 1211 * @param pixelX The X location at which you want to search for a vacant area. 1212 * @param pixelY The Y location at which you want to search for a vacant area. 1213 * @param spanX Horizontal span of the object. 1214 * @param spanY Vertical span of the object. 1215 * @param ignoreOccupied If true, the result can be an occupied cell 1216 * @param result Array in which to place the result, or null (in which case a new array will 1217 * be allocated) 1218 * @return The X, Y cell of a vacant area that can contain this object, 1219 * nearest the requested location. 1220 */ 1221 int[] findNearestArea(int pixelX, int pixelY, int spanX, int spanY, View ignoreView, 1222 boolean ignoreOccupied, int[] result) { 1223 return findNearestArea(pixelX, pixelY, spanX, spanY, 1224 spanX, spanY, ignoreView, ignoreOccupied, result, null, mOccupied); 1225 } 1226 1227 private final Stack<Rect> mTempRectStack = new Stack<Rect>(); 1228 private void lazyInitTempRectStack() { 1229 if (mTempRectStack.isEmpty()) { 1230 for (int i = 0; i < mCountX * mCountY; i++) { 1231 mTempRectStack.push(new Rect()); 1232 } 1233 } 1234 } 1235 1236 private void recycleTempRects(Stack<Rect> used) { 1237 while (!used.isEmpty()) { 1238 mTempRectStack.push(used.pop()); 1239 } 1240 } 1241 1242 /** 1243 * Find a vacant area that will fit the given bounds nearest the requested 1244 * cell location. Uses Euclidean distance to score multiple vacant areas. 1245 * 1246 * @param pixelX The X location at which you want to search for a vacant area. 1247 * @param pixelY The Y location at which you want to search for a vacant area. 1248 * @param minSpanX The minimum horizontal span required 1249 * @param minSpanY The minimum vertical span required 1250 * @param spanX Horizontal span of the object. 1251 * @param spanY Vertical span of the object. 1252 * @param ignoreOccupied If true, the result can be an occupied cell 1253 * @param result Array in which to place the result, or null (in which case a new array will 1254 * be allocated) 1255 * @return The X, Y cell of a vacant area that can contain this object, 1256 * nearest the requested location. 1257 */ 1258 int[] findNearestArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY, 1259 View ignoreView, boolean ignoreOccupied, int[] result, int[] resultSpan, 1260 boolean[][] occupied) { 1261 lazyInitTempRectStack(); 1262 // mark space take by ignoreView as available (method checks if ignoreView is null) 1263 markCellsAsUnoccupiedForView(ignoreView, occupied); 1264 1265 // For items with a spanX / spanY > 1, the passed in point (pixelX, pixelY) corresponds 1266 // to the center of the item, but we are searching based on the top-left cell, so 1267 // we translate the point over to correspond to the top-left. 1268 pixelX -= (mCellWidth + mWidthGap) * (spanX - 1) / 2f; 1269 pixelY -= (mCellHeight + mHeightGap) * (spanY - 1) / 2f; 1270 1271 // Keep track of best-scoring drop area 1272 final int[] bestXY = result != null ? result : new int[2]; 1273 double bestDistance = Double.MAX_VALUE; 1274 final Rect bestRect = new Rect(-1, -1, -1, -1); 1275 final Stack<Rect> validRegions = new Stack<Rect>(); 1276 1277 final int countX = mCountX; 1278 final int countY = mCountY; 1279 1280 if (minSpanX <= 0 || minSpanY <= 0 || spanX <= 0 || spanY <= 0 || 1281 spanX < minSpanX || spanY < minSpanY) { 1282 return bestXY; 1283 } 1284 1285 for (int y = 0; y < countY - (minSpanY - 1); y++) { 1286 inner: 1287 for (int x = 0; x < countX - (minSpanX - 1); x++) { 1288 int ySize = -1; 1289 int xSize = -1; 1290 if (ignoreOccupied) { 1291 // First, let's see if this thing fits anywhere 1292 for (int i = 0; i < minSpanX; i++) { 1293 for (int j = 0; j < minSpanY; j++) { 1294 if (occupied[x + i][y + j]) { 1295 continue inner; 1296 } 1297 } 1298 } 1299 xSize = minSpanX; 1300 ySize = minSpanY; 1301 1302 // We know that the item will fit at _some_ acceptable size, now let's see 1303 // how big we can make it. We'll alternate between incrementing x and y spans 1304 // until we hit a limit. 1305 boolean incX = true; 1306 boolean hitMaxX = xSize >= spanX; 1307 boolean hitMaxY = ySize >= spanY; 1308 while (!(hitMaxX && hitMaxY)) { 1309 if (incX && !hitMaxX) { 1310 for (int j = 0; j < ySize; j++) { 1311 if (x + xSize > countX -1 || occupied[x + xSize][y + j]) { 1312 // We can't move out horizontally 1313 hitMaxX = true; 1314 } 1315 } 1316 if (!hitMaxX) { 1317 xSize++; 1318 } 1319 } else if (!hitMaxY) { 1320 for (int i = 0; i < xSize; i++) { 1321 if (y + ySize > countY - 1 || occupied[x + i][y + ySize]) { 1322 // We can't move out vertically 1323 hitMaxY = true; 1324 } 1325 } 1326 if (!hitMaxY) { 1327 ySize++; 1328 } 1329 } 1330 hitMaxX |= xSize >= spanX; 1331 hitMaxY |= ySize >= spanY; 1332 incX = !incX; 1333 } 1334 incX = true; 1335 hitMaxX = xSize >= spanX; 1336 hitMaxY = ySize >= spanY; 1337 } 1338 final int[] cellXY = mTmpXY; 1339 cellToCenterPoint(x, y, cellXY); 1340 1341 // We verify that the current rect is not a sub-rect of any of our previous 1342 // candidates. In this case, the current rect is disqualified in favour of the 1343 // containing rect. 1344 Rect currentRect = mTempRectStack.pop(); 1345 currentRect.set(x, y, x + xSize, y + ySize); 1346 boolean contained = false; 1347 for (Rect r : validRegions) { 1348 if (r.contains(currentRect)) { 1349 contained = true; 1350 break; 1351 } 1352 } 1353 validRegions.push(currentRect); 1354 double distance = Math.sqrt(Math.pow(cellXY[0] - pixelX, 2) 1355 + Math.pow(cellXY[1] - pixelY, 2)); 1356 1357 if ((distance <= bestDistance && !contained) || 1358 currentRect.contains(bestRect)) { 1359 bestDistance = distance; 1360 bestXY[0] = x; 1361 bestXY[1] = y; 1362 if (resultSpan != null) { 1363 resultSpan[0] = xSize; 1364 resultSpan[1] = ySize; 1365 } 1366 bestRect.set(currentRect); 1367 } 1368 } 1369 } 1370 // re-mark space taken by ignoreView as occupied 1371 markCellsAsOccupiedForView(ignoreView, occupied); 1372 1373 // Return -1, -1 if no suitable location found 1374 if (bestDistance == Double.MAX_VALUE) { 1375 bestXY[0] = -1; 1376 bestXY[1] = -1; 1377 } 1378 recycleTempRects(validRegions); 1379 return bestXY; 1380 } 1381 1382 /** 1383 * Find a vacant area that will fit the given bounds nearest the requested 1384 * cell location, and will also weigh in a suggested direction vector of the 1385 * desired location. This method computers distance based on unit grid distances, 1386 * not pixel distances. 1387 * 1388 * @param cellX The X cell nearest to which you want to search for a vacant area. 1389 * @param cellY The Y cell nearest which you want to search for a vacant area. 1390 * @param spanX Horizontal span of the object. 1391 * @param spanY Vertical span of the object. 1392 * @param direction The favored direction in which the views should move from x, y 1393 * @param exactDirectionOnly If this parameter is true, then only solutions where the direction 1394 * matches exactly. Otherwise we find the best matching direction. 1395 * @param occoupied The array which represents which cells in the CellLayout are occupied 1396 * @param blockOccupied The array which represents which cells in the specified block (cellX, 1397 * cellY, spanX, spanY) are occupied. This is used when try to move a group of views. 1398 * @param result Array in which to place the result, or null (in which case a new array will 1399 * be allocated) 1400 * @return The X, Y cell of a vacant area that can contain this object, 1401 * nearest the requested location. 1402 */ 1403 private int[] findNearestArea(int cellX, int cellY, int spanX, int spanY, int[] direction, 1404 boolean[][] occupied, boolean blockOccupied[][], int[] result) { 1405 // Keep track of best-scoring drop area 1406 final int[] bestXY = result != null ? result : new int[2]; 1407 float bestDistance = Float.MAX_VALUE; 1408 int bestDirectionScore = Integer.MIN_VALUE; 1409 1410 final int countX = mCountX; 1411 final int countY = mCountY; 1412 1413 for (int y = 0; y < countY - (spanY - 1); y++) { 1414 inner: 1415 for (int x = 0; x < countX - (spanX - 1); x++) { 1416 // First, let's see if this thing fits anywhere 1417 for (int i = 0; i < spanX; i++) { 1418 for (int j = 0; j < spanY; j++) { 1419 if (occupied[x + i][y + j] && (blockOccupied == null || blockOccupied[i][j])) { 1420 continue inner; 1421 } 1422 } 1423 } 1424 1425 float distance = (float) 1426 Math.sqrt((x - cellX) * (x - cellX) + (y - cellY) * (y - cellY)); 1427 int[] curDirection = mTmpPoint; 1428 computeDirectionVector(x - cellX, y - cellY, curDirection); 1429 // The direction score is just the dot product of the two candidate direction 1430 // and that passed in. 1431 int curDirectionScore = direction[0] * curDirection[0] + 1432 direction[1] * curDirection[1]; 1433 boolean exactDirectionOnly = false; 1434 boolean directionMatches = direction[0] == curDirection[0] && 1435 direction[0] == curDirection[0]; 1436 if ((directionMatches || !exactDirectionOnly) && 1437 Float.compare(distance, bestDistance) < 0 || (Float.compare(distance, 1438 bestDistance) == 0 && curDirectionScore > bestDirectionScore)) { 1439 bestDistance = distance; 1440 bestDirectionScore = curDirectionScore; 1441 bestXY[0] = x; 1442 bestXY[1] = y; 1443 } 1444 } 1445 } 1446 1447 // Return -1, -1 if no suitable location found 1448 if (bestDistance == Float.MAX_VALUE) { 1449 bestXY[0] = -1; 1450 bestXY[1] = -1; 1451 } 1452 return bestXY; 1453 } 1454 1455 private boolean addViewToTempLocation(View v, Rect rectOccupiedByPotentialDrop, 1456 int[] direction, ItemConfiguration currentState) { 1457 CellAndSpan c = currentState.map.get(v); 1458 boolean success = false; 1459 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false); 1460 markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true); 1461 1462 findNearestArea(c.x, c.y, c.spanX, c.spanY, direction, mTmpOccupied, null, mTempLocation); 1463 1464 if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) { 1465 c.x = mTempLocation[0]; 1466 c.y = mTempLocation[1]; 1467 success = true; 1468 } 1469 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true); 1470 return success; 1471 } 1472 1473 /** 1474 * This helper class defines a cluster of views. It helps with defining complex edges 1475 * of the cluster and determining how those edges interact with other views. The edges 1476 * essentially define a fine-grained boundary around the cluster of views -- like a more 1477 * precise version of a bounding box. 1478 */ 1479 private class ViewCluster { 1480 final static int LEFT = 0; 1481 final static int TOP = 1; 1482 final static int RIGHT = 2; 1483 final static int BOTTOM = 3; 1484 1485 ArrayList<View> views; 1486 ItemConfiguration config; 1487 Rect boundingRect = new Rect(); 1488 1489 int[] leftEdge = new int[mCountY]; 1490 int[] rightEdge = new int[mCountY]; 1491 int[] topEdge = new int[mCountX]; 1492 int[] bottomEdge = new int[mCountX]; 1493 boolean leftEdgeDirty, rightEdgeDirty, topEdgeDirty, bottomEdgeDirty, boundingRectDirty; 1494 1495 @SuppressWarnings("unchecked") 1496 public ViewCluster(ArrayList<View> views, ItemConfiguration config) { 1497 this.views = (ArrayList<View>) views.clone(); 1498 this.config = config; 1499 resetEdges(); 1500 } 1501 1502 void resetEdges() { 1503 for (int i = 0; i < mCountX; i++) { 1504 topEdge[i] = -1; 1505 bottomEdge[i] = -1; 1506 } 1507 for (int i = 0; i < mCountY; i++) { 1508 leftEdge[i] = -1; 1509 rightEdge[i] = -1; 1510 } 1511 leftEdgeDirty = true; 1512 rightEdgeDirty = true; 1513 bottomEdgeDirty = true; 1514 topEdgeDirty = true; 1515 boundingRectDirty = true; 1516 } 1517 1518 void computeEdge(int which, int[] edge) { 1519 int count = views.size(); 1520 for (int i = 0; i < count; i++) { 1521 CellAndSpan cs = config.map.get(views.get(i)); 1522 switch (which) { 1523 case LEFT: 1524 int left = cs.x; 1525 for (int j = cs.y; j < cs.y + cs.spanY; j++) { 1526 if (left < edge[j] || edge[j] < 0) { 1527 edge[j] = left; 1528 } 1529 } 1530 break; 1531 case RIGHT: 1532 int right = cs.x + cs.spanX; 1533 for (int j = cs.y; j < cs.y + cs.spanY; j++) { 1534 if (right > edge[j]) { 1535 edge[j] = right; 1536 } 1537 } 1538 break; 1539 case TOP: 1540 int top = cs.y; 1541 for (int j = cs.x; j < cs.x + cs.spanX; j++) { 1542 if (top < edge[j] || edge[j] < 0) { 1543 edge[j] = top; 1544 } 1545 } 1546 break; 1547 case BOTTOM: 1548 int bottom = cs.y + cs.spanY; 1549 for (int j = cs.x; j < cs.x + cs.spanX; j++) { 1550 if (bottom > edge[j]) { 1551 edge[j] = bottom; 1552 } 1553 } 1554 break; 1555 } 1556 } 1557 } 1558 1559 boolean isViewTouchingEdge(View v, int whichEdge) { 1560 CellAndSpan cs = config.map.get(v); 1561 1562 int[] edge = getEdge(whichEdge); 1563 1564 switch (whichEdge) { 1565 case LEFT: 1566 for (int i = cs.y; i < cs.y + cs.spanY; i++) { 1567 if (edge[i] == cs.x + cs.spanX) { 1568 return true; 1569 } 1570 } 1571 break; 1572 case RIGHT: 1573 for (int i = cs.y; i < cs.y + cs.spanY; i++) { 1574 if (edge[i] == cs.x) { 1575 return true; 1576 } 1577 } 1578 break; 1579 case TOP: 1580 for (int i = cs.x; i < cs.x + cs.spanX; i++) { 1581 if (edge[i] == cs.y + cs.spanY) { 1582 return true; 1583 } 1584 } 1585 break; 1586 case BOTTOM: 1587 for (int i = cs.x; i < cs.x + cs.spanX; i++) { 1588 if (edge[i] == cs.y) { 1589 return true; 1590 } 1591 } 1592 break; 1593 } 1594 return false; 1595 } 1596 1597 void shift(int whichEdge, int delta) { 1598 for (View v: views) { 1599 CellAndSpan c = config.map.get(v); 1600 switch (whichEdge) { 1601 case LEFT: 1602 c.x -= delta; 1603 break; 1604 case RIGHT: 1605 c.x += delta; 1606 break; 1607 case TOP: 1608 c.y -= delta; 1609 break; 1610 case BOTTOM: 1611 default: 1612 c.y += delta; 1613 break; 1614 } 1615 } 1616 resetEdges(); 1617 } 1618 1619 public void addView(View v) { 1620 views.add(v); 1621 resetEdges(); 1622 } 1623 1624 public Rect getBoundingRect() { 1625 if (boundingRectDirty) { 1626 boolean first = true; 1627 for (View v: views) { 1628 CellAndSpan c = config.map.get(v); 1629 if (first) { 1630 boundingRect.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY); 1631 first = false; 1632 } else { 1633 boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY); 1634 } 1635 } 1636 } 1637 return boundingRect; 1638 } 1639 1640 public int[] getEdge(int which) { 1641 switch (which) { 1642 case LEFT: 1643 return getLeftEdge(); 1644 case RIGHT: 1645 return getRightEdge(); 1646 case TOP: 1647 return getTopEdge(); 1648 case BOTTOM: 1649 default: 1650 return getBottomEdge(); 1651 } 1652 } 1653 1654 public int[] getLeftEdge() { 1655 if (leftEdgeDirty) { 1656 computeEdge(LEFT, leftEdge); 1657 } 1658 return leftEdge; 1659 } 1660 1661 public int[] getRightEdge() { 1662 if (rightEdgeDirty) { 1663 computeEdge(RIGHT, rightEdge); 1664 } 1665 return rightEdge; 1666 } 1667 1668 public int[] getTopEdge() { 1669 if (topEdgeDirty) { 1670 computeEdge(TOP, topEdge); 1671 } 1672 return topEdge; 1673 } 1674 1675 public int[] getBottomEdge() { 1676 if (bottomEdgeDirty) { 1677 computeEdge(BOTTOM, bottomEdge); 1678 } 1679 return bottomEdge; 1680 } 1681 1682 PositionComparator comparator = new PositionComparator(); 1683 class PositionComparator implements Comparator<View> { 1684 int whichEdge = 0; 1685 public int compare(View left, View right) { 1686 CellAndSpan l = config.map.get(left); 1687 CellAndSpan r = config.map.get(right); 1688 switch (whichEdge) { 1689 case LEFT: 1690 return (r.x + r.spanX) - (l.x + l.spanX); 1691 case RIGHT: 1692 return l.x - r.x; 1693 case TOP: 1694 return (r.y + r.spanY) - (l.y + l.spanY); 1695 case BOTTOM: 1696 default: 1697 return l.y - r.y; 1698 } 1699 } 1700 } 1701 1702 public void sortConfigurationForEdgePush(int edge) { 1703 comparator.whichEdge = edge; 1704 Collections.sort(config.sortedViews, comparator); 1705 } 1706 } 1707 1708 private boolean pushViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop, 1709 int[] direction, View dragView, ItemConfiguration currentState) { 1710 1711 ViewCluster cluster = new ViewCluster(views, currentState); 1712 Rect clusterRect = cluster.getBoundingRect(); 1713 int whichEdge; 1714 int pushDistance; 1715 boolean fail = false; 1716 1717 // Determine the edge of the cluster that will be leading the push and how far 1718 // the cluster must be shifted. 1719 if (direction[0] < 0) { 1720 whichEdge = ViewCluster.LEFT; 1721 pushDistance = clusterRect.right - rectOccupiedByPotentialDrop.left; 1722 } else if (direction[0] > 0) { 1723 whichEdge = ViewCluster.RIGHT; 1724 pushDistance = rectOccupiedByPotentialDrop.right - clusterRect.left; 1725 } else if (direction[1] < 0) { 1726 whichEdge = ViewCluster.TOP; 1727 pushDistance = clusterRect.bottom - rectOccupiedByPotentialDrop.top; 1728 } else { 1729 whichEdge = ViewCluster.BOTTOM; 1730 pushDistance = rectOccupiedByPotentialDrop.bottom - clusterRect.top; 1731 } 1732 1733 // Break early for invalid push distance. 1734 if (pushDistance <= 0) { 1735 return false; 1736 } 1737 1738 // Mark the occupied state as false for the group of views we want to move. 1739 for (View v: views) { 1740 CellAndSpan c = currentState.map.get(v); 1741 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false); 1742 } 1743 1744 // We save the current configuration -- if we fail to find a solution we will revert 1745 // to the initial state. The process of finding a solution modifies the configuration 1746 // in place, hence the need for revert in the failure case. 1747 currentState.save(); 1748 1749 // The pushing algorithm is simplified by considering the views in the order in which 1750 // they would be pushed by the cluster. For example, if the cluster is leading with its 1751 // left edge, we consider sort the views by their right edge, from right to left. 1752 cluster.sortConfigurationForEdgePush(whichEdge); 1753 1754 while (pushDistance > 0 && !fail) { 1755 for (View v: currentState.sortedViews) { 1756 // For each view that isn't in the cluster, we see if the leading edge of the 1757 // cluster is contacting the edge of that view. If so, we add that view to the 1758 // cluster. 1759 if (!cluster.views.contains(v) && v != dragView) { 1760 if (cluster.isViewTouchingEdge(v, whichEdge)) { 1761 LayoutParams lp = (LayoutParams) v.getLayoutParams(); 1762 if (!lp.canReorder) { 1763 // The push solution includes the all apps button, this is not viable. 1764 fail = true; 1765 break; 1766 } 1767 cluster.addView(v); 1768 CellAndSpan c = currentState.map.get(v); 1769 1770 // Adding view to cluster, mark it as not occupied. 1771 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false); 1772 } 1773 } 1774 } 1775 pushDistance--; 1776 1777 // The cluster has been completed, now we move the whole thing over in the appropriate 1778 // direction. 1779 cluster.shift(whichEdge, 1); 1780 } 1781 1782 boolean foundSolution = false; 1783 clusterRect = cluster.getBoundingRect(); 1784 1785 // Due to the nature of the algorithm, the only check required to verify a valid solution 1786 // is to ensure that completed shifted cluster lies completely within the cell layout. 1787 if (!fail && clusterRect.left >= 0 && clusterRect.right <= mCountX && clusterRect.top >= 0 && 1788 clusterRect.bottom <= mCountY) { 1789 foundSolution = true; 1790 } else { 1791 currentState.restore(); 1792 } 1793 1794 // In either case, we set the occupied array as marked for the location of the views 1795 for (View v: cluster.views) { 1796 CellAndSpan c = currentState.map.get(v); 1797 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true); 1798 } 1799 1800 return foundSolution; 1801 } 1802 1803 private boolean addViewsToTempLocation(ArrayList<View> views, Rect rectOccupiedByPotentialDrop, 1804 int[] direction, View dragView, ItemConfiguration currentState) { 1805 if (views.size() == 0) return true; 1806 1807 boolean success = false; 1808 Rect boundingRect = null; 1809 // We construct a rect which represents the entire group of views passed in 1810 for (View v: views) { 1811 CellAndSpan c = currentState.map.get(v); 1812 if (boundingRect == null) { 1813 boundingRect = new Rect(c.x, c.y, c.x + c.spanX, c.y + c.spanY); 1814 } else { 1815 boundingRect.union(c.x, c.y, c.x + c.spanX, c.y + c.spanY); 1816 } 1817 } 1818 1819 // Mark the occupied state as false for the group of views we want to move. 1820 for (View v: views) { 1821 CellAndSpan c = currentState.map.get(v); 1822 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, false); 1823 } 1824 1825 boolean[][] blockOccupied = new boolean[boundingRect.width()][boundingRect.height()]; 1826 int top = boundingRect.top; 1827 int left = boundingRect.left; 1828 // We mark more precisely which parts of the bounding rect are truly occupied, allowing 1829 // for interlocking. 1830 for (View v: views) { 1831 CellAndSpan c = currentState.map.get(v); 1832 markCellsForView(c.x - left, c.y - top, c.spanX, c.spanY, blockOccupied, true); 1833 } 1834 1835 markCellsForRect(rectOccupiedByPotentialDrop, mTmpOccupied, true); 1836 1837 findNearestArea(boundingRect.left, boundingRect.top, boundingRect.width(), 1838 boundingRect.height(), direction, mTmpOccupied, blockOccupied, mTempLocation); 1839 1840 // If we successfuly found a location by pushing the block of views, we commit it 1841 if (mTempLocation[0] >= 0 && mTempLocation[1] >= 0) { 1842 int deltaX = mTempLocation[0] - boundingRect.left; 1843 int deltaY = mTempLocation[1] - boundingRect.top; 1844 for (View v: views) { 1845 CellAndSpan c = currentState.map.get(v); 1846 c.x += deltaX; 1847 c.y += deltaY; 1848 } 1849 success = true; 1850 } 1851 1852 // In either case, we set the occupied array as marked for the location of the views 1853 for (View v: views) { 1854 CellAndSpan c = currentState.map.get(v); 1855 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true); 1856 } 1857 return success; 1858 } 1859 1860 private void markCellsForRect(Rect r, boolean[][] occupied, boolean value) { 1861 markCellsForView(r.left, r.top, r.width(), r.height(), occupied, value); 1862 } 1863 1864 // This method tries to find a reordering solution which satisfies the push mechanic by trying 1865 // to push items in each of the cardinal directions, in an order based on the direction vector 1866 // passed. 1867 private boolean attemptPushInDirection(ArrayList<View> intersectingViews, Rect occupied, 1868 int[] direction, View ignoreView, ItemConfiguration solution) { 1869 if ((Math.abs(direction[0]) + Math.abs(direction[1])) > 1) { 1870 // If the direction vector has two non-zero components, we try pushing 1871 // separately in each of the components. 1872 int temp = direction[1]; 1873 direction[1] = 0; 1874 1875 if (pushViewsToTempLocation(intersectingViews, occupied, direction, 1876 ignoreView, solution)) { 1877 return true; 1878 } 1879 direction[1] = temp; 1880 temp = direction[0]; 1881 direction[0] = 0; 1882 1883 if (pushViewsToTempLocation(intersectingViews, occupied, direction, 1884 ignoreView, solution)) { 1885 return true; 1886 } 1887 // Revert the direction 1888 direction[0] = temp; 1889 1890 // Now we try pushing in each component of the opposite direction 1891 direction[0] *= -1; 1892 direction[1] *= -1; 1893 temp = direction[1]; 1894 direction[1] = 0; 1895 if (pushViewsToTempLocation(intersectingViews, occupied, direction, 1896 ignoreView, solution)) { 1897 return true; 1898 } 1899 1900 direction[1] = temp; 1901 temp = direction[0]; 1902 direction[0] = 0; 1903 if (pushViewsToTempLocation(intersectingViews, occupied, direction, 1904 ignoreView, solution)) { 1905 return true; 1906 } 1907 // revert the direction 1908 direction[0] = temp; 1909 direction[0] *= -1; 1910 direction[1] *= -1; 1911 1912 } else { 1913 // If the direction vector has a single non-zero component, we push first in the 1914 // direction of the vector 1915 if (pushViewsToTempLocation(intersectingViews, occupied, direction, 1916 ignoreView, solution)) { 1917 return true; 1918 } 1919 // Then we try the opposite direction 1920 direction[0] *= -1; 1921 direction[1] *= -1; 1922 if (pushViewsToTempLocation(intersectingViews, occupied, direction, 1923 ignoreView, solution)) { 1924 return true; 1925 } 1926 // Switch the direction back 1927 direction[0] *= -1; 1928 direction[1] *= -1; 1929 1930 // If we have failed to find a push solution with the above, then we try 1931 // to find a solution by pushing along the perpendicular axis. 1932 1933 // Swap the components 1934 int temp = direction[1]; 1935 direction[1] = direction[0]; 1936 direction[0] = temp; 1937 if (pushViewsToTempLocation(intersectingViews, occupied, direction, 1938 ignoreView, solution)) { 1939 return true; 1940 } 1941 1942 // Then we try the opposite direction 1943 direction[0] *= -1; 1944 direction[1] *= -1; 1945 if (pushViewsToTempLocation(intersectingViews, occupied, direction, 1946 ignoreView, solution)) { 1947 return true; 1948 } 1949 // Switch the direction back 1950 direction[0] *= -1; 1951 direction[1] *= -1; 1952 1953 // Swap the components back 1954 temp = direction[1]; 1955 direction[1] = direction[0]; 1956 direction[0] = temp; 1957 } 1958 return false; 1959 } 1960 1961 private boolean rearrangementExists(int cellX, int cellY, int spanX, int spanY, int[] direction, 1962 View ignoreView, ItemConfiguration solution) { 1963 // Return early if get invalid cell positions 1964 if (cellX < 0 || cellY < 0) return false; 1965 1966 mIntersectingViews.clear(); 1967 mOccupiedRect.set(cellX, cellY, cellX + spanX, cellY + spanY); 1968 1969 // Mark the desired location of the view currently being dragged. 1970 if (ignoreView != null) { 1971 CellAndSpan c = solution.map.get(ignoreView); 1972 if (c != null) { 1973 c.x = cellX; 1974 c.y = cellY; 1975 } 1976 } 1977 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY); 1978 Rect r1 = new Rect(); 1979 for (View child: solution.map.keySet()) { 1980 if (child == ignoreView) continue; 1981 CellAndSpan c = solution.map.get(child); 1982 LayoutParams lp = (LayoutParams) child.getLayoutParams(); 1983 r1.set(c.x, c.y, c.x + c.spanX, c.y + c.spanY); 1984 if (Rect.intersects(r0, r1)) { 1985 if (!lp.canReorder) { 1986 return false; 1987 } 1988 mIntersectingViews.add(child); 1989 } 1990 } 1991 1992 solution.intersectingViews = new ArrayList<View>(mIntersectingViews); 1993 1994 // First we try to find a solution which respects the push mechanic. That is, 1995 // we try to find a solution such that no displaced item travels through another item 1996 // without also displacing that item. 1997 if (attemptPushInDirection(mIntersectingViews, mOccupiedRect, direction, ignoreView, 1998 solution)) { 1999 return true; 2000 } 2001 2002 // Next we try moving the views as a block, but without requiring the push mechanic. 2003 if (addViewsToTempLocation(mIntersectingViews, mOccupiedRect, direction, ignoreView, 2004 solution)) { 2005 return true; 2006 } 2007 2008 // Ok, they couldn't move as a block, let's move them individually 2009 for (View v : mIntersectingViews) { 2010 if (!addViewToTempLocation(v, mOccupiedRect, direction, solution)) { 2011 return false; 2012 } 2013 } 2014 return true; 2015 } 2016 2017 /* 2018 * Returns a pair (x, y), where x,y are in {-1, 0, 1} corresponding to vector between 2019 * the provided point and the provided cell 2020 */ 2021 private void computeDirectionVector(float deltaX, float deltaY, int[] result) { 2022 double angle = Math.atan(((float) deltaY) / deltaX); 2023 2024 result[0] = 0; 2025 result[1] = 0; 2026 if (Math.abs(Math.cos(angle)) > 0.5f) { 2027 result[0] = (int) Math.signum(deltaX); 2028 } 2029 if (Math.abs(Math.sin(angle)) > 0.5f) { 2030 result[1] = (int) Math.signum(deltaY); 2031 } 2032 } 2033 2034 private void copyOccupiedArray(boolean[][] occupied) { 2035 for (int i = 0; i < mCountX; i++) { 2036 for (int j = 0; j < mCountY; j++) { 2037 occupied[i][j] = mOccupied[i][j]; 2038 } 2039 } 2040 } 2041 2042 ItemConfiguration findReorderSolution(int pixelX, int pixelY, int minSpanX, int minSpanY, 2043 int spanX, int spanY, int[] direction, View dragView, boolean decX, 2044 ItemConfiguration solution) { 2045 // Copy the current state into the solution. This solution will be manipulated as necessary. 2046 copyCurrentStateToSolution(solution, false); 2047 // Copy the current occupied array into the temporary occupied array. This array will be 2048 // manipulated as necessary to find a solution. 2049 copyOccupiedArray(mTmpOccupied); 2050 2051 // We find the nearest cell into which we would place the dragged item, assuming there's 2052 // nothing in its way. 2053 int result[] = new int[2]; 2054 result = findNearestArea(pixelX, pixelY, spanX, spanY, result); 2055 2056 boolean success = false; 2057 // First we try the exact nearest position of the item being dragged, 2058 // we will then want to try to move this around to other neighbouring positions 2059 success = rearrangementExists(result[0], result[1], spanX, spanY, direction, dragView, 2060 solution); 2061 2062 if (!success) { 2063 // We try shrinking the widget down to size in an alternating pattern, shrink 1 in 2064 // x, then 1 in y etc. 2065 if (spanX > minSpanX && (minSpanY == spanY || decX)) { 2066 return findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX - 1, spanY, 2067 direction, dragView, false, solution); 2068 } else if (spanY > minSpanY) { 2069 return findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY - 1, 2070 direction, dragView, true, solution); 2071 } 2072 solution.isSolution = false; 2073 } else { 2074 solution.isSolution = true; 2075 solution.dragViewX = result[0]; 2076 solution.dragViewY = result[1]; 2077 solution.dragViewSpanX = spanX; 2078 solution.dragViewSpanY = spanY; 2079 } 2080 return solution; 2081 } 2082 2083 private void copyCurrentStateToSolution(ItemConfiguration solution, boolean temp) { 2084 int childCount = mShortcutsAndWidgets.getChildCount(); 2085 for (int i = 0; i < childCount; i++) { 2086 View child = mShortcutsAndWidgets.getChildAt(i); 2087 LayoutParams lp = (LayoutParams) child.getLayoutParams(); 2088 CellAndSpan c; 2089 if (temp) { 2090 c = new CellAndSpan(lp.tmpCellX, lp.tmpCellY, lp.cellHSpan, lp.cellVSpan); 2091 } else { 2092 c = new CellAndSpan(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan); 2093 } 2094 solution.add(child, c); 2095 } 2096 } 2097 2098 private void copySolutionToTempState(ItemConfiguration solution, View dragView) { 2099 for (int i = 0; i < mCountX; i++) { 2100 for (int j = 0; j < mCountY; j++) { 2101 mTmpOccupied[i][j] = false; 2102 } 2103 } 2104 2105 int childCount = mShortcutsAndWidgets.getChildCount(); 2106 for (int i = 0; i < childCount; i++) { 2107 View child = mShortcutsAndWidgets.getChildAt(i); 2108 if (child == dragView) continue; 2109 LayoutParams lp = (LayoutParams) child.getLayoutParams(); 2110 CellAndSpan c = solution.map.get(child); 2111 if (c != null) { 2112 lp.tmpCellX = c.x; 2113 lp.tmpCellY = c.y; 2114 lp.cellHSpan = c.spanX; 2115 lp.cellVSpan = c.spanY; 2116 markCellsForView(c.x, c.y, c.spanX, c.spanY, mTmpOccupied, true); 2117 } 2118 } 2119 markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX, 2120 solution.dragViewSpanY, mTmpOccupied, true); 2121 } 2122 2123 private void animateItemsToSolution(ItemConfiguration solution, View dragView, boolean 2124 commitDragView) { 2125 2126 boolean[][] occupied = DESTRUCTIVE_REORDER ? mOccupied : mTmpOccupied; 2127 for (int i = 0; i < mCountX; i++) { 2128 for (int j = 0; j < mCountY; j++) { 2129 occupied[i][j] = false; 2130 } 2131 } 2132 2133 int childCount = mShortcutsAndWidgets.getChildCount(); 2134 for (int i = 0; i < childCount; i++) { 2135 View child = mShortcutsAndWidgets.getChildAt(i); 2136 if (child == dragView) continue; 2137 CellAndSpan c = solution.map.get(child); 2138 if (c != null) { 2139 animateChildToPosition(child, c.x, c.y, REORDER_ANIMATION_DURATION, 0, 2140 DESTRUCTIVE_REORDER, false); 2141 markCellsForView(c.x, c.y, c.spanX, c.spanY, occupied, true); 2142 } 2143 } 2144 if (commitDragView) { 2145 markCellsForView(solution.dragViewX, solution.dragViewY, solution.dragViewSpanX, 2146 solution.dragViewSpanY, occupied, true); 2147 } 2148 } 2149 2150 2151 // This method starts or changes the reorder preview animations 2152 private void beginOrAdjustReorderPreviewAnimations(ItemConfiguration solution, 2153 View dragView, int delay, int mode) { 2154 int childCount = mShortcutsAndWidgets.getChildCount(); 2155 for (int i = 0; i < childCount; i++) { 2156 View child = mShortcutsAndWidgets.getChildAt(i); 2157 if (child == dragView) continue; 2158 CellAndSpan c = solution.map.get(child); 2159 boolean skip = mode == ReorderPreviewAnimation.MODE_HINT && solution.intersectingViews 2160 != null && !solution.intersectingViews.contains(child); 2161 2162 LayoutParams lp = (LayoutParams) child.getLayoutParams(); 2163 if (c != null && !skip) { 2164 ReorderPreviewAnimation rha = new ReorderPreviewAnimation(child, mode, lp.cellX, 2165 lp.cellY, c.x, c.y, c.spanX, c.spanY); 2166 rha.animate(); 2167 } 2168 } 2169 } 2170 2171 // Class which represents the reorder preview animations. These animations show that an item is 2172 // in a temporary state, and hint at where the item will return to. 2173 class ReorderPreviewAnimation { 2174 View child; 2175 float finalDeltaX; 2176 float finalDeltaY; 2177 float initDeltaX; 2178 float initDeltaY; 2179 float finalScale; 2180 float initScale; 2181 int mode; 2182 boolean repeating = false; 2183 private static final int PREVIEW_DURATION = 300; 2184 private static final int HINT_DURATION = Workspace.REORDER_TIMEOUT; 2185 2186 public static final int MODE_HINT = 0; 2187 public static final int MODE_PREVIEW = 1; 2188 2189 Animator a; 2190 2191 public ReorderPreviewAnimation(View child, int mode, int cellX0, int cellY0, int cellX1, 2192 int cellY1, int spanX, int spanY) { 2193 regionToCenterPoint(cellX0, cellY0, spanX, spanY, mTmpPoint); 2194 final int x0 = mTmpPoint[0]; 2195 final int y0 = mTmpPoint[1]; 2196 regionToCenterPoint(cellX1, cellY1, spanX, spanY, mTmpPoint); 2197 final int x1 = mTmpPoint[0]; 2198 final int y1 = mTmpPoint[1]; 2199 final int dX = x1 - x0; 2200 final int dY = y1 - y0; 2201 finalDeltaX = 0; 2202 finalDeltaY = 0; 2203 int dir = mode == MODE_HINT ? -1 : 1; 2204 if (dX == dY && dX == 0) { 2205 } else { 2206 if (dY == 0) { 2207 finalDeltaX = - dir * Math.signum(dX) * mReorderPreviewAnimationMagnitude; 2208 } else if (dX == 0) { 2209 finalDeltaY = - dir * Math.signum(dY) * mReorderPreviewAnimationMagnitude; 2210 } else { 2211 double angle = Math.atan( (float) (dY) / dX); 2212 finalDeltaX = (int) (- dir * Math.signum(dX) * 2213 Math.abs(Math.cos(angle) * mReorderPreviewAnimationMagnitude)); 2214 finalDeltaY = (int) (- dir * Math.signum(dY) * 2215 Math.abs(Math.sin(angle) * mReorderPreviewAnimationMagnitude)); 2216 } 2217 } 2218 this.mode = mode; 2219 initDeltaX = child.getTranslationX(); 2220 initDeltaY = child.getTranslationY(); 2221 finalScale = getChildrenScale() - 4.0f / child.getWidth(); 2222 initScale = child.getScaleX(); 2223 this.child = child; 2224 } 2225 2226 void animate() { 2227 if (mShakeAnimators.containsKey(child)) { 2228 ReorderPreviewAnimation oldAnimation = mShakeAnimators.get(child); 2229 oldAnimation.cancel(); 2230 mShakeAnimators.remove(child); 2231 if (finalDeltaX == 0 && finalDeltaY == 0) { 2232 completeAnimationImmediately(); 2233 return; 2234 } 2235 } 2236 if (finalDeltaX == 0 && finalDeltaY == 0) { 2237 return; 2238 } 2239 ValueAnimator va = LauncherAnimUtils.ofFloat(child, 0f, 1f); 2240 a = va; 2241 va.setRepeatMode(ValueAnimator.REVERSE); 2242 va.setRepeatCount(ValueAnimator.INFINITE); 2243 va.setDuration(mode == MODE_HINT ? HINT_DURATION : PREVIEW_DURATION); 2244 va.setStartDelay((int) (Math.random() * 60)); 2245 va.addUpdateListener(new AnimatorUpdateListener() { 2246 @Override 2247 public void onAnimationUpdate(ValueAnimator animation) { 2248 float r = ((Float) animation.getAnimatedValue()).floatValue(); 2249 float r1 = (mode == MODE_HINT && repeating) ? 1.0f : r; 2250 float x = r1 * finalDeltaX + (1 - r1) * initDeltaX; 2251 float y = r1 * finalDeltaY + (1 - r1) * initDeltaY; 2252 child.setTranslationX(x); 2253 child.setTranslationY(y); 2254 float s = r * finalScale + (1 - r) * initScale; 2255 child.setScaleX(s); 2256 child.setScaleY(s); 2257 } 2258 }); 2259 va.addListener(new AnimatorListenerAdapter() { 2260 public void onAnimationRepeat(Animator animation) { 2261 // We make sure to end only after a full period 2262 initDeltaX = 0; 2263 initDeltaY = 0; 2264 initScale = getChildrenScale(); 2265 repeating = true; 2266 } 2267 }); 2268 mShakeAnimators.put(child, this); 2269 va.start(); 2270 } 2271 2272 private void cancel() { 2273 if (a != null) { 2274 a.cancel(); 2275 } 2276 } 2277 2278 private void completeAnimationImmediately() { 2279 if (a != null) { 2280 a.cancel(); 2281 } 2282 2283 AnimatorSet s = LauncherAnimUtils.createAnimatorSet(); 2284 a = s; 2285 s.playTogether( 2286 LauncherAnimUtils.ofFloat(child, "scaleX", getChildrenScale()), 2287 LauncherAnimUtils.ofFloat(child, "scaleY", getChildrenScale()), 2288 LauncherAnimUtils.ofFloat(child, "translationX", 0f), 2289 LauncherAnimUtils.ofFloat(child, "translationY", 0f) 2290 ); 2291 s.setDuration(REORDER_ANIMATION_DURATION); 2292 s.setInterpolator(new android.view.animation.DecelerateInterpolator(1.5f)); 2293 s.start(); 2294 } 2295 } 2296 2297 private void completeAndClearReorderPreviewAnimations() { 2298 for (ReorderPreviewAnimation a: mShakeAnimators.values()) { 2299 a.completeAnimationImmediately(); 2300 } 2301 mShakeAnimators.clear(); 2302 } 2303 2304 private void commitTempPlacement() { 2305 for (int i = 0; i < mCountX; i++) { 2306 for (int j = 0; j < mCountY; j++) { 2307 mOccupied[i][j] = mTmpOccupied[i][j]; 2308 } 2309 } 2310 int childCount = mShortcutsAndWidgets.getChildCount(); 2311 for (int i = 0; i < childCount; i++) { 2312 View child = mShortcutsAndWidgets.getChildAt(i); 2313 LayoutParams lp = (LayoutParams) child.getLayoutParams(); 2314 ItemInfo info = (ItemInfo) child.getTag(); 2315 // We do a null check here because the item info can be null in the case of the 2316 // AllApps button in the hotseat. 2317 if (info != null) { 2318 if (info.cellX != lp.tmpCellX || info.cellY != lp.tmpCellY || 2319 info.spanX != lp.cellHSpan || info.spanY != lp.cellVSpan) { 2320 info.requiresDbUpdate = true; 2321 } 2322 info.cellX = lp.cellX = lp.tmpCellX; 2323 info.cellY = lp.cellY = lp.tmpCellY; 2324 info.spanX = lp.cellHSpan; 2325 info.spanY = lp.cellVSpan; 2326 } 2327 } 2328 mLauncher.getWorkspace().updateItemLocationsInDatabase(this); 2329 } 2330 2331 public void setUseTempCoords(boolean useTempCoords) { 2332 int childCount = mShortcutsAndWidgets.getChildCount(); 2333 for (int i = 0; i < childCount; i++) { 2334 LayoutParams lp = (LayoutParams) mShortcutsAndWidgets.getChildAt(i).getLayoutParams(); 2335 lp.useTmpCoords = useTempCoords; 2336 } 2337 } 2338 2339 ItemConfiguration findConfigurationNoShuffle(int pixelX, int pixelY, int minSpanX, int minSpanY, 2340 int spanX, int spanY, View dragView, ItemConfiguration solution) { 2341 int[] result = new int[2]; 2342 int[] resultSpan = new int[2]; 2343 findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, null, result, 2344 resultSpan); 2345 if (result[0] >= 0 && result[1] >= 0) { 2346 copyCurrentStateToSolution(solution, false); 2347 solution.dragViewX = result[0]; 2348 solution.dragViewY = result[1]; 2349 solution.dragViewSpanX = resultSpan[0]; 2350 solution.dragViewSpanY = resultSpan[1]; 2351 solution.isSolution = true; 2352 } else { 2353 solution.isSolution = false; 2354 } 2355 return solution; 2356 } 2357 2358 public void prepareChildForDrag(View child) { 2359 markCellsAsUnoccupiedForView(child); 2360 } 2361 2362 /* This seems like it should be obvious and straight-forward, but when the direction vector 2363 needs to match with the notion of the dragView pushing other views, we have to employ 2364 a slightly more subtle notion of the direction vector. The question is what two points is 2365 the vector between? The center of the dragView and its desired destination? Not quite, as 2366 this doesn't necessarily coincide with the interaction of the dragView and items occupying 2367 those cells. Instead we use some heuristics to often lock the vector to up, down, left 2368 or right, which helps make pushing feel right. 2369 */ 2370 private void getDirectionVectorForDrop(int dragViewCenterX, int dragViewCenterY, int spanX, 2371 int spanY, View dragView, int[] resultDirection) { 2372 int[] targetDestination = new int[2]; 2373 2374 findNearestArea(dragViewCenterX, dragViewCenterY, spanX, spanY, targetDestination); 2375 Rect dragRect = new Rect(); 2376 regionToRect(targetDestination[0], targetDestination[1], spanX, spanY, dragRect); 2377 dragRect.offset(dragViewCenterX - dragRect.centerX(), dragViewCenterY - dragRect.centerY()); 2378 2379 Rect dropRegionRect = new Rect(); 2380 getViewsIntersectingRegion(targetDestination[0], targetDestination[1], spanX, spanY, 2381 dragView, dropRegionRect, mIntersectingViews); 2382 2383 int dropRegionSpanX = dropRegionRect.width(); 2384 int dropRegionSpanY = dropRegionRect.height(); 2385 2386 regionToRect(dropRegionRect.left, dropRegionRect.top, dropRegionRect.width(), 2387 dropRegionRect.height(), dropRegionRect); 2388 2389 int deltaX = (dropRegionRect.centerX() - dragViewCenterX) / spanX; 2390 int deltaY = (dropRegionRect.centerY() - dragViewCenterY) / spanY; 2391 2392 if (dropRegionSpanX == mCountX || spanX == mCountX) { 2393 deltaX = 0; 2394 } 2395 if (dropRegionSpanY == mCountY || spanY == mCountY) { 2396 deltaY = 0; 2397 } 2398 2399 if (deltaX == 0 && deltaY == 0) { 2400 // No idea what to do, give a random direction. 2401 resultDirection[0] = 1; 2402 resultDirection[1] = 0; 2403 } else { 2404 computeDirectionVector(deltaX, deltaY, resultDirection); 2405 } 2406 } 2407 2408 // For a given cell and span, fetch the set of views intersecting the region. 2409 private void getViewsIntersectingRegion(int cellX, int cellY, int spanX, int spanY, 2410 View dragView, Rect boundingRect, ArrayList<View> intersectingViews) { 2411 if (boundingRect != null) { 2412 boundingRect.set(cellX, cellY, cellX + spanX, cellY + spanY); 2413 } 2414 intersectingViews.clear(); 2415 Rect r0 = new Rect(cellX, cellY, cellX + spanX, cellY + spanY); 2416 Rect r1 = new Rect(); 2417 final int count = mShortcutsAndWidgets.getChildCount(); 2418 for (int i = 0; i < count; i++) { 2419 View child = mShortcutsAndWidgets.getChildAt(i); 2420 if (child == dragView) continue; 2421 LayoutParams lp = (LayoutParams) child.getLayoutParams(); 2422 r1.set(lp.cellX, lp.cellY, lp.cellX + lp.cellHSpan, lp.cellY + lp.cellVSpan); 2423 if (Rect.intersects(r0, r1)) { 2424 mIntersectingViews.add(child); 2425 if (boundingRect != null) { 2426 boundingRect.union(r1); 2427 } 2428 } 2429 } 2430 } 2431 2432 boolean isNearestDropLocationOccupied(int pixelX, int pixelY, int spanX, int spanY, 2433 View dragView, int[] result) { 2434 result = findNearestArea(pixelX, pixelY, spanX, spanY, result); 2435 getViewsIntersectingRegion(result[0], result[1], spanX, spanY, dragView, null, 2436 mIntersectingViews); 2437 return !mIntersectingViews.isEmpty(); 2438 } 2439 2440 void revertTempState() { 2441 completeAndClearReorderPreviewAnimations(); 2442 if (isItemPlacementDirty() && !DESTRUCTIVE_REORDER) { 2443 final int count = mShortcutsAndWidgets.getChildCount(); 2444 for (int i = 0; i < count; i++) { 2445 View child = mShortcutsAndWidgets.getChildAt(i); 2446 LayoutParams lp = (LayoutParams) child.getLayoutParams(); 2447 if (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY) { 2448 lp.tmpCellX = lp.cellX; 2449 lp.tmpCellY = lp.cellY; 2450 animateChildToPosition(child, lp.cellX, lp.cellY, REORDER_ANIMATION_DURATION, 2451 0, false, false); 2452 } 2453 } 2454 setItemPlacementDirty(false); 2455 } 2456 } 2457 2458 boolean createAreaForResize(int cellX, int cellY, int spanX, int spanY, 2459 View dragView, int[] direction, boolean commit) { 2460 int[] pixelXY = new int[2]; 2461 regionToCenterPoint(cellX, cellY, spanX, spanY, pixelXY); 2462 2463 // First we determine if things have moved enough to cause a different layout 2464 ItemConfiguration swapSolution = findReorderSolution(pixelXY[0], pixelXY[1], spanX, spanY, 2465 spanX, spanY, direction, dragView, true, new ItemConfiguration()); 2466 2467 setUseTempCoords(true); 2468 if (swapSolution != null && swapSolution.isSolution) { 2469 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother 2470 // committing anything or animating anything as we just want to determine if a solution 2471 // exists 2472 copySolutionToTempState(swapSolution, dragView); 2473 setItemPlacementDirty(true); 2474 animateItemsToSolution(swapSolution, dragView, commit); 2475 2476 if (commit) { 2477 commitTempPlacement(); 2478 completeAndClearReorderPreviewAnimations(); 2479 setItemPlacementDirty(false); 2480 } else { 2481 beginOrAdjustReorderPreviewAnimations(swapSolution, dragView, 2482 REORDER_ANIMATION_DURATION, ReorderPreviewAnimation.MODE_PREVIEW); 2483 } 2484 mShortcutsAndWidgets.requestLayout(); 2485 } 2486 return swapSolution.isSolution; 2487 } 2488 2489 int[] performReorder(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY, 2490 View dragView, int[] result, int resultSpan[], int mode) { 2491 // First we determine if things have moved enough to cause a different layout 2492 result = findNearestArea(pixelX, pixelY, spanX, spanY, result); 2493 2494 if (resultSpan == null) { 2495 resultSpan = new int[2]; 2496 } 2497 2498 // When we are checking drop validity or actually dropping, we don't recompute the 2499 // direction vector, since we want the solution to match the preview, and it's possible 2500 // that the exact position of the item has changed to result in a new reordering outcome. 2501 if ((mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL || mode == MODE_ACCEPT_DROP) 2502 && mPreviousReorderDirection[0] != INVALID_DIRECTION) { 2503 mDirectionVector[0] = mPreviousReorderDirection[0]; 2504 mDirectionVector[1] = mPreviousReorderDirection[1]; 2505 // We reset this vector after drop 2506 if (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) { 2507 mPreviousReorderDirection[0] = INVALID_DIRECTION; 2508 mPreviousReorderDirection[1] = INVALID_DIRECTION; 2509 } 2510 } else { 2511 getDirectionVectorForDrop(pixelX, pixelY, spanX, spanY, dragView, mDirectionVector); 2512 mPreviousReorderDirection[0] = mDirectionVector[0]; 2513 mPreviousReorderDirection[1] = mDirectionVector[1]; 2514 } 2515 2516 // Find a solution involving pushing / displacing any items in the way 2517 ItemConfiguration swapSolution = findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, 2518 spanX, spanY, mDirectionVector, dragView, true, new ItemConfiguration()); 2519 2520 // We attempt the approach which doesn't shuffle views at all 2521 ItemConfiguration noShuffleSolution = findConfigurationNoShuffle(pixelX, pixelY, minSpanX, 2522 minSpanY, spanX, spanY, dragView, new ItemConfiguration()); 2523 2524 ItemConfiguration finalSolution = null; 2525 2526 // If the reorder solution requires resizing (shrinking) the item being dropped, we instead 2527 // favor a solution in which the item is not resized, but 2528 if (swapSolution.isSolution && swapSolution.area() >= noShuffleSolution.area()) { 2529 finalSolution = swapSolution; 2530 } else if (noShuffleSolution.isSolution) { 2531 finalSolution = noShuffleSolution; 2532 } 2533 2534 if (mode == MODE_SHOW_REORDER_HINT) { 2535 if (finalSolution != null) { 2536 beginOrAdjustReorderPreviewAnimations(finalSolution, dragView, 0, 2537 ReorderPreviewAnimation.MODE_HINT); 2538 result[0] = finalSolution.dragViewX; 2539 result[1] = finalSolution.dragViewY; 2540 resultSpan[0] = finalSolution.dragViewSpanX; 2541 resultSpan[1] = finalSolution.dragViewSpanY; 2542 } else { 2543 result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1; 2544 } 2545 return result; 2546 } 2547 2548 boolean foundSolution = true; 2549 if (!DESTRUCTIVE_REORDER) { 2550 setUseTempCoords(true); 2551 } 2552 2553 if (finalSolution != null) { 2554 result[0] = finalSolution.dragViewX; 2555 result[1] = finalSolution.dragViewY; 2556 resultSpan[0] = finalSolution.dragViewSpanX; 2557 resultSpan[1] = finalSolution.dragViewSpanY; 2558 2559 // If we're just testing for a possible location (MODE_ACCEPT_DROP), we don't bother 2560 // committing anything or animating anything as we just want to determine if a solution 2561 // exists 2562 if (mode == MODE_DRAG_OVER || mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL) { 2563 if (!DESTRUCTIVE_REORDER) { 2564 copySolutionToTempState(finalSolution, dragView); 2565 } 2566 setItemPlacementDirty(true); 2567 animateItemsToSolution(finalSolution, dragView, mode == MODE_ON_DROP); 2568 2569 if (!DESTRUCTIVE_REORDER && 2570 (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL)) { 2571 commitTempPlacement(); 2572 completeAndClearReorderPreviewAnimations(); 2573 setItemPlacementDirty(false); 2574 } else { 2575 beginOrAdjustReorderPreviewAnimations(finalSolution, dragView, 2576 REORDER_ANIMATION_DURATION, ReorderPreviewAnimation.MODE_PREVIEW); 2577 } 2578 } 2579 } else { 2580 foundSolution = false; 2581 result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1; 2582 } 2583 2584 if ((mode == MODE_ON_DROP || !foundSolution) && !DESTRUCTIVE_REORDER) { 2585 setUseTempCoords(false); 2586 } 2587 2588 mShortcutsAndWidgets.requestLayout(); 2589 return result; 2590 } 2591 2592 void setItemPlacementDirty(boolean dirty) { 2593 mItemPlacementDirty = dirty; 2594 } 2595 boolean isItemPlacementDirty() { 2596 return mItemPlacementDirty; 2597 } 2598 2599 private class ItemConfiguration { 2600 HashMap<View, CellAndSpan> map = new HashMap<View, CellAndSpan>(); 2601 private HashMap<View, CellAndSpan> savedMap = new HashMap<View, CellAndSpan>(); 2602 ArrayList<View> sortedViews = new ArrayList<View>(); 2603 ArrayList<View> intersectingViews; 2604 boolean isSolution = false; 2605 int dragViewX, dragViewY, dragViewSpanX, dragViewSpanY; 2606 2607 void save() { 2608 // Copy current state into savedMap 2609 for (View v: map.keySet()) { 2610 map.get(v).copy(savedMap.get(v)); 2611 } 2612 } 2613 2614 void restore() { 2615 // Restore current state from savedMap 2616 for (View v: savedMap.keySet()) { 2617 savedMap.get(v).copy(map.get(v)); 2618 } 2619 } 2620 2621 void add(View v, CellAndSpan cs) { 2622 map.put(v, cs); 2623 savedMap.put(v, new CellAndSpan()); 2624 sortedViews.add(v); 2625 } 2626 2627 int area() { 2628 return dragViewSpanX * dragViewSpanY; 2629 } 2630 } 2631 2632 private class CellAndSpan { 2633 int x, y; 2634 int spanX, spanY; 2635 2636 public CellAndSpan() { 2637 } 2638 2639 public void copy(CellAndSpan copy) { 2640 copy.x = x; 2641 copy.y = y; 2642 copy.spanX = spanX; 2643 copy.spanY = spanY; 2644 } 2645 2646 public CellAndSpan(int x, int y, int spanX, int spanY) { 2647 this.x = x; 2648 this.y = y; 2649 this.spanX = spanX; 2650 this.spanY = spanY; 2651 } 2652 2653 public String toString() { 2654 return "(" + x + ", " + y + ": " + spanX + ", " + spanY + ")"; 2655 } 2656 2657 } 2658 2659 /** 2660 * Find a vacant area that will fit the given bounds nearest the requested 2661 * cell location. Uses Euclidean distance to score multiple vacant areas. 2662 * 2663 * @param pixelX The X location at which you want to search for a vacant area. 2664 * @param pixelY The Y location at which you want to search for a vacant area. 2665 * @param spanX Horizontal span of the object. 2666 * @param spanY Vertical span of the object. 2667 * @param ignoreView Considers space occupied by this view as unoccupied 2668 * @param result Previously returned value to possibly recycle. 2669 * @return The X, Y cell of a vacant area that can contain this object, 2670 * nearest the requested location. 2671 */ 2672 int[] findNearestVacantArea( 2673 int pixelX, int pixelY, int spanX, int spanY, View ignoreView, int[] result) { 2674 return findNearestArea(pixelX, pixelY, spanX, spanY, ignoreView, true, result); 2675 } 2676 2677 /** 2678 * Find a vacant area that will fit the given bounds nearest the requested 2679 * cell location. Uses Euclidean distance to score multiple vacant areas. 2680 * 2681 * @param pixelX The X location at which you want to search for a vacant area. 2682 * @param pixelY The Y location at which you want to search for a vacant area. 2683 * @param minSpanX The minimum horizontal span required 2684 * @param minSpanY The minimum vertical span required 2685 * @param spanX Horizontal span of the object. 2686 * @param spanY Vertical span of the object. 2687 * @param ignoreView Considers space occupied by this view as unoccupied 2688 * @param result Previously returned value to possibly recycle. 2689 * @return The X, Y cell of a vacant area that can contain this object, 2690 * nearest the requested location. 2691 */ 2692 int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY, 2693 int spanX, int spanY, View ignoreView, int[] result, int[] resultSpan) { 2694 return findNearestArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, ignoreView, true, 2695 result, resultSpan, mOccupied); 2696 } 2697 2698 /** 2699 * Find a starting cell position that will fit the given bounds nearest the requested 2700 * cell location. Uses Euclidean distance to score multiple vacant areas. 2701 * 2702 * @param pixelX The X location at which you want to search for a vacant area. 2703 * @param pixelY The Y location at which you want to search for a vacant area. 2704 * @param spanX Horizontal span of the object. 2705 * @param spanY Vertical span of the object. 2706 * @param ignoreView Considers space occupied by this view as unoccupied 2707 * @param result Previously returned value to possibly recycle. 2708 * @return The X, Y cell of a vacant area that can contain this object, 2709 * nearest the requested location. 2710 */ 2711 int[] findNearestArea( 2712 int pixelX, int pixelY, int spanX, int spanY, int[] result) { 2713 return findNearestArea(pixelX, pixelY, spanX, spanY, null, false, result); 2714 } 2715 2716 boolean existsEmptyCell() { 2717 return findCellForSpan(null, 1, 1); 2718 } 2719 2720 /** 2721 * Finds the upper-left coordinate of the first rectangle in the grid that can 2722 * hold a cell of the specified dimensions. If intersectX and intersectY are not -1, 2723 * then this method will only return coordinates for rectangles that contain the cell 2724 * (intersectX, intersectY) 2725 * 2726 * @param cellXY The array that will contain the position of a vacant cell if such a cell 2727 * can be found. 2728 * @param spanX The horizontal span of the cell we want to find. 2729 * @param spanY The vertical span of the cell we want to find. 2730 * 2731 * @return True if a vacant cell of the specified dimension was found, false otherwise. 2732 */ 2733 boolean findCellForSpan(int[] cellXY, int spanX, int spanY) { 2734 return findCellForSpanThatIntersectsIgnoring(cellXY, spanX, spanY, -1, -1, null, mOccupied); 2735 } 2736 2737 /** 2738 * Like above, but ignores any cells occupied by the item "ignoreView" 2739 * 2740 * @param cellXY The array that will contain the position of a vacant cell if such a cell 2741 * can be found. 2742 * @param spanX The horizontal span of the cell we want to find. 2743 * @param spanY The vertical span of the cell we want to find. 2744 * @param ignoreView The home screen item we should treat as not occupying any space 2745 * @return 2746 */ 2747 boolean findCellForSpanIgnoring(int[] cellXY, int spanX, int spanY, View ignoreView) { 2748 return findCellForSpanThatIntersectsIgnoring(cellXY, spanX, spanY, -1, -1, 2749 ignoreView, mOccupied); 2750 } 2751 2752 /** 2753 * Like above, but if intersectX and intersectY are not -1, then this method will try to 2754 * return coordinates for rectangles that contain the cell [intersectX, intersectY] 2755 * 2756 * @param spanX The horizontal span of the cell we want to find. 2757 * @param spanY The vertical span of the cell we want to find. 2758 * @param ignoreView The home screen item we should treat as not occupying any space 2759 * @param intersectX The X coordinate of the cell that we should try to overlap 2760 * @param intersectX The Y coordinate of the cell that we should try to overlap 2761 * 2762 * @return True if a vacant cell of the specified dimension was found, false otherwise. 2763 */ 2764 boolean findCellForSpanThatIntersects(int[] cellXY, int spanX, int spanY, 2765 int intersectX, int intersectY) { 2766 return findCellForSpanThatIntersectsIgnoring( 2767 cellXY, spanX, spanY, intersectX, intersectY, null, mOccupied); 2768 } 2769 2770 /** 2771 * The superset of the above two methods 2772 */ 2773 boolean findCellForSpanThatIntersectsIgnoring(int[] cellXY, int spanX, int spanY, 2774 int intersectX, int intersectY, View ignoreView, boolean occupied[][]) { 2775 // mark space take by ignoreView as available (method checks if ignoreView is null) 2776 markCellsAsUnoccupiedForView(ignoreView, occupied); 2777 2778 boolean foundCell = false; 2779 while (true) { 2780 int startX = 0; 2781 if (intersectX >= 0) { 2782 startX = Math.max(startX, intersectX - (spanX - 1)); 2783 } 2784 int endX = mCountX - (spanX - 1); 2785 if (intersectX >= 0) { 2786 endX = Math.min(endX, intersectX + (spanX - 1) + (spanX == 1 ? 1 : 0)); 2787 } 2788 int startY = 0; 2789 if (intersectY >= 0) { 2790 startY = Math.max(startY, intersectY - (spanY - 1)); 2791 } 2792 int endY = mCountY - (spanY - 1); 2793 if (intersectY >= 0) { 2794 endY = Math.min(endY, intersectY + (spanY - 1) + (spanY == 1 ? 1 : 0)); 2795 } 2796 2797 for (int y = startY; y < endY && !foundCell; y++) { 2798 inner: 2799 for (int x = startX; x < endX; x++) { 2800 for (int i = 0; i < spanX; i++) { 2801 for (int j = 0; j < spanY; j++) { 2802 if (occupied[x + i][y + j]) { 2803 // small optimization: we can skip to after the column we just found 2804 // an occupied cell 2805 x += i; 2806 continue inner; 2807 } 2808 } 2809 } 2810 if (cellXY != null) { 2811 cellXY[0] = x; 2812 cellXY[1] = y; 2813 } 2814 foundCell = true; 2815 break; 2816 } 2817 } 2818 if (intersectX == -1 && intersectY == -1) { 2819 break; 2820 } else { 2821 // if we failed to find anything, try again but without any requirements of 2822 // intersecting 2823 intersectX = -1; 2824 intersectY = -1; 2825 continue; 2826 } 2827 } 2828 2829 // re-mark space taken by ignoreView as occupied 2830 markCellsAsOccupiedForView(ignoreView, occupied); 2831 return foundCell; 2832 } 2833 2834 /** 2835 * A drag event has begun over this layout. 2836 * It may have begun over this layout (in which case onDragChild is called first), 2837 * or it may have begun on another layout. 2838 */ 2839 void onDragEnter() { 2840 mDragEnforcer.onDragEnter(); 2841 mDragging = true; 2842 } 2843 2844 /** 2845 * Called when drag has left this CellLayout or has been completed (successfully or not) 2846 */ 2847 void onDragExit() { 2848 mDragEnforcer.onDragExit(); 2849 // This can actually be called when we aren't in a drag, e.g. when adding a new 2850 // item to this layout via the customize drawer. 2851 // Guard against that case. 2852 if (mDragging) { 2853 mDragging = false; 2854 } 2855 2856 // Invalidate the drag data 2857 mDragCell[0] = mDragCell[1] = -1; 2858 mDragOutlineAnims[mDragOutlineCurrent].animateOut(); 2859 mDragOutlineCurrent = (mDragOutlineCurrent + 1) % mDragOutlineAnims.length; 2860 revertTempState(); 2861 setIsDragOverlapping(false); 2862 } 2863 2864 /** 2865 * Mark a child as having been dropped. 2866 * At the beginning of the drag operation, the child may have been on another 2867 * screen, but it is re-parented before this method is called. 2868 * 2869 * @param child The child that is being dropped 2870 */ 2871 void onDropChild(View child) { 2872 if (child != null) { 2873 LayoutParams lp = (LayoutParams) child.getLayoutParams(); 2874 lp.dropped = true; 2875 child.requestLayout(); 2876 } 2877 } 2878 2879 /** 2880 * Computes a bounding rectangle for a range of cells 2881 * 2882 * @param cellX X coordinate of upper left corner expressed as a cell position 2883 * @param cellY Y coordinate of upper left corner expressed as a cell position 2884 * @param cellHSpan Width in cells 2885 * @param cellVSpan Height in cells 2886 * @param resultRect Rect into which to put the results 2887 */ 2888 public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, Rect resultRect) { 2889 final int cellWidth = mCellWidth; 2890 final int cellHeight = mCellHeight; 2891 final int widthGap = mWidthGap; 2892 final int heightGap = mHeightGap; 2893 2894 final int hStartPadding = getPaddingLeft(); 2895 final int vStartPadding = getPaddingTop(); 2896 2897 int width = cellHSpan * cellWidth + ((cellHSpan - 1) * widthGap); 2898 int height = cellVSpan * cellHeight + ((cellVSpan - 1) * heightGap); 2899 2900 int x = hStartPadding + cellX * (cellWidth + widthGap); 2901 int y = vStartPadding + cellY * (cellHeight + heightGap); 2902 2903 resultRect.set(x, y, x + width, y + height); 2904 } 2905 2906 /** 2907 * Computes the required horizontal and vertical cell spans to always 2908 * fit the given rectangle. 2909 * 2910 * @param width Width in pixels 2911 * @param height Height in pixels 2912 * @param result An array of length 2 in which to store the result (may be null). 2913 */ 2914 public static int[] rectToCell(int width, int height, int[] result) { 2915 LauncherAppState app = LauncherAppState.getInstance(); 2916 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile(); 2917 Rect padding = grid.getWorkspacePadding(grid.isLandscape ? 2918 CellLayout.LANDSCAPE : CellLayout.PORTRAIT); 2919 2920 // Always assume we're working with the smallest span to make sure we 2921 // reserve enough space in both orientations. 2922 int parentWidth = grid.calculateCellWidth(grid.widthPx 2923 - padding.left - padding.right, (int) grid.numColumns); 2924 int parentHeight = grid.calculateCellHeight(grid.heightPx 2925 - padding.top - padding.bottom, (int) grid.numRows); 2926 int smallerSize = Math.min(parentWidth, parentHeight); 2927 2928 // Always round up to next largest cell 2929 int spanX = (int) Math.ceil(width / (float) smallerSize); 2930 int spanY = (int) Math.ceil(height / (float) smallerSize); 2931 2932 if (result == null) { 2933 return new int[] { spanX, spanY }; 2934 } 2935 result[0] = spanX; 2936 result[1] = spanY; 2937 return result; 2938 } 2939 2940 public int[] cellSpansToSize(int hSpans, int vSpans) { 2941 int[] size = new int[2]; 2942 size[0] = hSpans * mCellWidth + (hSpans - 1) * mWidthGap; 2943 size[1] = vSpans * mCellHeight + (vSpans - 1) * mHeightGap; 2944 return size; 2945 } 2946 2947 /** 2948 * Calculate the grid spans needed to fit given item 2949 */ 2950 public void calculateSpans(ItemInfo info) { 2951 final int minWidth; 2952 final int minHeight; 2953 2954 if (info instanceof LauncherAppWidgetInfo) { 2955 minWidth = ((LauncherAppWidgetInfo) info).minWidth; 2956 minHeight = ((LauncherAppWidgetInfo) info).minHeight; 2957 } else if (info instanceof PendingAddWidgetInfo) { 2958 minWidth = ((PendingAddWidgetInfo) info).minWidth; 2959 minHeight = ((PendingAddWidgetInfo) info).minHeight; 2960 } else { 2961 // It's not a widget, so it must be 1x1 2962 info.spanX = info.spanY = 1; 2963 return; 2964 } 2965 int[] spans = rectToCell(minWidth, minHeight, null); 2966 info.spanX = spans[0]; 2967 info.spanY = spans[1]; 2968 } 2969 2970 /** 2971 * Find the first vacant cell, if there is one. 2972 * 2973 * @param vacant Holds the x and y coordinate of the vacant cell 2974 * @param spanX Horizontal cell span. 2975 * @param spanY Vertical cell span. 2976 * 2977 * @return True if a vacant cell was found 2978 */ 2979 public boolean getVacantCell(int[] vacant, int spanX, int spanY) { 2980 2981 return findVacantCell(vacant, spanX, spanY, mCountX, mCountY, mOccupied); 2982 } 2983 2984 static boolean findVacantCell(int[] vacant, int spanX, int spanY, 2985 int xCount, int yCount, boolean[][] occupied) { 2986 2987 for (int y = 0; y < yCount; y++) { 2988 for (int x = 0; x < xCount; x++) { 2989 boolean available = !occupied[x][y]; 2990out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) { 2991 for (int j = y; j < y + spanY - 1 && y < yCount; j++) { 2992 available = available && !occupied[i][j]; 2993 if (!available) break out; 2994 } 2995 } 2996 2997 if (available) { 2998 vacant[0] = x; 2999 vacant[1] = y; 3000 return true; 3001 } 3002 } 3003 } 3004 3005 return false; 3006 } 3007 3008 private void clearOccupiedCells() { 3009 for (int x = 0; x < mCountX; x++) { 3010 for (int y = 0; y < mCountY; y++) { 3011 mOccupied[x][y] = false; 3012 } 3013 } 3014 } 3015 3016 public void onMove(View view, int newCellX, int newCellY, int newSpanX, int newSpanY) { 3017 markCellsAsUnoccupiedForView(view); 3018 markCellsForView(newCellX, newCellY, newSpanX, newSpanY, mOccupied, true); 3019 } 3020 3021 public void markCellsAsOccupiedForView(View view) { 3022 markCellsAsOccupiedForView(view, mOccupied); 3023 } 3024 public void markCellsAsOccupiedForView(View view, boolean[][] occupied) { 3025 if (view == null || view.getParent() != mShortcutsAndWidgets) return; 3026 LayoutParams lp = (LayoutParams) view.getLayoutParams(); 3027 markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, occupied, true); 3028 } 3029 3030 public void markCellsAsUnoccupiedForView(View view) { 3031 markCellsAsUnoccupiedForView(view, mOccupied); 3032 } 3033 public void markCellsAsUnoccupiedForView(View view, boolean occupied[][]) { 3034 if (view == null || view.getParent() != mShortcutsAndWidgets) return; 3035 LayoutParams lp = (LayoutParams) view.getLayoutParams(); 3036 markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, occupied, false); 3037 } 3038 3039 private void markCellsForView(int cellX, int cellY, int spanX, int spanY, boolean[][] occupied, 3040 boolean value) { 3041 if (cellX < 0 || cellY < 0) return; 3042 for (int x = cellX; x < cellX + spanX && x < mCountX; x++) { 3043 for (int y = cellY; y < cellY + spanY && y < mCountY; y++) { 3044 occupied[x][y] = value; 3045 } 3046 } 3047 } 3048 3049 public int getDesiredWidth() { 3050 return getPaddingLeft() + getPaddingRight() + (mCountX * mCellWidth) + 3051 (Math.max((mCountX - 1), 0) * mWidthGap); 3052 } 3053 3054 public int getDesiredHeight() { 3055 return getPaddingTop() + getPaddingBottom() + (mCountY * mCellHeight) + 3056 (Math.max((mCountY - 1), 0) * mHeightGap); 3057 } 3058 3059 public boolean isOccupied(int x, int y) { 3060 if (x < mCountX && y < mCountY) { 3061 return mOccupied[x][y]; 3062 } else { 3063 throw new RuntimeException("Position exceeds the bound of this CellLayout"); 3064 } 3065 } 3066 3067 @Override 3068 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { 3069 return new CellLayout.LayoutParams(getContext(), attrs); 3070 } 3071 3072 @Override 3073 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 3074 return p instanceof CellLayout.LayoutParams; 3075 } 3076 3077 @Override 3078 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { 3079 return new CellLayout.LayoutParams(p); 3080 } 3081 3082 public static class CellLayoutAnimationController extends LayoutAnimationController { 3083 public CellLayoutAnimationController(Animation animation, float delay) { 3084 super(animation, delay); 3085 } 3086 3087 @Override 3088 protected long getDelayForView(View view) { 3089 return (int) (Math.random() * 150); 3090 } 3091 } 3092 3093 public static class LayoutParams extends ViewGroup.MarginLayoutParams { 3094 /** 3095 * Horizontal location of the item in the grid. 3096 */ 3097 @ViewDebug.ExportedProperty 3098 public int cellX; 3099 3100 /** 3101 * Vertical location of the item in the grid. 3102 */ 3103 @ViewDebug.ExportedProperty 3104 public int cellY; 3105 3106 /** 3107 * Temporary horizontal location of the item in the grid during reorder 3108 */ 3109 public int tmpCellX; 3110 3111 /** 3112 * Temporary vertical location of the item in the grid during reorder 3113 */ 3114 public int tmpCellY; 3115 3116 /** 3117 * Indicates that the temporary coordinates should be used to layout the items 3118 */ 3119 public boolean useTmpCoords; 3120 3121 /** 3122 * Number of cells spanned horizontally by the item. 3123 */ 3124 @ViewDebug.ExportedProperty 3125 public int cellHSpan; 3126 3127 /** 3128 * Number of cells spanned vertically by the item. 3129 */ 3130 @ViewDebug.ExportedProperty 3131 public int cellVSpan; 3132 3133 /** 3134 * Indicates whether the item will set its x, y, width and height parameters freely, 3135 * or whether these will be computed based on cellX, cellY, cellHSpan and cellVSpan. 3136 */ 3137 public boolean isLockedToGrid = true; 3138 3139 /** 3140 * Indicates that this item should use the full extents of its parent. 3141 */ 3142 public boolean isFullscreen = false; 3143 3144 /** 3145 * Indicates whether this item can be reordered. Always true except in the case of the 3146 * the AllApps button. 3147 */ 3148 public boolean canReorder = true; 3149 3150 // X coordinate of the view in the layout. 3151 @ViewDebug.ExportedProperty 3152 int x; 3153 // Y coordinate of the view in the layout. 3154 @ViewDebug.ExportedProperty 3155 int y; 3156 3157 boolean dropped; 3158 3159 public LayoutParams(Context c, AttributeSet attrs) { 3160 super(c, attrs); 3161 cellHSpan = 1; 3162 cellVSpan = 1; 3163 } 3164 3165 public LayoutParams(ViewGroup.LayoutParams source) { 3166 super(source); 3167 cellHSpan = 1; 3168 cellVSpan = 1; 3169 } 3170 3171 public LayoutParams(LayoutParams source) { 3172 super(source); 3173 this.cellX = source.cellX; 3174 this.cellY = source.cellY; 3175 this.cellHSpan = source.cellHSpan; 3176 this.cellVSpan = source.cellVSpan; 3177 } 3178 3179 public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) { 3180 super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); 3181 this.cellX = cellX; 3182 this.cellY = cellY; 3183 this.cellHSpan = cellHSpan; 3184 this.cellVSpan = cellVSpan; 3185 } 3186 3187 public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap, 3188 boolean invertHorizontally, int colCount) { 3189 if (isLockedToGrid) { 3190 final int myCellHSpan = cellHSpan; 3191 final int myCellVSpan = cellVSpan; 3192 int myCellX = useTmpCoords ? tmpCellX : cellX; 3193 int myCellY = useTmpCoords ? tmpCellY : cellY; 3194 3195 if (invertHorizontally) { 3196 myCellX = colCount - myCellX - cellHSpan; 3197 } 3198 3199 width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) - 3200 leftMargin - rightMargin; 3201 height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) - 3202 topMargin - bottomMargin; 3203 x = (int) (myCellX * (cellWidth + widthGap) + leftMargin); 3204 y = (int) (myCellY * (cellHeight + heightGap) + topMargin); 3205 } 3206 } 3207 3208 public String toString() { 3209 return "(" + this.cellX + ", " + this.cellY + ")"; 3210 } 3211 3212 public void setWidth(int width) { 3213 this.width = width; 3214 } 3215 3216 public int getWidth() { 3217 return width; 3218 } 3219 3220 public void setHeight(int height) { 3221 this.height = height; 3222 } 3223 3224 public int getHeight() { 3225 return height; 3226 } 3227 3228 public void setX(int x) { 3229 this.x = x; 3230 } 3231 3232 public int getX() { 3233 return x; 3234 } 3235 3236 public void setY(int y) { 3237 this.y = y; 3238 } 3239 3240 public int getY() { 3241 return y; 3242 } 3243 } 3244 3245 // This class stores info for two purposes: 3246 // 1. When dragging items (mDragInfo in Workspace), we store the View, its cellX & cellY, 3247 // its spanX, spanY, and the screen it is on 3248 // 2. When long clicking on an empty cell in a CellLayout, we save information about the 3249 // cellX and cellY coordinates and which page was clicked. We then set this as a tag on 3250 // the CellLayout that was long clicked 3251 static final class CellInfo { 3252 View cell; 3253 int cellX = -1; 3254 int cellY = -1; 3255 int spanX; 3256 int spanY; 3257 long screenId; 3258 long container; 3259 3260 CellInfo(View v, ItemInfo info) { 3261 cell = v; 3262 cellX = info.cellX; 3263 cellY = info.cellY; 3264 spanX = info.spanX; 3265 spanY = info.spanY; 3266 screenId = info.screenId; 3267 container = info.container; 3268 } 3269 3270 @Override 3271 public String toString() { 3272 return "Cell[view=" + (cell == null ? "null" : cell.getClass()) 3273 + ", x=" + cellX + ", y=" + cellY + "]"; 3274 } 3275 } 3276 3277 public boolean lastDownOnOccupiedCell() { 3278 return mLastDownOnOccupiedCell; 3279 } 3280} 3281