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