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