1 /* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.launcher2; 18 19 import android.animation.Animator; 20 import android.animation.AnimatorListenerAdapter; 21 import android.animation.AnimatorSet; 22 import android.animation.ObjectAnimator; 23 import android.animation.PropertyValuesHolder; 24 import android.animation.TimeInterpolator; 25 import android.animation.ValueAnimator; 26 import android.animation.ValueAnimator.AnimatorUpdateListener; 27 import android.content.Context; 28 import android.content.res.Resources; 29 import android.content.res.TypedArray; 30 import android.graphics.Bitmap; 31 import android.graphics.Canvas; 32 import android.graphics.Paint; 33 import android.graphics.Point; 34 import android.graphics.PointF; 35 import android.graphics.PorterDuff; 36 import android.graphics.PorterDuffXfermode; 37 import android.graphics.Rect; 38 import android.graphics.RectF; 39 import android.graphics.Region; 40 import android.graphics.drawable.Drawable; 41 import android.graphics.drawable.NinePatchDrawable; 42 import android.util.AttributeSet; 43 import android.util.Log; 44 import android.view.MotionEvent; 45 import android.view.View; 46 import android.view.ViewDebug; 47 import android.view.ViewGroup; 48 import android.view.animation.Animation; 49 import android.view.animation.DecelerateInterpolator; 50 import android.view.animation.LayoutAnimationController; 51 52 import com.android.launcher.R; 53 import com.android.launcher2.FolderIcon.FolderRingAnimator; 54 55 import java.util.ArrayList; 56 import java.util.Arrays; 57 import java.util.HashMap; 58 59 public class CellLayout extends ViewGroup { 60 static final String TAG = "CellLayout"; 61 62 private int mOriginalCellWidth; 63 private int mOriginalCellHeight; 64 private int mCellWidth; 65 private int mCellHeight; 66 67 private int mCountX; 68 private int mCountY; 69 70 private int mOriginalWidthGap; 71 private int mOriginalHeightGap; 72 private int mWidthGap; 73 private int mHeightGap; 74 private int mMaxGap; 75 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 private final PointF mTmpPointF = new PointF(); 84 int[] mTempLocation = new int[2]; 85 86 boolean[][] mOccupied; 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 mActiveBackground; 100 private Drawable mActiveGlowBackground; 101 private Drawable mNormalBackgroundMini; 102 private Drawable mNormalGlowBackgroundMini; 103 private Drawable mActiveBackgroundMini; 104 private Drawable mActiveGlowBackgroundMini; 105 private Drawable mOverScrollForegroundDrawable; 106 private Drawable mOverScrollLeft; 107 private Drawable mOverScrollRight; 108 private Rect mBackgroundRect; 109 private Rect mForegroundRect; 110 private Rect mGlowBackgroundRect; 111 private float mGlowBackgroundScale; 112 private float mGlowBackgroundAlpha; 113 private int mForegroundPadding; 114 115 private boolean mAcceptsDrops = true; 116 // If we're actively dragging something over this screen, mIsDragOverlapping is true 117 private boolean mIsDragOverlapping = false; 118 private boolean mIsDragOccuring = false; 119 private boolean mIsDefaultDropTarget = false; 120 private final Point mDragCenter = new Point(); 121 122 // These arrays are used to implement the drag visualization on x-large screens. 123 // They are used as circular arrays, indexed by mDragOutlineCurrent. 124 private Point[] mDragOutlines = new Point[4]; 125 private float[] mDragOutlineAlphas = new float[mDragOutlines.length]; 126 private InterruptibleInOutAnimator[] mDragOutlineAnims = 127 new InterruptibleInOutAnimator[mDragOutlines.length]; 128 129 // Used as an index into the above 3 arrays; indicates which is the most current value. 130 private int mDragOutlineCurrent = 0; 131 private final Paint mDragOutlinePaint = new Paint(); 132 133 private BubbleTextView mPressedOrFocusedIcon; 134 135 private Drawable mCrosshairsDrawable = null; 136 private InterruptibleInOutAnimator mCrosshairsAnimator = null; 137 private float mCrosshairsVisibility = 0.0f; 138 139 private HashMap<CellLayout.LayoutParams, ObjectAnimator> mReorderAnimators = new 140 HashMap<CellLayout.LayoutParams, ObjectAnimator>(); 141 142 // When a drag operation is in progress, holds the nearest cell to the touch point 143 private final int[] mDragCell = new int[2]; 144 145 private boolean mDragging = false; 146 147 private TimeInterpolator mEaseOutInterpolator; 148 private CellLayoutChildren mChildren; 149 CellLayout(Context context)150 public CellLayout(Context context) { 151 this(context, null); 152 } 153 CellLayout(Context context, AttributeSet attrs)154 public CellLayout(Context context, AttributeSet attrs) { 155 this(context, attrs, 0); 156 } 157 CellLayout(Context context, AttributeSet attrs, int defStyle)158 public CellLayout(Context context, AttributeSet attrs, int defStyle) { 159 super(context, attrs, defStyle); 160 161 // A ViewGroup usually does not draw, but CellLayout needs to draw a rectangle to show 162 // the user where a dragged item will land when dropped. 163 setWillNotDraw(false); 164 165 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0); 166 167 mOriginalCellWidth = 168 mCellWidth = a.getDimensionPixelSize(R.styleable.CellLayout_cellWidth, 10); 169 mOriginalCellHeight = 170 mCellHeight = a.getDimensionPixelSize(R.styleable.CellLayout_cellHeight, 10); 171 mWidthGap = mOriginalWidthGap = a.getDimensionPixelSize(R.styleable.CellLayout_widthGap, 0); 172 mHeightGap = mOriginalHeightGap = a.getDimensionPixelSize(R.styleable.CellLayout_heightGap, 0); 173 mMaxGap = a.getDimensionPixelSize(R.styleable.CellLayout_maxGap, 0); 174 mCountX = LauncherModel.getCellCountX(); 175 mCountY = LauncherModel.getCellCountY(); 176 mOccupied = new boolean[mCountX][mCountY]; 177 178 a.recycle(); 179 180 setAlwaysDrawnWithCacheEnabled(false); 181 182 final Resources res = getResources(); 183 184 mNormalBackground = res.getDrawable(R.drawable.homescreen_blue_normal_holo); 185 mActiveBackground = res.getDrawable(R.drawable.homescreen_blue_strong_holo); 186 mActiveGlowBackground = res.getDrawable(R.drawable.homescreen_blue_strong_holo); 187 188 mNormalBackgroundMini = res.getDrawable(R.drawable.homescreen_small_blue); 189 mNormalGlowBackgroundMini = res.getDrawable(R.drawable.homescreen_small_blue_strong); 190 mActiveBackgroundMini = res.getDrawable(R.drawable.homescreen_small_blue_strong); 191 mActiveGlowBackgroundMini = res.getDrawable(R.drawable.homescreen_small_blue_strong); 192 mOverScrollLeft = res.getDrawable(R.drawable.overscroll_glow_left); 193 mOverScrollRight = res.getDrawable(R.drawable.overscroll_glow_right); 194 mForegroundPadding = 195 res.getDimensionPixelSize(R.dimen.workspace_overscroll_drawable_padding); 196 197 mNormalBackground.setFilterBitmap(true); 198 mActiveBackground.setFilterBitmap(true); 199 mActiveGlowBackground.setFilterBitmap(true); 200 mNormalBackgroundMini.setFilterBitmap(true); 201 mNormalGlowBackgroundMini.setFilterBitmap(true); 202 mActiveBackgroundMini.setFilterBitmap(true); 203 mActiveGlowBackgroundMini.setFilterBitmap(true); 204 205 // Initialize the data structures used for the drag visualization. 206 207 mCrosshairsDrawable = res.getDrawable(R.drawable.gardening_crosshairs); 208 mEaseOutInterpolator = new DecelerateInterpolator(2.5f); // Quint ease out 209 210 // Set up the animation for fading the crosshairs in and out 211 int animDuration = res.getInteger(R.integer.config_crosshairsFadeInTime); 212 mCrosshairsAnimator = new InterruptibleInOutAnimator(animDuration, 0.0f, 1.0f); 213 mCrosshairsAnimator.getAnimator().addUpdateListener(new AnimatorUpdateListener() { 214 public void onAnimationUpdate(ValueAnimator animation) { 215 mCrosshairsVisibility = ((Float) animation.getAnimatedValue()).floatValue(); 216 invalidate(); 217 } 218 }); 219 mCrosshairsAnimator.getAnimator().setInterpolator(mEaseOutInterpolator); 220 221 mDragCell[0] = mDragCell[1] = -1; 222 for (int i = 0; i < mDragOutlines.length; i++) { 223 mDragOutlines[i] = new Point(-1, -1); 224 } 225 226 // When dragging things around the home screens, we show a green outline of 227 // where the item will land. The outlines gradually fade out, leaving a trail 228 // behind the drag path. 229 // Set up all the animations that are used to implement this fading. 230 final int duration = res.getInteger(R.integer.config_dragOutlineFadeTime); 231 final float fromAlphaValue = 0; 232 final float toAlphaValue = (float)res.getInteger(R.integer.config_dragOutlineMaxAlpha); 233 234 Arrays.fill(mDragOutlineAlphas, fromAlphaValue); 235 236 for (int i = 0; i < mDragOutlineAnims.length; i++) { 237 final InterruptibleInOutAnimator anim = 238 new InterruptibleInOutAnimator(duration, fromAlphaValue, toAlphaValue); 239 anim.getAnimator().setInterpolator(mEaseOutInterpolator); 240 final int thisIndex = i; 241 anim.getAnimator().addUpdateListener(new AnimatorUpdateListener() { 242 public void onAnimationUpdate(ValueAnimator animation) { 243 final Bitmap outline = (Bitmap)anim.getTag(); 244 245 // If an animation is started and then stopped very quickly, we can still 246 // get spurious updates we've cleared the tag. Guard against this. 247 if (outline == null) { 248 if (false) { 249 Object val = animation.getAnimatedValue(); 250 Log.d(TAG, "anim " + thisIndex + " update: " + val + 251 ", isStopped " + anim.isStopped()); 252 } 253 // Try to prevent it from continuing to run 254 animation.cancel(); 255 } else { 256 mDragOutlineAlphas[thisIndex] = (Float) animation.getAnimatedValue(); 257 final int left = mDragOutlines[thisIndex].x; 258 final int top = mDragOutlines[thisIndex].y; 259 CellLayout.this.invalidate(left, top, 260 left + outline.getWidth(), top + outline.getHeight()); 261 } 262 } 263 }); 264 // The animation holds a reference to the drag outline bitmap as long is it's 265 // running. This way the bitmap can be GCed when the animations are complete. 266 anim.getAnimator().addListener(new AnimatorListenerAdapter() { 267 @Override 268 public void onAnimationEnd(Animator animation) { 269 if ((Float) ((ValueAnimator) animation).getAnimatedValue() == 0f) { 270 anim.setTag(null); 271 } 272 } 273 }); 274 mDragOutlineAnims[i] = anim; 275 } 276 277 mBackgroundRect = new Rect(); 278 mForegroundRect = new Rect(); 279 mGlowBackgroundRect = new Rect(); 280 setHoverScale(1.0f); 281 setHoverAlpha(1.0f); 282 283 mChildren = new CellLayoutChildren(context); 284 mChildren.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap); 285 addView(mChildren); 286 } 287 widthInPortrait(Resources r, int numCells)288 static int widthInPortrait(Resources r, int numCells) { 289 // We use this method from Workspace to figure out how many rows/columns Launcher should 290 // have. We ignore the left/right padding on CellLayout because it turns out in our design 291 // the padding extends outside the visible screen size, but it looked fine anyway. 292 int cellWidth = r.getDimensionPixelSize(R.dimen.workspace_cell_width); 293 int minGap = Math.min(r.getDimensionPixelSize(R.dimen.workspace_width_gap), 294 r.getDimensionPixelSize(R.dimen.workspace_height_gap)); 295 296 return minGap * (numCells - 1) + cellWidth * numCells; 297 } 298 heightInLandscape(Resources r, int numCells)299 static int heightInLandscape(Resources r, int numCells) { 300 // We use this method from Workspace to figure out how many rows/columns Launcher should 301 // have. We ignore the left/right padding on CellLayout because it turns out in our design 302 // the padding extends outside the visible screen size, but it looked fine anyway. 303 int cellHeight = r.getDimensionPixelSize(R.dimen.workspace_cell_height); 304 int minGap = Math.min(r.getDimensionPixelSize(R.dimen.workspace_width_gap), 305 r.getDimensionPixelSize(R.dimen.workspace_height_gap)); 306 307 return minGap * (numCells - 1) + cellHeight * numCells; 308 } 309 enableHardwareLayers()310 public void enableHardwareLayers() { 311 mChildren.enableHardwareLayers(); 312 } 313 setGridSize(int x, int y)314 public void setGridSize(int x, int y) { 315 mCountX = x; 316 mCountY = y; 317 mOccupied = new boolean[mCountX][mCountY]; 318 requestLayout(); 319 } 320 invalidateBubbleTextView(BubbleTextView icon)321 private void invalidateBubbleTextView(BubbleTextView icon) { 322 final int padding = icon.getPressedOrFocusedBackgroundPadding(); 323 invalidate(icon.getLeft() + getPaddingLeft() - padding, 324 icon.getTop() + getPaddingTop() - padding, 325 icon.getRight() + getPaddingLeft() + padding, 326 icon.getBottom() + getPaddingTop() + padding); 327 } 328 setOverScrollAmount(float r, boolean left)329 void setOverScrollAmount(float r, boolean left) { 330 if (left && mOverScrollForegroundDrawable != mOverScrollLeft) { 331 mOverScrollForegroundDrawable = mOverScrollLeft; 332 } else if (!left && mOverScrollForegroundDrawable != mOverScrollRight) { 333 mOverScrollForegroundDrawable = mOverScrollRight; 334 } 335 336 mForegroundAlpha = (int) Math.round((r * 255)); 337 mOverScrollForegroundDrawable.setAlpha(mForegroundAlpha); 338 invalidate(); 339 } 340 setPressedOrFocusedIcon(BubbleTextView icon)341 void setPressedOrFocusedIcon(BubbleTextView icon) { 342 // We draw the pressed or focused BubbleTextView's background in CellLayout because it 343 // requires an expanded clip rect (due to the glow's blur radius) 344 BubbleTextView oldIcon = mPressedOrFocusedIcon; 345 mPressedOrFocusedIcon = icon; 346 if (oldIcon != null) { 347 invalidateBubbleTextView(oldIcon); 348 } 349 if (mPressedOrFocusedIcon != null) { 350 invalidateBubbleTextView(mPressedOrFocusedIcon); 351 } 352 } 353 getChildrenLayout()354 public CellLayoutChildren getChildrenLayout() { 355 if (getChildCount() > 0) { 356 return (CellLayoutChildren) getChildAt(0); 357 } 358 return null; 359 } 360 setIsDefaultDropTarget(boolean isDefaultDropTarget)361 public void setIsDefaultDropTarget(boolean isDefaultDropTarget) { 362 if (mIsDefaultDropTarget != isDefaultDropTarget) { 363 mIsDefaultDropTarget = isDefaultDropTarget; 364 invalidate(); 365 } 366 } 367 setIsDragOccuring(boolean isDragOccuring)368 void setIsDragOccuring(boolean isDragOccuring) { 369 if (mIsDragOccuring != isDragOccuring) { 370 mIsDragOccuring = isDragOccuring; 371 invalidate(); 372 } 373 } 374 setIsDragOverlapping(boolean isDragOverlapping)375 void setIsDragOverlapping(boolean isDragOverlapping) { 376 if (mIsDragOverlapping != isDragOverlapping) { 377 mIsDragOverlapping = isDragOverlapping; 378 invalidate(); 379 } 380 } 381 getIsDragOverlapping()382 boolean getIsDragOverlapping() { 383 return mIsDragOverlapping; 384 } 385 updateGlowRect()386 private void updateGlowRect() { 387 float marginFraction = (mGlowBackgroundScale - 1.0f) / 2.0f; 388 int marginX = (int) (marginFraction * (mBackgroundRect.right - mBackgroundRect.left)); 389 int marginY = (int) (marginFraction * (mBackgroundRect.bottom - mBackgroundRect.top)); 390 mGlowBackgroundRect.set(mBackgroundRect.left - marginX, mBackgroundRect.top - marginY, 391 mBackgroundRect.right + marginX, mBackgroundRect.bottom + marginY); 392 invalidate(); 393 } 394 setHoverScale(float scaleFactor)395 public void setHoverScale(float scaleFactor) { 396 if (scaleFactor != mGlowBackgroundScale) { 397 mGlowBackgroundScale = scaleFactor; 398 updateGlowRect(); 399 if (getParent() != null) { 400 ((View) getParent()).invalidate(); 401 } 402 } 403 } 404 getHoverScale()405 public float getHoverScale() { 406 return mGlowBackgroundScale; 407 } 408 getHoverAlpha()409 public float getHoverAlpha() { 410 return mGlowBackgroundAlpha; 411 } 412 setHoverAlpha(float alpha)413 public void setHoverAlpha(float alpha) { 414 mGlowBackgroundAlpha = alpha; 415 invalidate(); 416 } 417 animateDrop()418 void animateDrop() { 419 Resources res = getResources(); 420 float onDropScale = res.getInteger(R.integer.config_screenOnDropScalePercent) / 100.0f; 421 ObjectAnimator scaleUp = ObjectAnimator.ofFloat(this, "hoverScale", onDropScale); 422 scaleUp.setDuration(res.getInteger(R.integer.config_screenOnDropScaleUpDuration)); 423 ObjectAnimator scaleDown = ObjectAnimator.ofFloat(this, "hoverScale", 1.0f); 424 scaleDown.setDuration(res.getInteger(R.integer.config_screenOnDropScaleDownDuration)); 425 ObjectAnimator alphaFadeOut = ObjectAnimator.ofFloat(this, "hoverAlpha", 0.0f); 426 427 alphaFadeOut.setStartDelay(res.getInteger(R.integer.config_screenOnDropAlphaFadeDelay)); 428 alphaFadeOut.setDuration(res.getInteger(R.integer.config_screenOnDropAlphaFadeDuration)); 429 430 AnimatorSet bouncer = new AnimatorSet(); 431 bouncer.play(scaleUp).before(scaleDown); 432 bouncer.play(scaleUp).with(alphaFadeOut); 433 bouncer.addListener(new AnimatorListenerAdapter() { 434 @Override 435 public void onAnimationStart(Animator animation) { 436 setIsDragOverlapping(true); 437 } 438 @Override 439 public void onAnimationEnd(Animator animation) { 440 setIsDragOverlapping(false); 441 setHoverScale(1.0f); 442 setHoverAlpha(1.0f); 443 } 444 }); 445 bouncer.start(); 446 } 447 448 @Override onDraw(Canvas canvas)449 protected void onDraw(Canvas canvas) { 450 // When we're large, we are either drawn in a "hover" state (ie when dragging an item to 451 // a neighboring page) or with just a normal background (if backgroundAlpha > 0.0f) 452 // When we're small, we are either drawn normally or in the "accepts drops" state (during 453 // a drag). However, we also drag the mini hover background *over* one of those two 454 // backgrounds 455 if (mBackgroundAlpha > 0.0f) { 456 Drawable bg; 457 boolean mini = getScaleX() < 0.5f; 458 459 if (mIsDragOverlapping) { 460 // In the mini case, we draw the active_glow bg *over* the active background 461 bg = mini ? mActiveBackgroundMini : mActiveGlowBackground; 462 } else if (mIsDragOccuring && mAcceptsDrops) { 463 bg = mini ? mActiveBackgroundMini : mActiveBackground; 464 } else if (mIsDefaultDropTarget && mini) { 465 bg = mNormalGlowBackgroundMini; 466 } else { 467 bg = mini ? mNormalBackgroundMini : mNormalBackground; 468 } 469 470 bg.setAlpha((int) (mBackgroundAlpha * mBackgroundAlphaMultiplier * 255)); 471 bg.setBounds(mBackgroundRect); 472 bg.draw(canvas); 473 474 if (mini && mIsDragOverlapping) { 475 boolean modifiedClipRect = false; 476 if (mGlowBackgroundScale > 1.0f) { 477 // If the hover background's scale is greater than 1, we'll be drawing outside 478 // the bounds of this CellLayout. Get around that by temporarily increasing the 479 // size of the clip rect 480 float marginFraction = (mGlowBackgroundScale - 1.0f) / 2.0f; 481 Rect clipRect = canvas.getClipBounds(); 482 int marginX = (int) (marginFraction * (clipRect.right - clipRect.left)); 483 int marginY = (int) (marginFraction * (clipRect.bottom - clipRect.top)); 484 canvas.save(Canvas.CLIP_SAVE_FLAG); 485 canvas.clipRect(-marginX, -marginY, 486 getWidth() + marginX, getHeight() + marginY, Region.Op.REPLACE); 487 modifiedClipRect = true; 488 } 489 490 mActiveGlowBackgroundMini.setAlpha( 491 (int) (mBackgroundAlpha * mGlowBackgroundAlpha * 255)); 492 mActiveGlowBackgroundMini.setBounds(mGlowBackgroundRect); 493 mActiveGlowBackgroundMini.draw(canvas); 494 if (modifiedClipRect) { 495 canvas.restore(); 496 } 497 } 498 } 499 500 if (mCrosshairsVisibility > 0.0f) { 501 final int countX = mCountX; 502 final int countY = mCountY; 503 504 final float MAX_ALPHA = 0.4f; 505 final int MAX_VISIBLE_DISTANCE = 600; 506 final float DISTANCE_MULTIPLIER = 0.002f; 507 508 final Drawable d = mCrosshairsDrawable; 509 final int width = d.getIntrinsicWidth(); 510 final int height = d.getIntrinsicHeight(); 511 512 int x = getPaddingLeft() - (mWidthGap / 2) - (width / 2); 513 for (int col = 0; col <= countX; col++) { 514 int y = getPaddingTop() - (mHeightGap / 2) - (height / 2); 515 for (int row = 0; row <= countY; row++) { 516 mTmpPointF.set(x - mDragCenter.x, y - mDragCenter.y); 517 float dist = mTmpPointF.length(); 518 // Crosshairs further from the drag point are more faint 519 float alpha = Math.min(MAX_ALPHA, 520 DISTANCE_MULTIPLIER * (MAX_VISIBLE_DISTANCE - dist)); 521 if (alpha > 0.0f) { 522 d.setBounds(x, y, x + width, y + height); 523 d.setAlpha((int) (alpha * 255 * mCrosshairsVisibility)); 524 d.draw(canvas); 525 } 526 y += mCellHeight + mHeightGap; 527 } 528 x += mCellWidth + mWidthGap; 529 } 530 } 531 532 final Paint paint = mDragOutlinePaint; 533 for (int i = 0; i < mDragOutlines.length; i++) { 534 final float alpha = mDragOutlineAlphas[i]; 535 if (alpha > 0) { 536 final Point p = mDragOutlines[i]; 537 final Bitmap b = (Bitmap) mDragOutlineAnims[i].getTag(); 538 paint.setAlpha((int)(alpha + .5f)); 539 canvas.drawBitmap(b, p.x, p.y, paint); 540 } 541 } 542 543 // We draw the pressed or focused BubbleTextView's background in CellLayout because it 544 // requires an expanded clip rect (due to the glow's blur radius) 545 if (mPressedOrFocusedIcon != null) { 546 final int padding = mPressedOrFocusedIcon.getPressedOrFocusedBackgroundPadding(); 547 final Bitmap b = mPressedOrFocusedIcon.getPressedOrFocusedBackground(); 548 if (b != null) { 549 canvas.drawBitmap(b, 550 mPressedOrFocusedIcon.getLeft() + getPaddingLeft() - padding, 551 mPressedOrFocusedIcon.getTop() + getPaddingTop() - padding, 552 null); 553 } 554 } 555 556 // The folder outer / inner ring image(s) 557 for (int i = 0; i < mFolderOuterRings.size(); i++) { 558 FolderRingAnimator fra = mFolderOuterRings.get(i); 559 560 // Draw outer ring 561 Drawable d = FolderRingAnimator.sSharedOuterRingDrawable; 562 int width = (int) fra.getOuterRingSize(); 563 int height = width; 564 cellToPoint(fra.mCellX, fra.mCellY, mTempLocation); 565 566 int centerX = mTempLocation[0] + mCellWidth / 2; 567 int centerY = mTempLocation[1] + FolderRingAnimator.sPreviewSize / 2; 568 569 canvas.save(); 570 canvas.translate(centerX - width / 2, centerY - height / 2); 571 d.setBounds(0, 0, width, height); 572 d.draw(canvas); 573 canvas.restore(); 574 575 // Draw inner ring 576 d = FolderRingAnimator.sSharedInnerRingDrawable; 577 width = (int) fra.getInnerRingSize(); 578 height = width; 579 cellToPoint(fra.mCellX, fra.mCellY, mTempLocation); 580 581 centerX = mTempLocation[0] + mCellWidth / 2; 582 centerY = mTempLocation[1] + FolderRingAnimator.sPreviewSize / 2; 583 canvas.save(); 584 canvas.translate(centerX - width / 2, centerY - width / 2); 585 d.setBounds(0, 0, width, height); 586 d.draw(canvas); 587 canvas.restore(); 588 } 589 590 if (mFolderLeaveBehindCell[0] >= 0 && mFolderLeaveBehindCell[1] >= 0) { 591 Drawable d = FolderIcon.sSharedFolderLeaveBehind; 592 int width = d.getIntrinsicWidth(); 593 int height = d.getIntrinsicHeight(); 594 595 cellToPoint(mFolderLeaveBehindCell[0], mFolderLeaveBehindCell[1], mTempLocation); 596 int centerX = mTempLocation[0] + mCellWidth / 2; 597 int centerY = mTempLocation[1] + FolderRingAnimator.sPreviewSize / 2; 598 599 canvas.save(); 600 canvas.translate(centerX - width / 2, centerY - width / 2); 601 d.setBounds(0, 0, width, height); 602 d.draw(canvas); 603 canvas.restore(); 604 } 605 } 606 607 @Override dispatchDraw(Canvas canvas)608 protected void dispatchDraw(Canvas canvas) { 609 super.dispatchDraw(canvas); 610 if (mForegroundAlpha > 0) { 611 mOverScrollForegroundDrawable.setBounds(mForegroundRect); 612 Paint p = ((NinePatchDrawable) mOverScrollForegroundDrawable).getPaint(); 613 p.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD)); 614 mOverScrollForegroundDrawable.draw(canvas); 615 p.setXfermode(null); 616 } 617 } 618 showFolderAccept(FolderRingAnimator fra)619 public void showFolderAccept(FolderRingAnimator fra) { 620 mFolderOuterRings.add(fra); 621 } 622 hideFolderAccept(FolderRingAnimator fra)623 public void hideFolderAccept(FolderRingAnimator fra) { 624 if (mFolderOuterRings.contains(fra)) { 625 mFolderOuterRings.remove(fra); 626 } 627 invalidate(); 628 } 629 setFolderLeaveBehindCell(int x, int y)630 public void setFolderLeaveBehindCell(int x, int y) { 631 mFolderLeaveBehindCell[0] = x; 632 mFolderLeaveBehindCell[1] = y; 633 invalidate(); 634 } 635 clearFolderLeaveBehind()636 public void clearFolderLeaveBehind() { 637 mFolderLeaveBehindCell[0] = -1; 638 mFolderLeaveBehindCell[1] = -1; 639 invalidate(); 640 } 641 642 @Override shouldDelayChildPressedState()643 public boolean shouldDelayChildPressedState() { 644 return false; 645 } 646 647 @Override cancelLongPress()648 public void cancelLongPress() { 649 super.cancelLongPress(); 650 651 // Cancel long press for all children 652 final int count = getChildCount(); 653 for (int i = 0; i < count; i++) { 654 final View child = getChildAt(i); 655 child.cancelLongPress(); 656 } 657 } 658 setOnInterceptTouchListener(View.OnTouchListener listener)659 public void setOnInterceptTouchListener(View.OnTouchListener listener) { 660 mInterceptTouchListener = listener; 661 } 662 getCountX()663 int getCountX() { 664 return mCountX; 665 } 666 getCountY()667 int getCountY() { 668 return mCountY; 669 } 670 addViewToCellLayout( View child, int index, int childId, LayoutParams params, boolean markCells)671 public boolean addViewToCellLayout( 672 View child, int index, int childId, LayoutParams params, boolean markCells) { 673 final LayoutParams lp = params; 674 675 // Generate an id for each view, this assumes we have at most 256x256 cells 676 // per workspace screen 677 if (lp.cellX >= 0 && lp.cellX <= mCountX - 1 && lp.cellY >= 0 && lp.cellY <= mCountY - 1) { 678 // If the horizontal or vertical span is set to -1, it is taken to 679 // mean that it spans the extent of the CellLayout 680 if (lp.cellHSpan < 0) lp.cellHSpan = mCountX; 681 if (lp.cellVSpan < 0) lp.cellVSpan = mCountY; 682 683 child.setId(childId); 684 685 mChildren.addView(child, index, lp); 686 687 if (markCells) markCellsAsOccupiedForView(child); 688 689 return true; 690 } 691 return false; 692 } 693 setAcceptsDrops(boolean acceptsDrops)694 public void setAcceptsDrops(boolean acceptsDrops) { 695 if (mAcceptsDrops != acceptsDrops) { 696 mAcceptsDrops = acceptsDrops; 697 invalidate(); 698 } 699 } 700 701 @Override removeAllViews()702 public void removeAllViews() { 703 clearOccupiedCells(); 704 mChildren.removeAllViews(); 705 } 706 707 @Override removeAllViewsInLayout()708 public void removeAllViewsInLayout() { 709 if (mChildren.getChildCount() > 0) { 710 clearOccupiedCells(); 711 mChildren.removeAllViewsInLayout(); 712 } 713 } 714 removeViewWithoutMarkingCells(View view)715 public void removeViewWithoutMarkingCells(View view) { 716 mChildren.removeView(view); 717 } 718 719 @Override removeView(View view)720 public void removeView(View view) { 721 markCellsAsUnoccupiedForView(view); 722 mChildren.removeView(view); 723 } 724 725 @Override removeViewAt(int index)726 public void removeViewAt(int index) { 727 markCellsAsUnoccupiedForView(mChildren.getChildAt(index)); 728 mChildren.removeViewAt(index); 729 } 730 731 @Override removeViewInLayout(View view)732 public void removeViewInLayout(View view) { 733 markCellsAsUnoccupiedForView(view); 734 mChildren.removeViewInLayout(view); 735 } 736 737 @Override removeViews(int start, int count)738 public void removeViews(int start, int count) { 739 for (int i = start; i < start + count; i++) { 740 markCellsAsUnoccupiedForView(mChildren.getChildAt(i)); 741 } 742 mChildren.removeViews(start, count); 743 } 744 745 @Override removeViewsInLayout(int start, int count)746 public void removeViewsInLayout(int start, int count) { 747 for (int i = start; i < start + count; i++) { 748 markCellsAsUnoccupiedForView(mChildren.getChildAt(i)); 749 } 750 mChildren.removeViewsInLayout(start, count); 751 } 752 drawChildren(Canvas canvas)753 public void drawChildren(Canvas canvas) { 754 mChildren.draw(canvas); 755 } 756 buildChildrenLayer()757 void buildChildrenLayer() { 758 mChildren.buildLayer(); 759 } 760 761 @Override onAttachedToWindow()762 protected void onAttachedToWindow() { 763 super.onAttachedToWindow(); 764 mCellInfo.screen = ((ViewGroup) getParent()).indexOfChild(this); 765 } 766 setTagToCellInfoForPoint(int touchX, int touchY)767 public void setTagToCellInfoForPoint(int touchX, int touchY) { 768 final CellInfo cellInfo = mCellInfo; 769 final Rect frame = mRect; 770 final int x = touchX + mScrollX; 771 final int y = touchY + mScrollY; 772 final int count = mChildren.getChildCount(); 773 774 boolean found = false; 775 for (int i = count - 1; i >= 0; i--) { 776 final View child = mChildren.getChildAt(i); 777 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 778 779 if ((child.getVisibility() == VISIBLE || child.getAnimation() != null) && 780 lp.isLockedToGrid) { 781 child.getHitRect(frame); 782 783 // The child hit rect is relative to the CellLayoutChildren parent, so we need to 784 // offset that by this CellLayout's padding to test an (x,y) point that is relative 785 // to this view. 786 frame.offset(mPaddingLeft, mPaddingTop); 787 788 if (frame.contains(x, y)) { 789 cellInfo.cell = child; 790 cellInfo.cellX = lp.cellX; 791 cellInfo.cellY = lp.cellY; 792 cellInfo.spanX = lp.cellHSpan; 793 cellInfo.spanY = lp.cellVSpan; 794 found = true; 795 break; 796 } 797 } 798 } 799 800 mLastDownOnOccupiedCell = found; 801 802 if (!found) { 803 final int cellXY[] = mTmpXY; 804 pointToCellExact(x, y, cellXY); 805 806 cellInfo.cell = null; 807 cellInfo.cellX = cellXY[0]; 808 cellInfo.cellY = cellXY[1]; 809 cellInfo.spanX = 1; 810 cellInfo.spanY = 1; 811 } 812 setTag(cellInfo); 813 } 814 815 @Override onInterceptTouchEvent(MotionEvent ev)816 public boolean onInterceptTouchEvent(MotionEvent ev) { 817 // First we clear the tag to ensure that on every touch down we start with a fresh slate, 818 // even in the case where we return early. Not clearing here was causing bugs whereby on 819 // long-press we'd end up picking up an item from a previous drag operation. 820 final int action = ev.getAction(); 821 822 if (action == MotionEvent.ACTION_DOWN) { 823 clearTagCellInfo(); 824 } 825 826 if (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev)) { 827 return true; 828 } 829 830 if (action == MotionEvent.ACTION_DOWN) { 831 setTagToCellInfoForPoint((int) ev.getX(), (int) ev.getY()); 832 } 833 return false; 834 } 835 clearTagCellInfo()836 private void clearTagCellInfo() { 837 final CellInfo cellInfo = mCellInfo; 838 cellInfo.cell = null; 839 cellInfo.cellX = -1; 840 cellInfo.cellY = -1; 841 cellInfo.spanX = 0; 842 cellInfo.spanY = 0; 843 setTag(cellInfo); 844 } 845 getTag()846 public CellInfo getTag() { 847 return (CellInfo) super.getTag(); 848 } 849 850 /** 851 * Given a point, return the cell that strictly encloses that point 852 * @param x X coordinate of the point 853 * @param y Y coordinate of the point 854 * @param result Array of 2 ints to hold the x and y coordinate of the cell 855 */ pointToCellExact(int x, int y, int[] result)856 void pointToCellExact(int x, int y, int[] result) { 857 final int hStartPadding = getPaddingLeft(); 858 final int vStartPadding = getPaddingTop(); 859 860 result[0] = (x - hStartPadding) / (mCellWidth + mWidthGap); 861 result[1] = (y - vStartPadding) / (mCellHeight + mHeightGap); 862 863 final int xAxis = mCountX; 864 final int yAxis = mCountY; 865 866 if (result[0] < 0) result[0] = 0; 867 if (result[0] >= xAxis) result[0] = xAxis - 1; 868 if (result[1] < 0) result[1] = 0; 869 if (result[1] >= yAxis) result[1] = yAxis - 1; 870 } 871 872 /** 873 * Given a point, return the cell that most closely encloses that point 874 * @param x X coordinate of the point 875 * @param y Y coordinate of the point 876 * @param result Array of 2 ints to hold the x and y coordinate of the cell 877 */ pointToCellRounded(int x, int y, int[] result)878 void pointToCellRounded(int x, int y, int[] result) { 879 pointToCellExact(x + (mCellWidth / 2), y + (mCellHeight / 2), result); 880 } 881 882 /** 883 * Given a cell coordinate, return the point that represents the upper left corner of that cell 884 * 885 * @param cellX X coordinate of the cell 886 * @param cellY Y coordinate of the cell 887 * 888 * @param result Array of 2 ints to hold the x and y coordinate of the point 889 */ cellToPoint(int cellX, int cellY, int[] result)890 void cellToPoint(int cellX, int cellY, int[] result) { 891 final int hStartPadding = getPaddingLeft(); 892 final int vStartPadding = getPaddingTop(); 893 894 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap); 895 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap); 896 } 897 898 /** 899 * Given a cell coordinate, return the point that represents the upper left corner of that cell 900 * 901 * @param cellX X coordinate of the cell 902 * @param cellY Y coordinate of the cell 903 * 904 * @param result Array of 2 ints to hold the x and y coordinate of the point 905 */ cellToCenterPoint(int cellX, int cellY, int[] result)906 void cellToCenterPoint(int cellX, int cellY, int[] result) { 907 final int hStartPadding = getPaddingLeft(); 908 final int vStartPadding = getPaddingTop(); 909 910 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap) + mCellWidth / 2; 911 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap) + mCellHeight / 2; 912 } 913 getCellWidth()914 int getCellWidth() { 915 return mCellWidth; 916 } 917 getCellHeight()918 int getCellHeight() { 919 return mCellHeight; 920 } 921 getWidthGap()922 int getWidthGap() { 923 return mWidthGap; 924 } 925 getHeightGap()926 int getHeightGap() { 927 return mHeightGap; 928 } 929 getContentRect(Rect r)930 Rect getContentRect(Rect r) { 931 if (r == null) { 932 r = new Rect(); 933 } 934 int left = getPaddingLeft(); 935 int top = getPaddingTop(); 936 int right = left + getWidth() - mPaddingLeft - mPaddingRight; 937 int bottom = top + getHeight() - mPaddingTop - mPaddingBottom; 938 r.set(left, top, right, bottom); 939 return r; 940 } 941 942 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)943 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 944 // TODO: currently ignoring padding 945 946 int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); 947 int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); 948 949 int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); 950 int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); 951 952 if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) { 953 throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions"); 954 } 955 956 int numWidthGaps = mCountX - 1; 957 int numHeightGaps = mCountY - 1; 958 959 if (mOriginalWidthGap < 0 || mOriginalHeightGap < 0) { 960 int hSpace = widthSpecSize - mPaddingLeft - mPaddingRight; 961 int vSpace = heightSpecSize - mPaddingTop - mPaddingBottom; 962 int hFreeSpace = hSpace - (mCountX * mOriginalCellWidth); 963 int vFreeSpace = vSpace - (mCountY * mOriginalCellHeight); 964 mWidthGap = Math.min(mMaxGap, numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0); 965 mHeightGap = Math.min(mMaxGap,numHeightGaps > 0 ? (vFreeSpace / numHeightGaps) : 0); 966 mChildren.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap); 967 } else { 968 mWidthGap = mOriginalWidthGap; 969 mHeightGap = mOriginalHeightGap; 970 } 971 972 // Initial values correspond to widthSpecMode == MeasureSpec.EXACTLY 973 int newWidth = widthSpecSize; 974 int newHeight = heightSpecSize; 975 if (widthSpecMode == MeasureSpec.AT_MOST) { 976 newWidth = mPaddingLeft + mPaddingRight + (mCountX * mCellWidth) + 977 ((mCountX - 1) * mWidthGap); 978 newHeight = mPaddingTop + mPaddingBottom + (mCountY * mCellHeight) + 979 ((mCountY - 1) * mHeightGap); 980 setMeasuredDimension(newWidth, newHeight); 981 } 982 983 int count = getChildCount(); 984 for (int i = 0; i < count; i++) { 985 View child = getChildAt(i); 986 int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(newWidth - mPaddingLeft - 987 mPaddingRight, MeasureSpec.EXACTLY); 988 int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(newHeight - mPaddingTop - 989 mPaddingBottom, MeasureSpec.EXACTLY); 990 child.measure(childWidthMeasureSpec, childheightMeasureSpec); 991 } 992 setMeasuredDimension(newWidth, newHeight); 993 } 994 995 @Override onLayout(boolean changed, int l, int t, int r, int b)996 protected void onLayout(boolean changed, int l, int t, int r, int b) { 997 int count = getChildCount(); 998 for (int i = 0; i < count; i++) { 999 View child = getChildAt(i); 1000 child.layout(mPaddingLeft, mPaddingTop, 1001 r - l - mPaddingRight, b - t - mPaddingBottom); 1002 } 1003 } 1004 1005 @Override onSizeChanged(int w, int h, int oldw, int oldh)1006 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 1007 super.onSizeChanged(w, h, oldw, oldh); 1008 mBackgroundRect.set(0, 0, w, h); 1009 mForegroundRect.set(mForegroundPadding, mForegroundPadding, 1010 w - 2 * mForegroundPadding, h - 2 * mForegroundPadding); 1011 updateGlowRect(); 1012 } 1013 1014 @Override setChildrenDrawingCacheEnabled(boolean enabled)1015 protected void setChildrenDrawingCacheEnabled(boolean enabled) { 1016 mChildren.setChildrenDrawingCacheEnabled(enabled); 1017 } 1018 1019 @Override setChildrenDrawnWithCacheEnabled(boolean enabled)1020 protected void setChildrenDrawnWithCacheEnabled(boolean enabled) { 1021 mChildren.setChildrenDrawnWithCacheEnabled(enabled); 1022 } 1023 getBackgroundAlpha()1024 public float getBackgroundAlpha() { 1025 return mBackgroundAlpha; 1026 } 1027 setFastBackgroundAlpha(float alpha)1028 public void setFastBackgroundAlpha(float alpha) { 1029 mBackgroundAlpha = alpha; 1030 } 1031 setBackgroundAlphaMultiplier(float multiplier)1032 public void setBackgroundAlphaMultiplier(float multiplier) { 1033 mBackgroundAlphaMultiplier = multiplier; 1034 } 1035 getBackgroundAlphaMultiplier()1036 public float getBackgroundAlphaMultiplier() { 1037 return mBackgroundAlphaMultiplier; 1038 } 1039 setBackgroundAlpha(float alpha)1040 public void setBackgroundAlpha(float alpha) { 1041 mBackgroundAlpha = alpha; 1042 invalidate(); 1043 } 1044 1045 // Need to return true to let the view system know we know how to handle alpha-- this is 1046 // because when our children have an alpha of 0.0f, they are still rendering their "dimmed" 1047 // versions 1048 @Override onSetAlpha(int alpha)1049 protected boolean onSetAlpha(int alpha) { 1050 return true; 1051 } 1052 setAlpha(float alpha)1053 public void setAlpha(float alpha) { 1054 setChildrenAlpha(alpha); 1055 super.setAlpha(alpha); 1056 } 1057 setFastAlpha(float alpha)1058 public void setFastAlpha(float alpha) { 1059 setFastChildrenAlpha(alpha); 1060 super.setFastAlpha(alpha); 1061 } 1062 setChildrenAlpha(float alpha)1063 private void setChildrenAlpha(float alpha) { 1064 final int childCount = getChildCount(); 1065 for (int i = 0; i < childCount; i++) { 1066 getChildAt(i).setAlpha(alpha); 1067 } 1068 } 1069 setFastChildrenAlpha(float alpha)1070 private void setFastChildrenAlpha(float alpha) { 1071 final int childCount = getChildCount(); 1072 for (int i = 0; i < childCount; i++) { 1073 getChildAt(i).setFastAlpha(alpha); 1074 } 1075 } 1076 getChildAt(int x, int y)1077 public View getChildAt(int x, int y) { 1078 return mChildren.getChildAt(x, y); 1079 } 1080 animateChildToPosition(final View child, int cellX, int cellY, int duration, int delay)1081 public boolean animateChildToPosition(final View child, int cellX, int cellY, int duration, 1082 int delay) { 1083 CellLayoutChildren clc = getChildrenLayout(); 1084 if (clc.indexOfChild(child) != -1 && !mOccupied[cellX][cellY]) { 1085 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 1086 final ItemInfo info = (ItemInfo) child.getTag(); 1087 1088 // We cancel any existing animations 1089 if (mReorderAnimators.containsKey(lp)) { 1090 mReorderAnimators.get(lp).cancel(); 1091 mReorderAnimators.remove(lp); 1092 } 1093 1094 int oldX = lp.x; 1095 int oldY = lp.y; 1096 mOccupied[lp.cellX][lp.cellY] = false; 1097 mOccupied[cellX][cellY] = true; 1098 1099 lp.isLockedToGrid = true; 1100 lp.cellX = info.cellX = cellX; 1101 lp.cellY = info.cellY = cellY; 1102 clc.setupLp(lp); 1103 lp.isLockedToGrid = false; 1104 int newX = lp.x; 1105 int newY = lp.y; 1106 1107 lp.x = oldX; 1108 lp.y = oldY; 1109 child.requestLayout(); 1110 1111 PropertyValuesHolder x = PropertyValuesHolder.ofInt("x", oldX, newX); 1112 PropertyValuesHolder y = PropertyValuesHolder.ofInt("y", oldY, newY); 1113 ObjectAnimator oa = ObjectAnimator.ofPropertyValuesHolder(lp, x, y); 1114 oa.setDuration(duration); 1115 mReorderAnimators.put(lp, oa); 1116 oa.addUpdateListener(new AnimatorUpdateListener() { 1117 public void onAnimationUpdate(ValueAnimator animation) { 1118 child.requestLayout(); 1119 } 1120 }); 1121 oa.addListener(new AnimatorListenerAdapter() { 1122 boolean cancelled = false; 1123 public void onAnimationEnd(Animator animation) { 1124 // If the animation was cancelled, it means that another animation 1125 // has interrupted this one, and we don't want to lock the item into 1126 // place just yet. 1127 if (!cancelled) { 1128 lp.isLockedToGrid = true; 1129 } 1130 if (mReorderAnimators.containsKey(lp)) { 1131 mReorderAnimators.remove(lp); 1132 } 1133 } 1134 public void onAnimationCancel(Animator animation) { 1135 cancelled = true; 1136 } 1137 }); 1138 oa.setStartDelay(delay); 1139 oa.start(); 1140 return true; 1141 } 1142 return false; 1143 } 1144 1145 /** 1146 * Estimate where the top left cell of the dragged item will land if it is dropped. 1147 * 1148 * @param originX The X value of the top left corner of the item 1149 * @param originY The Y value of the top left corner of the item 1150 * @param spanX The number of horizontal cells that the item spans 1151 * @param spanY The number of vertical cells that the item spans 1152 * @param result The estimated drop cell X and Y. 1153 */ estimateDropCell(int originX, int originY, int spanX, int spanY, int[] result)1154 void estimateDropCell(int originX, int originY, int spanX, int spanY, int[] result) { 1155 final int countX = mCountX; 1156 final int countY = mCountY; 1157 1158 // pointToCellRounded takes the top left of a cell but will pad that with 1159 // cellWidth/2 and cellHeight/2 when finding the matching cell 1160 pointToCellRounded(originX, originY, result); 1161 1162 // If the item isn't fully on this screen, snap to the edges 1163 int rightOverhang = result[0] + spanX - countX; 1164 if (rightOverhang > 0) { 1165 result[0] -= rightOverhang; // Snap to right 1166 } 1167 result[0] = Math.max(0, result[0]); // Snap to left 1168 int bottomOverhang = result[1] + spanY - countY; 1169 if (bottomOverhang > 0) { 1170 result[1] -= bottomOverhang; // Snap to bottom 1171 } 1172 result[1] = Math.max(0, result[1]); // Snap to top 1173 } 1174 visualizeDropLocation(View v, Bitmap dragOutline, int originX, int originY, int spanX, int spanY, Point dragOffset, Rect dragRegion)1175 void visualizeDropLocation(View v, Bitmap dragOutline, int originX, int originY, 1176 int spanX, int spanY, Point dragOffset, Rect dragRegion) { 1177 1178 final int oldDragCellX = mDragCell[0]; 1179 final int oldDragCellY = mDragCell[1]; 1180 final int[] nearest = findNearestVacantArea(originX, originY, spanX, spanY, v, mDragCell); 1181 if (v != null && dragOffset == null) { 1182 mDragCenter.set(originX + (v.getWidth() / 2), originY + (v.getHeight() / 2)); 1183 } else { 1184 mDragCenter.set(originX, originY); 1185 } 1186 1187 if (dragOutline == null && v == null) { 1188 if (mCrosshairsDrawable != null) { 1189 invalidate(); 1190 } 1191 return; 1192 } 1193 1194 if (nearest != null && (nearest[0] != oldDragCellX || nearest[1] != oldDragCellY)) { 1195 // Find the top left corner of the rect the object will occupy 1196 final int[] topLeft = mTmpPoint; 1197 cellToPoint(nearest[0], nearest[1], topLeft); 1198 1199 int left = topLeft[0]; 1200 int top = topLeft[1]; 1201 1202 if (v != null && dragOffset == null) { 1203 // When drawing the drag outline, it did not account for margin offsets 1204 // added by the view's parent. 1205 MarginLayoutParams lp = (MarginLayoutParams) v.getLayoutParams(); 1206 left += lp.leftMargin; 1207 top += lp.topMargin; 1208 1209 // Offsets due to the size difference between the View and the dragOutline. 1210 // There is a size difference to account for the outer blur, which may lie 1211 // outside the bounds of the view. 1212 top += (v.getHeight() - dragOutline.getHeight()) / 2; 1213 // We center about the x axis 1214 left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap) 1215 - dragOutline.getWidth()) / 2; 1216 } else { 1217 if (dragOffset != null && dragRegion != null) { 1218 // Center the drag region *horizontally* in the cell and apply a drag 1219 // outline offset 1220 left += dragOffset.x + ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap) 1221 - dragRegion.width()) / 2; 1222 top += dragOffset.y; 1223 } else { 1224 // Center the drag outline in the cell 1225 left += ((mCellWidth * spanX) + ((spanX - 1) * mWidthGap) 1226 - dragOutline.getWidth()) / 2; 1227 top += ((mCellHeight * spanY) + ((spanY - 1) * mHeightGap) 1228 - dragOutline.getHeight()) / 2; 1229 } 1230 } 1231 1232 final int oldIndex = mDragOutlineCurrent; 1233 mDragOutlineAnims[oldIndex].animateOut(); 1234 mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length; 1235 1236 mDragOutlines[mDragOutlineCurrent].set(left, top); 1237 mDragOutlineAnims[mDragOutlineCurrent].setTag(dragOutline); 1238 mDragOutlineAnims[mDragOutlineCurrent].animateIn(); 1239 } 1240 1241 // If we are drawing crosshairs, the entire CellLayout needs to be invalidated 1242 if (mCrosshairsDrawable != null) { 1243 invalidate(); 1244 } 1245 } 1246 clearDragOutlines()1247 public void clearDragOutlines() { 1248 final int oldIndex = mDragOutlineCurrent; 1249 mDragOutlineAnims[oldIndex].animateOut(); 1250 mDragCell[0] = -1; 1251 mDragCell[1] = -1; 1252 } 1253 1254 /** 1255 * Find a vacant area that will fit the given bounds nearest the requested 1256 * cell location. Uses Euclidean distance to score multiple vacant areas. 1257 * 1258 * @param pixelX The X location at which you want to search for a vacant area. 1259 * @param pixelY The Y location at which you want to search for a vacant area. 1260 * @param spanX Horizontal span of the object. 1261 * @param spanY Vertical span of the object. 1262 * @param result Array in which to place the result, or null (in which case a new array will 1263 * be allocated) 1264 * @return The X, Y cell of a vacant area that can contain this object, 1265 * nearest the requested location. 1266 */ findNearestVacantArea( int pixelX, int pixelY, int spanX, int spanY, int[] result)1267 int[] findNearestVacantArea( 1268 int pixelX, int pixelY, int spanX, int spanY, int[] result) { 1269 return findNearestVacantArea(pixelX, pixelY, spanX, spanY, null, result); 1270 } 1271 1272 /** 1273 * Find a vacant area that will fit the given bounds nearest the requested 1274 * cell location. Uses Euclidean distance to score multiple vacant areas. 1275 * 1276 * @param pixelX The X location at which you want to search for a vacant area. 1277 * @param pixelY The Y location at which you want to search for a vacant area. 1278 * @param spanX Horizontal span of the object. 1279 * @param spanY Vertical span of the object. 1280 * @param ignoreOccupied If true, the result can be an occupied cell 1281 * @param result Array in which to place the result, or null (in which case a new array will 1282 * be allocated) 1283 * @return The X, Y cell of a vacant area that can contain this object, 1284 * nearest the requested location. 1285 */ findNearestArea(int pixelX, int pixelY, int spanX, int spanY, View ignoreView, boolean ignoreOccupied, int[] result)1286 int[] findNearestArea(int pixelX, int pixelY, int spanX, int spanY, View ignoreView, 1287 boolean ignoreOccupied, int[] result) { 1288 // mark space take by ignoreView as available (method checks if ignoreView is null) 1289 markCellsAsUnoccupiedForView(ignoreView); 1290 1291 // For items with a spanX / spanY > 1, the passed in point (pixelX, pixelY) corresponds 1292 // to the center of the item, but we are searching based on the top-left cell, so 1293 // we translate the point over to correspond to the top-left. 1294 pixelX -= (mCellWidth + mWidthGap) * (spanX - 1) / 2f; 1295 pixelY -= (mCellHeight + mHeightGap) * (spanY - 1) / 2f; 1296 1297 // Keep track of best-scoring drop area 1298 final int[] bestXY = result != null ? result : new int[2]; 1299 double bestDistance = Double.MAX_VALUE; 1300 1301 final int countX = mCountX; 1302 final int countY = mCountY; 1303 final boolean[][] occupied = mOccupied; 1304 1305 for (int y = 0; y < countY - (spanY - 1); y++) { 1306 inner: 1307 for (int x = 0; x < countX - (spanX - 1); x++) { 1308 if (ignoreOccupied) { 1309 for (int i = 0; i < spanX; i++) { 1310 for (int j = 0; j < spanY; j++) { 1311 if (occupied[x + i][y + j]) { 1312 // small optimization: we can skip to after the column we 1313 // just found an occupied cell 1314 x += i; 1315 continue inner; 1316 } 1317 } 1318 } 1319 } 1320 final int[] cellXY = mTmpXY; 1321 cellToCenterPoint(x, y, cellXY); 1322 1323 double distance = Math.sqrt(Math.pow(cellXY[0] - pixelX, 2) 1324 + Math.pow(cellXY[1] - pixelY, 2)); 1325 if (distance <= bestDistance) { 1326 bestDistance = distance; 1327 bestXY[0] = x; 1328 bestXY[1] = y; 1329 } 1330 } 1331 } 1332 // re-mark space taken by ignoreView as occupied 1333 markCellsAsOccupiedForView(ignoreView); 1334 1335 // Return -1, -1 if no suitable location found 1336 if (bestDistance == Double.MAX_VALUE) { 1337 bestXY[0] = -1; 1338 bestXY[1] = -1; 1339 } 1340 return bestXY; 1341 } 1342 1343 /** 1344 * Find a vacant area that will fit the given bounds nearest the requested 1345 * cell location. Uses Euclidean distance to score multiple vacant areas. 1346 * 1347 * @param pixelX The X location at which you want to search for a vacant area. 1348 * @param pixelY The Y location at which you want to search for a vacant area. 1349 * @param spanX Horizontal span of the object. 1350 * @param spanY Vertical span of the object. 1351 * @param ignoreView Considers space occupied by this view as unoccupied 1352 * @param result Previously returned value to possibly recycle. 1353 * @return The X, Y cell of a vacant area that can contain this object, 1354 * nearest the requested location. 1355 */ findNearestVacantArea( int pixelX, int pixelY, int spanX, int spanY, View ignoreView, int[] result)1356 int[] findNearestVacantArea( 1357 int pixelX, int pixelY, int spanX, int spanY, View ignoreView, int[] result) { 1358 return findNearestArea(pixelX, pixelY, spanX, spanY, ignoreView, true, result); 1359 } 1360 1361 /** 1362 * Find a starting cell position that will fit the given bounds nearest the requested 1363 * cell location. Uses Euclidean distance to score multiple vacant areas. 1364 * 1365 * @param pixelX The X location at which you want to search for a vacant area. 1366 * @param pixelY The Y location at which you want to search for a vacant area. 1367 * @param spanX Horizontal span of the object. 1368 * @param spanY Vertical span of the object. 1369 * @param ignoreView Considers space occupied by this view as unoccupied 1370 * @param result Previously returned value to possibly recycle. 1371 * @return The X, Y cell of a vacant area that can contain this object, 1372 * nearest the requested location. 1373 */ findNearestArea( int pixelX, int pixelY, int spanX, int spanY, int[] result)1374 int[] findNearestArea( 1375 int pixelX, int pixelY, int spanX, int spanY, int[] result) { 1376 return findNearestArea(pixelX, pixelY, spanX, spanY, null, false, result); 1377 } 1378 existsEmptyCell()1379 boolean existsEmptyCell() { 1380 return findCellForSpan(null, 1, 1); 1381 } 1382 1383 /** 1384 * Finds the upper-left coordinate of the first rectangle in the grid that can 1385 * hold a cell of the specified dimensions. If intersectX and intersectY are not -1, 1386 * then this method will only return coordinates for rectangles that contain the cell 1387 * (intersectX, intersectY) 1388 * 1389 * @param cellXY The array that will contain the position of a vacant cell if such a cell 1390 * can be found. 1391 * @param spanX The horizontal span of the cell we want to find. 1392 * @param spanY The vertical span of the cell we want to find. 1393 * 1394 * @return True if a vacant cell of the specified dimension was found, false otherwise. 1395 */ findCellForSpan(int[] cellXY, int spanX, int spanY)1396 boolean findCellForSpan(int[] cellXY, int spanX, int spanY) { 1397 return findCellForSpanThatIntersectsIgnoring(cellXY, spanX, spanY, -1, -1, null); 1398 } 1399 1400 /** 1401 * Like above, but ignores any cells occupied by the item "ignoreView" 1402 * 1403 * @param cellXY The array that will contain the position of a vacant cell if such a cell 1404 * can be found. 1405 * @param spanX The horizontal span of the cell we want to find. 1406 * @param spanY The vertical span of the cell we want to find. 1407 * @param ignoreView The home screen item we should treat as not occupying any space 1408 * @return 1409 */ findCellForSpanIgnoring(int[] cellXY, int spanX, int spanY, View ignoreView)1410 boolean findCellForSpanIgnoring(int[] cellXY, int spanX, int spanY, View ignoreView) { 1411 return findCellForSpanThatIntersectsIgnoring(cellXY, spanX, spanY, -1, -1, ignoreView); 1412 } 1413 1414 /** 1415 * Like above, but if intersectX and intersectY are not -1, then this method will try to 1416 * return coordinates for rectangles that contain the cell [intersectX, intersectY] 1417 * 1418 * @param spanX The horizontal span of the cell we want to find. 1419 * @param spanY The vertical span of the cell we want to find. 1420 * @param ignoreView The home screen item we should treat as not occupying any space 1421 * @param intersectX The X coordinate of the cell that we should try to overlap 1422 * @param intersectX The Y coordinate of the cell that we should try to overlap 1423 * 1424 * @return True if a vacant cell of the specified dimension was found, false otherwise. 1425 */ findCellForSpanThatIntersects(int[] cellXY, int spanX, int spanY, int intersectX, int intersectY)1426 boolean findCellForSpanThatIntersects(int[] cellXY, int spanX, int spanY, 1427 int intersectX, int intersectY) { 1428 return findCellForSpanThatIntersectsIgnoring( 1429 cellXY, spanX, spanY, intersectX, intersectY, null); 1430 } 1431 1432 /** 1433 * The superset of the above two methods 1434 */ findCellForSpanThatIntersectsIgnoring(int[] cellXY, int spanX, int spanY, int intersectX, int intersectY, View ignoreView)1435 boolean findCellForSpanThatIntersectsIgnoring(int[] cellXY, int spanX, int spanY, 1436 int intersectX, int intersectY, View ignoreView) { 1437 // mark space take by ignoreView as available (method checks if ignoreView is null) 1438 markCellsAsUnoccupiedForView(ignoreView); 1439 1440 boolean foundCell = false; 1441 while (true) { 1442 int startX = 0; 1443 if (intersectX >= 0) { 1444 startX = Math.max(startX, intersectX - (spanX - 1)); 1445 } 1446 int endX = mCountX - (spanX - 1); 1447 if (intersectX >= 0) { 1448 endX = Math.min(endX, intersectX + (spanX - 1) + (spanX == 1 ? 1 : 0)); 1449 } 1450 int startY = 0; 1451 if (intersectY >= 0) { 1452 startY = Math.max(startY, intersectY - (spanY - 1)); 1453 } 1454 int endY = mCountY - (spanY - 1); 1455 if (intersectY >= 0) { 1456 endY = Math.min(endY, intersectY + (spanY - 1) + (spanY == 1 ? 1 : 0)); 1457 } 1458 1459 for (int y = startY; y < endY && !foundCell; y++) { 1460 inner: 1461 for (int x = startX; x < endX; x++) { 1462 for (int i = 0; i < spanX; i++) { 1463 for (int j = 0; j < spanY; j++) { 1464 if (mOccupied[x + i][y + j]) { 1465 // small optimization: we can skip to after the column we just found 1466 // an occupied cell 1467 x += i; 1468 continue inner; 1469 } 1470 } 1471 } 1472 if (cellXY != null) { 1473 cellXY[0] = x; 1474 cellXY[1] = y; 1475 } 1476 foundCell = true; 1477 break; 1478 } 1479 } 1480 if (intersectX == -1 && intersectY == -1) { 1481 break; 1482 } else { 1483 // if we failed to find anything, try again but without any requirements of 1484 // intersecting 1485 intersectX = -1; 1486 intersectY = -1; 1487 continue; 1488 } 1489 } 1490 1491 // re-mark space taken by ignoreView as occupied 1492 markCellsAsOccupiedForView(ignoreView); 1493 return foundCell; 1494 } 1495 1496 /** 1497 * A drag event has begun over this layout. 1498 * It may have begun over this layout (in which case onDragChild is called first), 1499 * or it may have begun on another layout. 1500 */ onDragEnter()1501 void onDragEnter() { 1502 if (!mDragging) { 1503 // Fade in the drag indicators 1504 if (mCrosshairsAnimator != null) { 1505 mCrosshairsAnimator.animateIn(); 1506 } 1507 } 1508 mDragging = true; 1509 } 1510 1511 /** 1512 * Called when drag has left this CellLayout or has been completed (successfully or not) 1513 */ onDragExit()1514 void onDragExit() { 1515 // This can actually be called when we aren't in a drag, e.g. when adding a new 1516 // item to this layout via the customize drawer. 1517 // Guard against that case. 1518 if (mDragging) { 1519 mDragging = false; 1520 1521 // Fade out the drag indicators 1522 if (mCrosshairsAnimator != null) { 1523 mCrosshairsAnimator.animateOut(); 1524 } 1525 } 1526 1527 // Invalidate the drag data 1528 mDragCell[0] = -1; 1529 mDragCell[1] = -1; 1530 mDragOutlineAnims[mDragOutlineCurrent].animateOut(); 1531 mDragOutlineCurrent = (mDragOutlineCurrent + 1) % mDragOutlineAnims.length; 1532 1533 setIsDragOverlapping(false); 1534 } 1535 1536 /** 1537 * Mark a child as having been dropped. 1538 * At the beginning of the drag operation, the child may have been on another 1539 * screen, but it is re-parented before this method is called. 1540 * 1541 * @param child The child that is being dropped 1542 */ onDropChild(View child)1543 void onDropChild(View child) { 1544 if (child != null) { 1545 LayoutParams lp = (LayoutParams) child.getLayoutParams(); 1546 lp.dropped = true; 1547 child.requestLayout(); 1548 } 1549 } 1550 1551 /** 1552 * Computes a bounding rectangle for a range of cells 1553 * 1554 * @param cellX X coordinate of upper left corner expressed as a cell position 1555 * @param cellY Y coordinate of upper left corner expressed as a cell position 1556 * @param cellHSpan Width in cells 1557 * @param cellVSpan Height in cells 1558 * @param resultRect Rect into which to put the results 1559 */ cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, RectF resultRect)1560 public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, RectF resultRect) { 1561 final int cellWidth = mCellWidth; 1562 final int cellHeight = mCellHeight; 1563 final int widthGap = mWidthGap; 1564 final int heightGap = mHeightGap; 1565 1566 final int hStartPadding = getPaddingLeft(); 1567 final int vStartPadding = getPaddingTop(); 1568 1569 int width = cellHSpan * cellWidth + ((cellHSpan - 1) * widthGap); 1570 int height = cellVSpan * cellHeight + ((cellVSpan - 1) * heightGap); 1571 1572 int x = hStartPadding + cellX * (cellWidth + widthGap); 1573 int y = vStartPadding + cellY * (cellHeight + heightGap); 1574 1575 resultRect.set(x, y, x + width, y + height); 1576 } 1577 1578 /** 1579 * Computes the required horizontal and vertical cell spans to always 1580 * fit the given rectangle. 1581 * 1582 * @param width Width in pixels 1583 * @param height Height in pixels 1584 * @param result An array of length 2 in which to store the result (may be null). 1585 */ rectToCell(int width, int height, int[] result)1586 public int[] rectToCell(int width, int height, int[] result) { 1587 return rectToCell(getResources(), width, height, result); 1588 } 1589 rectToCell(Resources resources, int width, int height, int[] result)1590 public static int[] rectToCell(Resources resources, int width, int height, int[] result) { 1591 // Always assume we're working with the smallest span to make sure we 1592 // reserve enough space in both orientations. 1593 int actualWidth = resources.getDimensionPixelSize(R.dimen.workspace_cell_width); 1594 int actualHeight = resources.getDimensionPixelSize(R.dimen.workspace_cell_height); 1595 int smallerSize = Math.min(actualWidth, actualHeight); 1596 1597 // Always round up to next largest cell 1598 int spanX = (int) Math.ceil(width / (float) smallerSize); 1599 int spanY = (int) Math.ceil(height / (float) smallerSize); 1600 1601 if (result == null) { 1602 return new int[] { spanX, spanY }; 1603 } 1604 result[0] = spanX; 1605 result[1] = spanY; 1606 return result; 1607 } 1608 cellSpansToSize(int hSpans, int vSpans)1609 public int[] cellSpansToSize(int hSpans, int vSpans) { 1610 int[] size = new int[2]; 1611 size[0] = hSpans * mCellWidth + (hSpans - 1) * mWidthGap; 1612 size[1] = vSpans * mCellHeight + (vSpans - 1) * mHeightGap; 1613 return size; 1614 } 1615 1616 /** 1617 * Calculate the grid spans needed to fit given item 1618 */ calculateSpans(ItemInfo info)1619 public void calculateSpans(ItemInfo info) { 1620 final int minWidth; 1621 final int minHeight; 1622 1623 if (info instanceof LauncherAppWidgetInfo) { 1624 minWidth = ((LauncherAppWidgetInfo) info).minWidth; 1625 minHeight = ((LauncherAppWidgetInfo) info).minHeight; 1626 } else if (info instanceof PendingAddWidgetInfo) { 1627 minWidth = ((PendingAddWidgetInfo) info).minWidth; 1628 minHeight = ((PendingAddWidgetInfo) info).minHeight; 1629 } else { 1630 // It's not a widget, so it must be 1x1 1631 info.spanX = info.spanY = 1; 1632 return; 1633 } 1634 int[] spans = rectToCell(minWidth, minHeight, null); 1635 info.spanX = spans[0]; 1636 info.spanY = spans[1]; 1637 } 1638 1639 /** 1640 * Find the first vacant cell, if there is one. 1641 * 1642 * @param vacant Holds the x and y coordinate of the vacant cell 1643 * @param spanX Horizontal cell span. 1644 * @param spanY Vertical cell span. 1645 * 1646 * @return True if a vacant cell was found 1647 */ getVacantCell(int[] vacant, int spanX, int spanY)1648 public boolean getVacantCell(int[] vacant, int spanX, int spanY) { 1649 1650 return findVacantCell(vacant, spanX, spanY, mCountX, mCountY, mOccupied); 1651 } 1652 findVacantCell(int[] vacant, int spanX, int spanY, int xCount, int yCount, boolean[][] occupied)1653 static boolean findVacantCell(int[] vacant, int spanX, int spanY, 1654 int xCount, int yCount, boolean[][] occupied) { 1655 1656 for (int y = 0; y < yCount; y++) { 1657 for (int x = 0; x < xCount; x++) { 1658 boolean available = !occupied[x][y]; 1659 out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) { 1660 for (int j = y; j < y + spanY - 1 && y < yCount; j++) { 1661 available = available && !occupied[i][j]; 1662 if (!available) break out; 1663 } 1664 } 1665 1666 if (available) { 1667 vacant[0] = x; 1668 vacant[1] = y; 1669 return true; 1670 } 1671 } 1672 } 1673 1674 return false; 1675 } 1676 clearOccupiedCells()1677 private void clearOccupiedCells() { 1678 for (int x = 0; x < mCountX; x++) { 1679 for (int y = 0; y < mCountY; y++) { 1680 mOccupied[x][y] = false; 1681 } 1682 } 1683 } 1684 1685 /** 1686 * Given a view, determines how much that view can be expanded in all directions, in terms of 1687 * whether or not there are other items occupying adjacent cells. Used by the 1688 * AppWidgetResizeFrame to determine how the widget can be resized. 1689 */ getExpandabilityArrayForView(View view, int[] expandability)1690 public void getExpandabilityArrayForView(View view, int[] expandability) { 1691 final LayoutParams lp = (LayoutParams) view.getLayoutParams(); 1692 boolean flag; 1693 1694 expandability[AppWidgetResizeFrame.LEFT] = 0; 1695 for (int x = lp.cellX - 1; x >= 0; x--) { 1696 flag = false; 1697 for (int y = lp.cellY; y < lp.cellY + lp.cellVSpan; y++) { 1698 if (mOccupied[x][y]) flag = true; 1699 } 1700 if (flag) break; 1701 expandability[AppWidgetResizeFrame.LEFT]++; 1702 } 1703 1704 expandability[AppWidgetResizeFrame.TOP] = 0; 1705 for (int y = lp.cellY - 1; y >= 0; y--) { 1706 flag = false; 1707 for (int x = lp.cellX; x < lp.cellX + lp.cellHSpan; x++) { 1708 if (mOccupied[x][y]) flag = true; 1709 } 1710 if (flag) break; 1711 expandability[AppWidgetResizeFrame.TOP]++; 1712 } 1713 1714 expandability[AppWidgetResizeFrame.RIGHT] = 0; 1715 for (int x = lp.cellX + lp.cellHSpan; x < mCountX; x++) { 1716 flag = false; 1717 for (int y = lp.cellY; y < lp.cellY + lp.cellVSpan; y++) { 1718 if (mOccupied[x][y]) flag = true; 1719 } 1720 if (flag) break; 1721 expandability[AppWidgetResizeFrame.RIGHT]++; 1722 } 1723 1724 expandability[AppWidgetResizeFrame.BOTTOM] = 0; 1725 for (int y = lp.cellY + lp.cellVSpan; y < mCountY; y++) { 1726 flag = false; 1727 for (int x = lp.cellX; x < lp.cellX + lp.cellHSpan; x++) { 1728 if (mOccupied[x][y]) flag = true; 1729 } 1730 if (flag) break; 1731 expandability[AppWidgetResizeFrame.BOTTOM]++; 1732 } 1733 } 1734 onMove(View view, int newCellX, int newCellY)1735 public void onMove(View view, int newCellX, int newCellY) { 1736 LayoutParams lp = (LayoutParams) view.getLayoutParams(); 1737 markCellsAsUnoccupiedForView(view); 1738 markCellsForView(newCellX, newCellY, lp.cellHSpan, lp.cellVSpan, true); 1739 } 1740 markCellsAsOccupiedForView(View view)1741 public void markCellsAsOccupiedForView(View view) { 1742 if (view == null || view.getParent() != mChildren) return; 1743 LayoutParams lp = (LayoutParams) view.getLayoutParams(); 1744 markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, true); 1745 } 1746 markCellsAsUnoccupiedForView(View view)1747 public void markCellsAsUnoccupiedForView(View view) { 1748 if (view == null || view.getParent() != mChildren) return; 1749 LayoutParams lp = (LayoutParams) view.getLayoutParams(); 1750 markCellsForView(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, false); 1751 } 1752 markCellsForView(int cellX, int cellY, int spanX, int spanY, boolean value)1753 private void markCellsForView(int cellX, int cellY, int spanX, int spanY, boolean value) { 1754 for (int x = cellX; x < cellX + spanX && x < mCountX; x++) { 1755 for (int y = cellY; y < cellY + spanY && y < mCountY; y++) { 1756 mOccupied[x][y] = value; 1757 } 1758 } 1759 } 1760 getDesiredWidth()1761 public int getDesiredWidth() { 1762 return mPaddingLeft + mPaddingRight + (mCountX * mCellWidth) + 1763 (Math.max((mCountX - 1), 0) * mWidthGap); 1764 } 1765 getDesiredHeight()1766 public int getDesiredHeight() { 1767 return mPaddingTop + mPaddingBottom + (mCountY * mCellHeight) + 1768 (Math.max((mCountY - 1), 0) * mHeightGap); 1769 } 1770 isOccupied(int x, int y)1771 public boolean isOccupied(int x, int y) { 1772 if (x < mCountX && y < mCountY) { 1773 return mOccupied[x][y]; 1774 } else { 1775 throw new RuntimeException("Position exceeds the bound of this CellLayout"); 1776 } 1777 } 1778 1779 @Override generateLayoutParams(AttributeSet attrs)1780 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { 1781 return new CellLayout.LayoutParams(getContext(), attrs); 1782 } 1783 1784 @Override checkLayoutParams(ViewGroup.LayoutParams p)1785 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 1786 return p instanceof CellLayout.LayoutParams; 1787 } 1788 1789 @Override generateLayoutParams(ViewGroup.LayoutParams p)1790 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { 1791 return new CellLayout.LayoutParams(p); 1792 } 1793 1794 public static class CellLayoutAnimationController extends LayoutAnimationController { CellLayoutAnimationController(Animation animation, float delay)1795 public CellLayoutAnimationController(Animation animation, float delay) { 1796 super(animation, delay); 1797 } 1798 1799 @Override getDelayForView(View view)1800 protected long getDelayForView(View view) { 1801 return (int) (Math.random() * 150); 1802 } 1803 } 1804 1805 public static class LayoutParams extends ViewGroup.MarginLayoutParams { 1806 /** 1807 * Horizontal location of the item in the grid. 1808 */ 1809 @ViewDebug.ExportedProperty 1810 public int cellX; 1811 1812 /** 1813 * Vertical location of the item in the grid. 1814 */ 1815 @ViewDebug.ExportedProperty 1816 public int cellY; 1817 1818 /** 1819 * Number of cells spanned horizontally by the item. 1820 */ 1821 @ViewDebug.ExportedProperty 1822 public int cellHSpan; 1823 1824 /** 1825 * Number of cells spanned vertically by the item. 1826 */ 1827 @ViewDebug.ExportedProperty 1828 public int cellVSpan; 1829 1830 /** 1831 * Indicates whether the item will set its x, y, width and height parameters freely, 1832 * or whether these will be computed based on cellX, cellY, cellHSpan and cellVSpan. 1833 */ 1834 public boolean isLockedToGrid = true; 1835 1836 // X coordinate of the view in the layout. 1837 @ViewDebug.ExportedProperty 1838 int x; 1839 // Y coordinate of the view in the layout. 1840 @ViewDebug.ExportedProperty 1841 int y; 1842 1843 boolean dropped; 1844 LayoutParams(Context c, AttributeSet attrs)1845 public LayoutParams(Context c, AttributeSet attrs) { 1846 super(c, attrs); 1847 cellHSpan = 1; 1848 cellVSpan = 1; 1849 } 1850 LayoutParams(ViewGroup.LayoutParams source)1851 public LayoutParams(ViewGroup.LayoutParams source) { 1852 super(source); 1853 cellHSpan = 1; 1854 cellVSpan = 1; 1855 } 1856 LayoutParams(LayoutParams source)1857 public LayoutParams(LayoutParams source) { 1858 super(source); 1859 this.cellX = source.cellX; 1860 this.cellY = source.cellY; 1861 this.cellHSpan = source.cellHSpan; 1862 this.cellVSpan = source.cellVSpan; 1863 } 1864 LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan)1865 public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) { 1866 super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); 1867 this.cellX = cellX; 1868 this.cellY = cellY; 1869 this.cellHSpan = cellHSpan; 1870 this.cellVSpan = cellVSpan; 1871 } 1872 setup(int cellWidth, int cellHeight, int widthGap, int heightGap)1873 public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap) { 1874 if (isLockedToGrid) { 1875 final int myCellHSpan = cellHSpan; 1876 final int myCellVSpan = cellVSpan; 1877 final int myCellX = cellX; 1878 final int myCellY = cellY; 1879 1880 width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) - 1881 leftMargin - rightMargin; 1882 height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) - 1883 topMargin - bottomMargin; 1884 x = myCellX * (cellWidth + widthGap) + leftMargin; 1885 y = myCellY * (cellHeight + heightGap) + topMargin; 1886 } 1887 } 1888 toString()1889 public String toString() { 1890 return "(" + this.cellX + ", " + this.cellY + ")"; 1891 } 1892 setWidth(int width)1893 public void setWidth(int width) { 1894 this.width = width; 1895 } 1896 getWidth()1897 public int getWidth() { 1898 return width; 1899 } 1900 setHeight(int height)1901 public void setHeight(int height) { 1902 this.height = height; 1903 } 1904 getHeight()1905 public int getHeight() { 1906 return height; 1907 } 1908 setX(int x)1909 public void setX(int x) { 1910 this.x = x; 1911 } 1912 getX()1913 public int getX() { 1914 return x; 1915 } 1916 setY(int y)1917 public void setY(int y) { 1918 this.y = y; 1919 } 1920 getY()1921 public int getY() { 1922 return y; 1923 } 1924 } 1925 1926 // This class stores info for two purposes: 1927 // 1. When dragging items (mDragInfo in Workspace), we store the View, its cellX & cellY, 1928 // its spanX, spanY, and the screen it is on 1929 // 2. When long clicking on an empty cell in a CellLayout, we save information about the 1930 // cellX and cellY coordinates and which page was clicked. We then set this as a tag on 1931 // the CellLayout that was long clicked 1932 static final class CellInfo { 1933 View cell; 1934 int cellX = -1; 1935 int cellY = -1; 1936 int spanX; 1937 int spanY; 1938 int screen; 1939 long container; 1940 1941 @Override toString()1942 public String toString() { 1943 return "Cell[view=" + (cell == null ? "null" : cell.getClass()) 1944 + ", x=" + cellX + ", y=" + cellY + "]"; 1945 } 1946 } 1947 lastDownOnOccupiedCell()1948 public boolean lastDownOnOccupiedCell() { 1949 return mLastDownOnOccupiedCell; 1950 } 1951 } 1952