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