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