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