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