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