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