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