blob: 16f8135f1fe42f6de0f7c2ad06c19237853f6f8d [file] [log] [blame]
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001/*
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
Joe Onoratoa5902522009-07-30 13:37:37 -070017package com.android.launcher2;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080018
Patrick Dubroy6569f2c2010-07-12 14:25:18 -070019import com.android.launcher.R;
Winson Chungaafa03c2010-06-11 17:34:16 -070020
21import android.app.WallpaperManager;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080022import android.content.Context;
Joe Onorato79e56262009-09-21 15:23:04 -040023import android.content.res.Resources;
Winson Chungaafa03c2010-06-11 17:34:16 -070024import android.content.res.TypedArray;
Michael Jurkadee05892010-07-27 10:01:56 -070025import android.graphics.Bitmap;
Winson Chungaafa03c2010-06-11 17:34:16 -070026import android.graphics.Canvas;
Michael Jurkadee05892010-07-27 10:01:56 -070027import android.graphics.Matrix;
28import android.graphics.Paint;
29import android.graphics.PorterDuff;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080030import android.graphics.Rect;
31import android.graphics.RectF;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -070032import android.graphics.drawable.Drawable;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080033import android.util.AttributeSet;
34import android.view.ContextMenu;
35import android.view.MotionEvent;
36import android.view.View;
37import android.view.ViewDebug;
38import android.view.ViewGroup;
Michael Jurkadee05892010-07-27 10:01:56 -070039import android.view.View.OnTouchListener;
Winson Chungaafa03c2010-06-11 17:34:16 -070040import android.view.animation.Animation;
41import android.view.animation.LayoutAnimationController;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080042
Patrick Dubroy6569f2c2010-07-12 14:25:18 -070043import java.util.ArrayList;
44import java.util.Arrays;
Romain Guyedcce092010-03-04 13:03:17 -080045
The Android Open Source Project31dd5032009-03-03 19:32:27 -080046public class CellLayout extends ViewGroup {
Winson Chungaafa03c2010-06-11 17:34:16 -070047 static final String TAG = "CellLayout";
Michael Jurkadee05892010-07-27 10:01:56 -070048 // we make the dimmed bitmap smaller than the screen itself for memory + perf reasons
49 static final float DIMMED_BITMAP_SCALE = 0.25f;
Winson Chungaafa03c2010-06-11 17:34:16 -070050
The Android Open Source Project31dd5032009-03-03 19:32:27 -080051 private boolean mPortrait;
52
53 private int mCellWidth;
54 private int mCellHeight;
Winson Chungaafa03c2010-06-11 17:34:16 -070055
The Android Open Source Project31dd5032009-03-03 19:32:27 -080056 private int mLongAxisStartPadding;
57 private int mLongAxisEndPadding;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080058 private int mShortAxisStartPadding;
59 private int mShortAxisEndPadding;
60
Winson Chungaafa03c2010-06-11 17:34:16 -070061 private int mLeftPadding;
62 private int mRightPadding;
63 private int mTopPadding;
64 private int mBottomPadding;
65
The Android Open Source Project31dd5032009-03-03 19:32:27 -080066 private int mShortAxisCells;
67 private int mLongAxisCells;
68
69 private int mWidthGap;
70 private int mHeightGap;
71
72 private final Rect mRect = new Rect();
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -070073 private final RectF mRectF = new RectF();
The Android Open Source Project31dd5032009-03-03 19:32:27 -080074 private final CellInfo mCellInfo = new CellInfo();
Winson Chungaafa03c2010-06-11 17:34:16 -070075
Patrick Dubroy6569f2c2010-07-12 14:25:18 -070076 // This is a temporary variable to prevent having to allocate a new object just to
77 // return an (x, y) value from helper functions. Do NOT use it to maintain other state.
78 private final int[] mTmpCellXY = new int[2];
79
The Android Open Source Project31dd5032009-03-03 19:32:27 -080080 boolean[][] mOccupied;
81
Michael Jurkadee05892010-07-27 10:01:56 -070082 private OnTouchListener mInterceptTouchListener;
83
84 // this is what the home screen fades to when it shrinks
85 // (ie in all apps and in home screen customize mode)
86 private Bitmap mDimmedBitmap;
87 private Canvas mDimmedBitmapCanvas;
88 private float mDimmedBitmapAlpha;
89 private boolean mDimmedBitmapDirty;
90 private final Paint mDimmedBitmapPaint = new Paint();
91 private final Rect mLayoutRect = new Rect();
92 private final Rect mDimmedBitmapRect = new Rect();
93
Patrick Dubroy6569f2c2010-07-12 14:25:18 -070094 private final RectF mDragRect = new RectF();
95
96 // When dragging, used to indicate a vacant drop location
97 private Drawable mVacantDrawable;
98
99 // When dragging, used to indicate an occupied drop location
100 private Drawable mOccupiedDrawable;
101
102 // Updated to point to mVacantDrawable or mOccupiedDrawable, as appropriate
103 private Drawable mDragRectDrawable;
104
105 // When a drag operation is in progress, holds the nearest cell to the touch point
106 private final int[] mDragCell = new int[2];
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800107
108 private boolean mDirtyTag;
Mike Cleronf8bbd342009-10-23 16:15:16 -0700109 private boolean mLastDownOnOccupiedCell = false;
Winson Chungaafa03c2010-06-11 17:34:16 -0700110
111 private final WallpaperManager mWallpaperManager;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800112
113 public CellLayout(Context context) {
114 this(context, null);
115 }
116
117 public CellLayout(Context context, AttributeSet attrs) {
118 this(context, attrs, 0);
119 }
120
121 public CellLayout(Context context, AttributeSet attrs, int defStyle) {
122 super(context, attrs, defStyle);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700123
124 // A ViewGroup usually does not draw, but CellLayout needs to draw a rectangle to show
125 // the user where a dragged item will land when dropped.
126 setWillNotDraw(false);
127 mVacantDrawable = getResources().getDrawable(R.drawable.rounded_rect_green);
128 mOccupiedDrawable = getResources().getDrawable(R.drawable.rounded_rect_red);
129
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800130 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0);
131
132 mCellWidth = a.getDimensionPixelSize(R.styleable.CellLayout_cellWidth, 10);
133 mCellHeight = a.getDimensionPixelSize(R.styleable.CellLayout_cellHeight, 10);
Winson Chungaafa03c2010-06-11 17:34:16 -0700134
135 mLongAxisStartPadding =
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800136 a.getDimensionPixelSize(R.styleable.CellLayout_longAxisStartPadding, 10);
Winson Chungaafa03c2010-06-11 17:34:16 -0700137 mLongAxisEndPadding =
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800138 a.getDimensionPixelSize(R.styleable.CellLayout_longAxisEndPadding, 10);
139 mShortAxisStartPadding =
140 a.getDimensionPixelSize(R.styleable.CellLayout_shortAxisStartPadding, 10);
Winson Chungaafa03c2010-06-11 17:34:16 -0700141 mShortAxisEndPadding =
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800142 a.getDimensionPixelSize(R.styleable.CellLayout_shortAxisEndPadding, 10);
Winson Chungaafa03c2010-06-11 17:34:16 -0700143
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800144 mShortAxisCells = a.getInt(R.styleable.CellLayout_shortAxisCells, 4);
145 mLongAxisCells = a.getInt(R.styleable.CellLayout_longAxisCells, 4);
146
147 a.recycle();
148
149 setAlwaysDrawnWithCacheEnabled(false);
150
Romain Guy84f296c2009-11-04 15:00:44 -0800151 mWallpaperManager = WallpaperManager.getInstance(getContext());
Michael Jurkadee05892010-07-27 10:01:56 -0700152
153 mDimmedBitmapPaint.setFilterBitmap(true);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800154 }
155
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700156 @Override
Romain Guya6abce82009-11-10 02:54:41 -0800157 public void dispatchDraw(Canvas canvas) {
158 super.dispatchDraw(canvas);
159 }
160
161 @Override
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700162 protected void onDraw(Canvas canvas) {
163 if (!mDragRect.isEmpty()) {
164 mDragRectDrawable.setBounds(
165 (int)mDragRect.left,
166 (int)mDragRect.top,
167 (int)mDragRect.right,
168 (int)mDragRect.bottom);
169 mDragRectDrawable.draw(canvas);
170 }
Michael Jurkadee05892010-07-27 10:01:56 -0700171 if (mDimmedBitmap != null && mDimmedBitmapAlpha > 0.0f) {
172 if (mDimmedBitmapDirty) {
173 updateDimmedBitmap();
174 mDimmedBitmapDirty = false;
175 }
176 mDimmedBitmapPaint.setAlpha((int) (mDimmedBitmapAlpha * 255));
177
178 canvas.drawBitmap(mDimmedBitmap, mDimmedBitmapRect, mLayoutRect, mDimmedBitmapPaint);
179 }
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700180 }
181
182 @Override
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700183 public void cancelLongPress() {
184 super.cancelLongPress();
185
186 // Cancel long press for all children
187 final int count = getChildCount();
188 for (int i = 0; i < count; i++) {
189 final View child = getChildAt(i);
190 child.cancelLongPress();
191 }
192 }
193
Michael Jurkadee05892010-07-27 10:01:56 -0700194 public void setOnInterceptTouchListener(View.OnTouchListener listener) {
195 mInterceptTouchListener = listener;
196 }
197
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800198 int getCountX() {
199 return mPortrait ? mShortAxisCells : mLongAxisCells;
200 }
201
202 int getCountY() {
203 return mPortrait ? mLongAxisCells : mShortAxisCells;
204 }
205
Winson Chungaafa03c2010-06-11 17:34:16 -0700206 // Takes canonical layout parameters
207 public boolean addViewToCellLayout(View child, int index, int childId, LayoutParams params) {
208 final LayoutParams lp = params;
209
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800210 // Generate an id for each view, this assumes we have at most 256x256 cells
211 // per workspace screen
Winson Chungaafa03c2010-06-11 17:34:16 -0700212 if (lp.cellX >= 0 && lp.cellX <= getCountX() - 1 && lp.cellY >= 0 && lp.cellY <= getCountY() - 1) {
213 // If the horizontal or vertical span is set to -1, it is taken to
214 // mean that it spans the extent of the CellLayout
215 if (lp.cellHSpan < 0) lp.cellHSpan = getCountX();
216 if (lp.cellVSpan < 0) lp.cellVSpan = getCountY();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800217
Winson Chungaafa03c2010-06-11 17:34:16 -0700218 child.setId(childId);
219
Michael Jurkadee05892010-07-27 10:01:56 -0700220 // We might be in the middle or end of shrinking/fading to a dimmed view
221 // Make sure this view's alpha is set the same as all the rest of the views
222 child.setAlpha(1.0f - mDimmedBitmapAlpha);
223
Winson Chungaafa03c2010-06-11 17:34:16 -0700224 addView(child, index, lp);
Michael Jurkadee05892010-07-27 10:01:56 -0700225
226 // next time we draw the dimmed bitmap we need to update it
227 mDimmedBitmapDirty = true;
Winson Chungaafa03c2010-06-11 17:34:16 -0700228 return true;
229 }
230 return false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800231 }
232
233 @Override
Michael Jurkadee05892010-07-27 10:01:56 -0700234 public void removeView(View view) {
235 super.removeView(view);
236 mDimmedBitmapDirty = true;
237 }
238
239 @Override
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800240 public void requestChildFocus(View child, View focused) {
241 super.requestChildFocus(child, focused);
242 if (child != null) {
243 Rect r = new Rect();
244 child.getDrawingRect(r);
245 requestRectangleOnScreen(r);
246 }
247 }
248
249 @Override
250 protected void onAttachedToWindow() {
251 super.onAttachedToWindow();
252 mCellInfo.screen = ((ViewGroup) getParent()).indexOfChild(this);
253 }
254
Michael Jurkaaf442092010-06-10 17:01:57 -0700255 public void setTagToCellInfoForPoint(int touchX, int touchY) {
256 final CellInfo cellInfo = mCellInfo;
257 final Rect frame = mRect;
258 final int x = touchX + mScrollX;
259 final int y = touchY + mScrollY;
260 final int count = getChildCount();
261
262 boolean found = false;
263 for (int i = count - 1; i >= 0; i--) {
264 final View child = getChildAt(i);
265
266 if ((child.getVisibility()) == VISIBLE || child.getAnimation() != null) {
267 child.getHitRect(frame);
268 if (frame.contains(x, y)) {
269 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
270 cellInfo.cell = child;
271 cellInfo.cellX = lp.cellX;
272 cellInfo.cellY = lp.cellY;
273 cellInfo.spanX = lp.cellHSpan;
274 cellInfo.spanY = lp.cellVSpan;
275 cellInfo.valid = true;
276 found = true;
277 mDirtyTag = false;
278 break;
279 }
280 }
281 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700282
Michael Jurkaaf442092010-06-10 17:01:57 -0700283 mLastDownOnOccupiedCell = found;
284
285 if (!found) {
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700286 final int cellXY[] = mTmpCellXY;
Michael Jurkaaf442092010-06-10 17:01:57 -0700287 pointToCellExact(x, y, cellXY);
288
289 final boolean portrait = mPortrait;
290 final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
291 final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
292
293 final boolean[][] occupied = mOccupied;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700294 findOccupiedCells(xCount, yCount, occupied, null, true);
Michael Jurkaaf442092010-06-10 17:01:57 -0700295
296 cellInfo.cell = null;
297 cellInfo.cellX = cellXY[0];
298 cellInfo.cellY = cellXY[1];
299 cellInfo.spanX = 1;
300 cellInfo.spanY = 1;
301 cellInfo.valid = cellXY[0] >= 0 && cellXY[1] >= 0 && cellXY[0] < xCount &&
302 cellXY[1] < yCount && !occupied[cellXY[0]][cellXY[1]];
303
304 // Instead of finding the interesting vacant cells here, wait until a
305 // caller invokes getTag() to retrieve the result. Finding the vacant
306 // cells is a bit expensive and can generate many new objects, it's
307 // therefore better to defer it until we know we actually need it.
308
309 mDirtyTag = true;
310 }
311 setTag(cellInfo);
312 }
313
Winson Chungaafa03c2010-06-11 17:34:16 -0700314
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800315 @Override
316 public boolean onInterceptTouchEvent(MotionEvent ev) {
Michael Jurkadee05892010-07-27 10:01:56 -0700317 if (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev)) {
318 return true;
319 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800320 final int action = ev.getAction();
321 final CellInfo cellInfo = mCellInfo;
322
323 if (action == MotionEvent.ACTION_DOWN) {
Michael Jurkaaf442092010-06-10 17:01:57 -0700324 setTagToCellInfoForPoint((int) ev.getX(), (int) ev.getY());
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800325 } else if (action == MotionEvent.ACTION_UP) {
326 cellInfo.cell = null;
327 cellInfo.cellX = -1;
328 cellInfo.cellY = -1;
329 cellInfo.spanX = 0;
330 cellInfo.spanY = 0;
331 cellInfo.valid = false;
332 mDirtyTag = false;
333 setTag(cellInfo);
334 }
335
336 return false;
337 }
338
339 @Override
340 public CellInfo getTag() {
341 final CellInfo info = (CellInfo) super.getTag();
342 if (mDirtyTag && info.valid) {
343 final boolean portrait = mPortrait;
344 final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
345 final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
346
347 final boolean[][] occupied = mOccupied;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700348 findOccupiedCells(xCount, yCount, occupied, null, true);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800349
350 findIntersectingVacantCells(info, info.cellX, info.cellY, xCount, yCount, occupied);
351
352 mDirtyTag = false;
353 }
354 return info;
355 }
356
Winson Chungaafa03c2010-06-11 17:34:16 -0700357 private static void findIntersectingVacantCells(CellInfo cellInfo, int x,
358 int y, int xCount, int yCount, boolean[][] occupied) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800359
360 cellInfo.maxVacantSpanX = Integer.MIN_VALUE;
361 cellInfo.maxVacantSpanXSpanY = Integer.MIN_VALUE;
362 cellInfo.maxVacantSpanY = Integer.MIN_VALUE;
363 cellInfo.maxVacantSpanYSpanX = Integer.MIN_VALUE;
364 cellInfo.clearVacantCells();
365
366 if (occupied[x][y]) {
367 return;
368 }
369
370 cellInfo.current.set(x, y, x, y);
371
372 findVacantCell(cellInfo.current, xCount, yCount, occupied, cellInfo);
373 }
374
375 private static void findVacantCell(Rect current, int xCount, int yCount, boolean[][] occupied,
376 CellInfo cellInfo) {
377
378 addVacantCell(current, cellInfo);
379
380 if (current.left > 0) {
381 if (isColumnEmpty(current.left - 1, current.top, current.bottom, occupied)) {
382 current.left--;
383 findVacantCell(current, xCount, yCount, occupied, cellInfo);
384 current.left++;
385 }
386 }
387
388 if (current.right < xCount - 1) {
389 if (isColumnEmpty(current.right + 1, current.top, current.bottom, occupied)) {
390 current.right++;
391 findVacantCell(current, xCount, yCount, occupied, cellInfo);
392 current.right--;
393 }
394 }
395
396 if (current.top > 0) {
397 if (isRowEmpty(current.top - 1, current.left, current.right, occupied)) {
398 current.top--;
399 findVacantCell(current, xCount, yCount, occupied, cellInfo);
400 current.top++;
401 }
402 }
403
404 if (current.bottom < yCount - 1) {
405 if (isRowEmpty(current.bottom + 1, current.left, current.right, occupied)) {
406 current.bottom++;
407 findVacantCell(current, xCount, yCount, occupied, cellInfo);
408 current.bottom--;
409 }
410 }
411 }
412
413 private static void addVacantCell(Rect current, CellInfo cellInfo) {
414 CellInfo.VacantCell cell = CellInfo.VacantCell.acquire();
415 cell.cellX = current.left;
416 cell.cellY = current.top;
417 cell.spanX = current.right - current.left + 1;
418 cell.spanY = current.bottom - current.top + 1;
419 if (cell.spanX > cellInfo.maxVacantSpanX) {
420 cellInfo.maxVacantSpanX = cell.spanX;
421 cellInfo.maxVacantSpanXSpanY = cell.spanY;
422 }
423 if (cell.spanY > cellInfo.maxVacantSpanY) {
424 cellInfo.maxVacantSpanY = cell.spanY;
425 cellInfo.maxVacantSpanYSpanX = cell.spanX;
426 }
427 cellInfo.vacantCells.add(cell);
428 }
429
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700430 /**
431 * Check if the column 'x' is empty from rows 'top' to 'bottom', inclusive.
432 */
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800433 private static boolean isColumnEmpty(int x, int top, int bottom, boolean[][] occupied) {
434 for (int y = top; y <= bottom; y++) {
435 if (occupied[x][y]) {
436 return false;
437 }
438 }
439 return true;
440 }
441
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700442 /**
443 * Check if the row 'y' is empty from columns 'left' to 'right', inclusive.
444 */
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800445 private static boolean isRowEmpty(int y, int left, int right, boolean[][] occupied) {
446 for (int x = left; x <= right; x++) {
447 if (occupied[x][y]) {
448 return false;
449 }
450 }
451 return true;
452 }
453
Jeff Sharkey70864282009-04-07 21:08:40 -0700454 CellInfo findAllVacantCells(boolean[] occupiedCells, View ignoreView) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800455 final boolean portrait = mPortrait;
456 final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
457 final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
458
459 boolean[][] occupied = mOccupied;
460
461 if (occupiedCells != null) {
462 for (int y = 0; y < yCount; y++) {
463 for (int x = 0; x < xCount; x++) {
464 occupied[x][y] = occupiedCells[y * xCount + x];
465 }
466 }
467 } else {
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700468 findOccupiedCells(xCount, yCount, occupied, ignoreView, true);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800469 }
470
471 CellInfo cellInfo = new CellInfo();
472
473 cellInfo.cellX = -1;
474 cellInfo.cellY = -1;
475 cellInfo.spanY = 0;
476 cellInfo.spanX = 0;
477 cellInfo.maxVacantSpanX = Integer.MIN_VALUE;
478 cellInfo.maxVacantSpanXSpanY = Integer.MIN_VALUE;
479 cellInfo.maxVacantSpanY = Integer.MIN_VALUE;
480 cellInfo.maxVacantSpanYSpanX = Integer.MIN_VALUE;
481 cellInfo.screen = mCellInfo.screen;
482
483 Rect current = cellInfo.current;
484
485 for (int x = 0; x < xCount; x++) {
486 for (int y = 0; y < yCount; y++) {
487 if (!occupied[x][y]) {
488 current.set(x, y, x, y);
489 findVacantCell(current, xCount, yCount, occupied, cellInfo);
490 occupied[x][y] = true;
491 }
492 }
493 }
494
495 cellInfo.valid = cellInfo.vacantCells.size() > 0;
496
497 // Assume the caller will perform their own cell searching, otherwise we
498 // risk causing an unnecessary rebuild after findCellForSpan()
Winson Chungaafa03c2010-06-11 17:34:16 -0700499
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800500 return cellInfo;
501 }
502
503 /**
Winson Chungaafa03c2010-06-11 17:34:16 -0700504 * Given a point, return the cell that strictly encloses that point
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800505 * @param x X coordinate of the point
506 * @param y Y coordinate of the point
507 * @param result Array of 2 ints to hold the x and y coordinate of the cell
508 */
509 void pointToCellExact(int x, int y, int[] result) {
510 final boolean portrait = mPortrait;
Winson Chungaafa03c2010-06-11 17:34:16 -0700511
512 final int hStartPadding = getLeftPadding();
513 final int vStartPadding = getTopPadding();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800514
515 result[0] = (x - hStartPadding) / (mCellWidth + mWidthGap);
516 result[1] = (y - vStartPadding) / (mCellHeight + mHeightGap);
517
518 final int xAxis = portrait ? mShortAxisCells : mLongAxisCells;
519 final int yAxis = portrait ? mLongAxisCells : mShortAxisCells;
520
521 if (result[0] < 0) result[0] = 0;
522 if (result[0] >= xAxis) result[0] = xAxis - 1;
523 if (result[1] < 0) result[1] = 0;
524 if (result[1] >= yAxis) result[1] = yAxis - 1;
525 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700526
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800527 /**
528 * Given a point, return the cell that most closely encloses that point
529 * @param x X coordinate of the point
530 * @param y Y coordinate of the point
531 * @param result Array of 2 ints to hold the x and y coordinate of the cell
532 */
533 void pointToCellRounded(int x, int y, int[] result) {
534 pointToCellExact(x + (mCellWidth / 2), y + (mCellHeight / 2), result);
535 }
536
537 /**
538 * Given a cell coordinate, return the point that represents the upper left corner of that cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700539 *
540 * @param cellX X coordinate of the cell
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800541 * @param cellY Y coordinate of the cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700542 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800543 * @param result Array of 2 ints to hold the x and y coordinate of the point
544 */
545 void cellToPoint(int cellX, int cellY, int[] result) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700546 final int hStartPadding = getLeftPadding();
547 final int vStartPadding = getTopPadding();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800548
549 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap);
550 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap);
551 }
552
Romain Guy84f296c2009-11-04 15:00:44 -0800553 int getCellWidth() {
554 return mCellWidth;
555 }
556
557 int getCellHeight() {
558 return mCellHeight;
559 }
560
Romain Guy1a304a12009-11-10 00:02:32 -0800561 int getLeftPadding() {
Winson Chungaafa03c2010-06-11 17:34:16 -0700562 return mLeftPadding;
Romain Guy1a304a12009-11-10 00:02:32 -0800563 }
564
565 int getTopPadding() {
Winson Chungaafa03c2010-06-11 17:34:16 -0700566 return mTopPadding;
Romain Guy1a304a12009-11-10 00:02:32 -0800567 }
568
569 int getRightPadding() {
Winson Chungaafa03c2010-06-11 17:34:16 -0700570 return mRightPadding;
Romain Guy1a304a12009-11-10 00:02:32 -0800571 }
572
573 int getBottomPadding() {
Winson Chungaafa03c2010-06-11 17:34:16 -0700574 return mBottomPadding;
Romain Guy1a304a12009-11-10 00:02:32 -0800575 }
576
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800577 @Override
578 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
579 // TODO: currently ignoring padding
Winson Chungaafa03c2010-06-11 17:34:16 -0700580
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800581 int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
Winson Chungaafa03c2010-06-11 17:34:16 -0700582 int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
583
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800584 int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
585 int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
Winson Chungaafa03c2010-06-11 17:34:16 -0700586
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800587 if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
588 throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
589 }
590
591 final int shortAxisCells = mShortAxisCells;
592 final int longAxisCells = mLongAxisCells;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800593 final int cellWidth = mCellWidth;
594 final int cellHeight = mCellHeight;
595
Winson Chungaafa03c2010-06-11 17:34:16 -0700596 boolean portrait = heightSpecSize > widthSpecSize;
597 if (portrait != mPortrait || mOccupied == null) {
598 if (portrait) {
599 mOccupied = new boolean[mShortAxisCells][mLongAxisCells];
600 } else {
601 mOccupied = new boolean[mLongAxisCells][mShortAxisCells];
602 }
603 }
604 mPortrait = portrait;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800605
606 int numShortGaps = shortAxisCells - 1;
607 int numLongGaps = longAxisCells - 1;
608
609 if (mPortrait) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700610 int vSpaceLeft = heightSpecSize - mLongAxisStartPadding
611 - mLongAxisEndPadding - (cellHeight * longAxisCells);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800612 mHeightGap = vSpaceLeft / numLongGaps;
613
Winson Chungaafa03c2010-06-11 17:34:16 -0700614 int hSpaceLeft = widthSpecSize - mShortAxisStartPadding
615 - mShortAxisEndPadding - (cellWidth * shortAxisCells);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800616 if (numShortGaps > 0) {
617 mWidthGap = hSpaceLeft / numShortGaps;
618 } else {
619 mWidthGap = 0;
620 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700621
622 if (LauncherApplication.isInPlaceRotationEnabled()) {
623 mWidthGap = mHeightGap = Math.min(mHeightGap, mWidthGap);
624 mLeftPadding = mRightPadding = (widthSpecSize - cellWidth
625 * shortAxisCells - (shortAxisCells - 1) * mWidthGap) / 2;
626 mTopPadding = mBottomPadding = (heightSpecSize - cellHeight
627 * longAxisCells - (longAxisCells - 1) * mHeightGap) / 2;
628 } else {
629 mLeftPadding = mShortAxisStartPadding;
630 mRightPadding = mShortAxisEndPadding;
631 mTopPadding = mLongAxisStartPadding;
632 mBottomPadding = mLongAxisEndPadding;
633 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800634 } else {
Winson Chungaafa03c2010-06-11 17:34:16 -0700635 int hSpaceLeft = widthSpecSize - mLongAxisStartPadding
636 - mLongAxisEndPadding - (cellWidth * longAxisCells);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800637 mWidthGap = hSpaceLeft / numLongGaps;
638
Winson Chungaafa03c2010-06-11 17:34:16 -0700639 int vSpaceLeft = heightSpecSize - mShortAxisStartPadding
640 - mShortAxisEndPadding - (cellHeight * shortAxisCells);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800641 if (numShortGaps > 0) {
642 mHeightGap = vSpaceLeft / numShortGaps;
643 } else {
644 mHeightGap = 0;
645 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700646
647 if (LauncherApplication.isScreenXLarge()) {
648 mWidthGap = mHeightGap = Math.min(mHeightGap, mWidthGap);
649 mLeftPadding = mRightPadding = (widthSpecSize - cellWidth
650 * longAxisCells - (longAxisCells - 1) * mWidthGap) / 2 ;
651 mTopPadding = mBottomPadding = (heightSpecSize - cellHeight
652 * shortAxisCells - (shortAxisCells - 1) * mHeightGap) / 2;
653 } else {
654 mLeftPadding = mLongAxisStartPadding;
655 mRightPadding = mLongAxisEndPadding;
656 mTopPadding = mShortAxisStartPadding;
657 mBottomPadding = mShortAxisEndPadding;
658 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800659 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800660 int count = getChildCount();
661
662 for (int i = 0; i < count; i++) {
663 View child = getChildAt(i);
664 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Winson Chungaafa03c2010-06-11 17:34:16 -0700665 lp.setup(cellWidth, cellHeight, mWidthGap, mHeightGap,
666 mLeftPadding, mTopPadding);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800667
Winson Chungaafa03c2010-06-11 17:34:16 -0700668 int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.width,
669 MeasureSpec.EXACTLY);
670 int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(lp.height,
671 MeasureSpec.EXACTLY);
672
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800673 child.measure(childWidthMeasureSpec, childheightMeasureSpec);
674 }
675
676 setMeasuredDimension(widthSpecSize, heightSpecSize);
677 }
678
679 @Override
680 protected void onLayout(boolean changed, int l, int t, int r, int b) {
681 int count = getChildCount();
682
683 for (int i = 0; i < count; i++) {
684 View child = getChildAt(i);
685 if (child.getVisibility() != GONE) {
686
687 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
688
689 int childLeft = lp.x;
690 int childTop = lp.y;
691 child.layout(childLeft, childTop, childLeft + lp.width, childTop + lp.height);
Romain Guy84f296c2009-11-04 15:00:44 -0800692
693 if (lp.dropped) {
694 lp.dropped = false;
695
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700696 final int[] cellXY = mTmpCellXY;
Romain Guy06762ab2010-01-25 16:51:08 -0800697 getLocationOnScreen(cellXY);
Romain Guy84f296c2009-11-04 15:00:44 -0800698 mWallpaperManager.sendWallpaperCommand(getWindowToken(), "android.home.drop",
Romain Guy06762ab2010-01-25 16:51:08 -0800699 cellXY[0] + childLeft + lp.width / 2,
700 cellXY[1] + childTop + lp.height / 2, 0, null);
Romain Guy84f296c2009-11-04 15:00:44 -0800701 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800702 }
703 }
704 }
705
706 @Override
Michael Jurkadee05892010-07-27 10:01:56 -0700707 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
708 super.onSizeChanged(w, h, oldw, oldh);
709 mLayoutRect.set(0, 0, w, h);
710 mDimmedBitmapRect.set(0, 0, (int) (DIMMED_BITMAP_SCALE * w), (int) (DIMMED_BITMAP_SCALE * h));
711 }
712
713 @Override
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800714 protected void setChildrenDrawingCacheEnabled(boolean enabled) {
715 final int count = getChildCount();
716 for (int i = 0; i < count; i++) {
717 final View view = getChildAt(i);
718 view.setDrawingCacheEnabled(enabled);
719 // Update the drawing caches
Adam Powellfefa0ce2010-05-03 10:23:50 -0700720 view.buildDrawingCache(true);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800721 }
722 }
723
724 @Override
725 protected void setChildrenDrawnWithCacheEnabled(boolean enabled) {
726 super.setChildrenDrawnWithCacheEnabled(enabled);
727 }
728
Michael Jurkadee05892010-07-27 10:01:56 -0700729 public float getDimmedBitmapAlpha() {
730 return mDimmedBitmapAlpha;
731 }
732
733 public void setDimmedBitmapAlpha(float alpha) {
734 // If we're dimming the screen after it was not dimmed, refresh
735 // to allow for updated widgets. We don't continually refresh it
736 // after this point, however, as an optimization
737 if (mDimmedBitmapAlpha == 0.0f && alpha > 0.0f) {
738 updateDimmedBitmap();
739 }
740 mDimmedBitmapAlpha = alpha;
741 setChildrenAlpha(1.0f - mDimmedBitmapAlpha);
742 }
743
744 private void setChildrenAlpha(float alpha) {
745 for (int i = 0; i < getChildCount(); i++) {
746 getChildAt(i).setAlpha(alpha);
747 }
748 }
749
750 public void updateDimmedBitmap() {
751 if (mDimmedBitmap == null) {
752 mDimmedBitmap = Bitmap.createBitmap((int) (getWidth() * DIMMED_BITMAP_SCALE),
753 (int) (getHeight() * DIMMED_BITMAP_SCALE), Bitmap.Config.ARGB_8888);
754 mDimmedBitmapCanvas = new Canvas(mDimmedBitmap);
755 mDimmedBitmapCanvas.scale(DIMMED_BITMAP_SCALE, DIMMED_BITMAP_SCALE);
756 }
757 // clear the canvas
758 mDimmedBitmapCanvas.drawColor(0x00000000, PorterDuff.Mode.CLEAR);
759
760 // draw the screen into the bitmap
761 // just for drawing to the bitmap, make all the items on the screen opaque
762 setChildrenAlpha(1.0f);
763 dispatchDraw(mDimmedBitmapCanvas);
764 setChildrenAlpha(1.0f - mDimmedBitmapAlpha);
765
766 // make the bitmap 'dimmed' ie colored regions are dark grey,
767 // the rest is light grey
768 // We draw grey to the whole bitmap, but filter where we draw based on
769 // what regions are transparent or not (SRC_OUT), causing the intended effect
770
771 // First, draw light grey everywhere in the background (currently transparent) regions
772 // This will leave the regions with the widgets as mostly transparent
773 mDimmedBitmapCanvas.drawColor(0xCCCCCCCC, PorterDuff.Mode.SRC_OUT);
774 // Now, fill the the remaining transparent regions with dark grey
775 mDimmedBitmapCanvas.drawColor(0xCC333333, PorterDuff.Mode.SRC_OUT);
776 }
777
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700778 private boolean isVacant(int originX, int originY, int spanX, int spanY) {
779 for (int i = 0; i < spanY; i++) {
780 if (!isRowEmpty(originY + i, originX, originX + spanX - 1, mOccupied)) {
781 return false;
782 }
783 }
784 return true;
785 }
786
Patrick Dubroy440c3602010-07-13 17:50:32 -0700787 public View getChildAt(int x, int y) {
788 final int count = getChildCount();
789 for (int i = 0; i < count; i++) {
790 View child = getChildAt(i);
791 LayoutParams lp = (LayoutParams) child.getLayoutParams();
792
793 if ((lp.cellX <= x) && (x < lp.cellX + lp.cellHSpan) &&
794 (lp.cellY <= y) && (y < lp.cellY + lp.cellHSpan)) {
795 return child;
796 }
797 }
798 return null;
799 }
800
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700801 /**
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -0700802 * Estimate the size that a child with the given dimensions will take in the layout.
803 */
804 void estimateChildSize(int minWidth, int minHeight, int[] result) {
805 // Assuming it's placed at 0, 0, find where the bottom right cell will land
806 rectToCell(minWidth, minHeight, result);
807
808 // Then figure out the rect it will occupy
809 cellToRect(0, 0, result[0], result[1], mRectF);
810 result[0] = (int)mRectF.width();
811 result[1] = (int)mRectF.height();
812 }
813
814 /**
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700815 * Estimate where the top left cell of the dragged item will land if it is dropped.
816 *
817 * @param originX The X value of the top left corner of the item
818 * @param originY The Y value of the top left corner of the item
819 * @param spanX The number of horizontal cells that the item spans
820 * @param spanY The number of vertical cells that the item spans
821 * @param result The estimated drop cell X and Y.
822 */
823 void estimateDropCell(int originX, int originY, int spanX, int spanY, int[] result) {
824 final int countX = getCountX();
825 final int countY = getCountY();
826
Patrick Dubroy6d5c6ec2010-07-14 11:14:47 -0700827 pointToCellRounded(originX, originY, result);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700828
829 // If the item isn't fully on this screen, snap to the edges
830 int rightOverhang = result[0] + spanX - countX;
831 if (rightOverhang > 0) {
832 result[0] -= rightOverhang; // Snap to right
833 }
834 result[0] = Math.max(0, result[0]); // Snap to left
835 int bottomOverhang = result[1] + spanY - countY;
836 if (bottomOverhang > 0) {
837 result[1] -= bottomOverhang; // Snap to bottom
838 }
839 result[1] = Math.max(0, result[1]); // Snap to top
840 }
841
842 void visualizeDropLocation(View view, int originX, int originY, int spanX, int spanY) {
843 final int[] originCell = mDragCell;
844 final int[] cellXY = mTmpCellXY;
845 estimateDropCell(originX, originY, spanX, spanY, cellXY);
846
847 // Only recalculate the bounding rect when necessary
848 if (!Arrays.equals(cellXY, originCell)) {
849 originCell[0] = cellXY[0];
850 originCell[1] = cellXY[1];
851
852 // Find the top left corner of the rect the object will occupy
853 final int[] topLeft = mTmpCellXY;
854 cellToPoint(originCell[0], originCell[1], topLeft);
855 final int left = topLeft[0];
856 final int top = topLeft[1];
857
858 // Now find the bottom right
859 final int[] bottomRight = mTmpCellXY;
860 cellToPoint(originCell[0] + spanX - 1, originCell[1] + spanY - 1, bottomRight);
861 bottomRight[0] += mCellWidth;
862 bottomRight[1] += mCellHeight;
863
864 final int countX = mPortrait ? mShortAxisCells : mLongAxisCells;
865 final int countY = mPortrait ? mLongAxisCells : mShortAxisCells;
866 // TODO: It's not necessary to do this every time, but it's not especially expensive
867 findOccupiedCells(countX, countY, mOccupied, view, false);
868
869 boolean vacant = isVacant(originCell[0], originCell[1], spanX, spanY);
870 mDragRectDrawable = vacant ? mVacantDrawable : mOccupiedDrawable;
871
872 // mDragRect will be rendered in onDraw()
873 mDragRect.set(left, top, bottomRight[0], bottomRight[1]);
874 invalidate();
875 }
876 }
877
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800878 /**
Jeff Sharkey70864282009-04-07 21:08:40 -0700879 * Find a vacant area that will fit the given bounds nearest the requested
880 * cell location. Uses Euclidean distance to score multiple vacant areas.
Winson Chungaafa03c2010-06-11 17:34:16 -0700881 *
Romain Guy51afc022009-05-04 18:03:43 -0700882 * @param pixelX The X location at which you want to search for a vacant area.
883 * @param pixelY The Y location at which you want to search for a vacant area.
Jeff Sharkey70864282009-04-07 21:08:40 -0700884 * @param spanX Horizontal span of the object.
885 * @param spanY Vertical span of the object.
886 * @param vacantCells Pre-computed set of vacant cells to search.
887 * @param recycle Previously returned value to possibly recycle.
888 * @return The X, Y cell of a vacant area that can contain this object,
889 * nearest the requested location.
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800890 */
Jeff Sharkey70864282009-04-07 21:08:40 -0700891 int[] findNearestVacantArea(int pixelX, int pixelY, int spanX, int spanY,
892 CellInfo vacantCells, int[] recycle) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700893
Jeff Sharkey70864282009-04-07 21:08:40 -0700894 // Keep track of best-scoring drop area
895 final int[] bestXY = recycle != null ? recycle : new int[2];
Jeff Sharkey70864282009-04-07 21:08:40 -0700896 double bestDistance = Double.MAX_VALUE;
Winson Chungaafa03c2010-06-11 17:34:16 -0700897
Jeff Sharkey70864282009-04-07 21:08:40 -0700898 // Bail early if vacant cells aren't valid
899 if (!vacantCells.valid) {
900 return null;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800901 }
902
Jeff Sharkey70864282009-04-07 21:08:40 -0700903 // Look across all vacant cells for best fit
904 final int size = vacantCells.vacantCells.size();
905 for (int i = 0; i < size; i++) {
906 final CellInfo.VacantCell cell = vacantCells.vacantCells.get(i);
Winson Chungaafa03c2010-06-11 17:34:16 -0700907
Jeff Sharkey70864282009-04-07 21:08:40 -0700908 // Reject if vacant cell isn't our exact size
909 if (cell.spanX != spanX || cell.spanY != spanY) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800910 continue;
911 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700912
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700913 // Score is distance from requested pixel to the top left of each cell
914 final int[] cellXY = mTmpCellXY;
Jeff Sharkey70864282009-04-07 21:08:40 -0700915 cellToPoint(cell.cellX, cell.cellY, cellXY);
Winson Chungaafa03c2010-06-11 17:34:16 -0700916
917 double distance = Math.sqrt(Math.pow(cellXY[0] - pixelX, 2)
918 + Math.pow(cellXY[1] - pixelY, 2));
Jeff Sharkey70864282009-04-07 21:08:40 -0700919 if (distance <= bestDistance) {
920 bestDistance = distance;
921 bestXY[0] = cell.cellX;
922 bestXY[1] = cell.cellY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800923 }
924 }
925
Winson Chungaafa03c2010-06-11 17:34:16 -0700926 // Return null if no suitable location found
Jeff Sharkey70864282009-04-07 21:08:40 -0700927 if (bestDistance < Double.MAX_VALUE) {
928 return bestXY;
929 } else {
930 return null;
931 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800932 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700933
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800934 /**
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700935 * Called when a drag and drop operation has finished (successfully or not).
936 */
937 void onDragComplete() {
938 // Invalidate the drag data
939 mDragCell[0] = -1;
940 mDragCell[1] = -1;
941
942 mDragRect.setEmpty();
943 invalidate();
944 }
945
946 /**
Winson Chungaafa03c2010-06-11 17:34:16 -0700947 * Mark a child as having been dropped.
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800948 *
949 * @param child The child that is being dropped
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800950 */
Winson Chungaafa03c2010-06-11 17:34:16 -0700951 void onDropChild(View child) {
Romain Guyd94533d2009-08-17 10:01:15 -0700952 if (child != null) {
953 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Romain Guyd94533d2009-08-17 10:01:15 -0700954 lp.isDragging = false;
Romain Guy84f296c2009-11-04 15:00:44 -0800955 lp.dropped = true;
Romain Guyd94533d2009-08-17 10:01:15 -0700956 mDragRect.setEmpty();
957 child.requestLayout();
Romain Guyd94533d2009-08-17 10:01:15 -0700958 }
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700959 onDragComplete();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800960 }
961
962 void onDropAborted(View child) {
963 if (child != null) {
964 ((LayoutParams) child.getLayoutParams()).isDragging = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800965 }
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700966 onDragComplete();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800967 }
968
969 /**
970 * Start dragging the specified child
Winson Chungaafa03c2010-06-11 17:34:16 -0700971 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800972 * @param child The child that is being dragged
973 */
974 void onDragChild(View child) {
975 LayoutParams lp = (LayoutParams) child.getLayoutParams();
976 lp.isDragging = true;
977 mDragRect.setEmpty();
978 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700979
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800980 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800981 * Computes a bounding rectangle for a range of cells
Winson Chungaafa03c2010-06-11 17:34:16 -0700982 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800983 * @param cellX X coordinate of upper left corner expressed as a cell position
984 * @param cellY Y coordinate of upper left corner expressed as a cell position
Winson Chungaafa03c2010-06-11 17:34:16 -0700985 * @param cellHSpan Width in cells
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800986 * @param cellVSpan Height in cells
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700987 * @param resultRect Rect into which to put the results
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800988 */
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700989 public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, RectF resultRect) {
990 final boolean portrait = mPortrait;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800991 final int cellWidth = mCellWidth;
992 final int cellHeight = mCellHeight;
993 final int widthGap = mWidthGap;
994 final int heightGap = mHeightGap;
Winson Chungaafa03c2010-06-11 17:34:16 -0700995
996 final int hStartPadding = getLeftPadding();
997 final int vStartPadding = getTopPadding();
998
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800999 int width = cellHSpan * cellWidth + ((cellHSpan - 1) * widthGap);
1000 int height = cellVSpan * cellHeight + ((cellVSpan - 1) * heightGap);
1001
1002 int x = hStartPadding + cellX * (cellWidth + widthGap);
1003 int y = vStartPadding + cellY * (cellHeight + heightGap);
Winson Chungaafa03c2010-06-11 17:34:16 -07001004
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001005 resultRect.set(x, y, x + width, y + height);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001006 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001007
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001008 /**
Winson Chungaafa03c2010-06-11 17:34:16 -07001009 * Computes the required horizontal and vertical cell spans to always
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001010 * fit the given rectangle.
Winson Chungaafa03c2010-06-11 17:34:16 -07001011 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001012 * @param width Width in pixels
1013 * @param height Height in pixels
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -07001014 * @param result An array of length 2 in which to store the result (may be null).
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001015 */
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -07001016 public int[] rectToCell(int width, int height, int[] result) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001017 // Always assume we're working with the smallest span to make sure we
1018 // reserve enough space in both orientations.
Joe Onorato79e56262009-09-21 15:23:04 -04001019 final Resources resources = getResources();
1020 int actualWidth = resources.getDimensionPixelSize(R.dimen.workspace_cell_width);
1021 int actualHeight = resources.getDimensionPixelSize(R.dimen.workspace_cell_height);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001022 int smallerSize = Math.min(actualWidth, actualHeight);
Joe Onorato79e56262009-09-21 15:23:04 -04001023
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001024 // Always round up to next largest cell
1025 int spanX = (width + smallerSize) / smallerSize;
1026 int spanY = (height + smallerSize) / smallerSize;
Joe Onorato79e56262009-09-21 15:23:04 -04001027
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -07001028 if (result == null) {
1029 return new int[] { spanX, spanY };
1030 }
1031 result[0] = spanX;
1032 result[1] = spanY;
1033 return result;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001034 }
1035
1036 /**
1037 * Find the first vacant cell, if there is one.
1038 *
1039 * @param vacant Holds the x and y coordinate of the vacant cell
1040 * @param spanX Horizontal cell span.
1041 * @param spanY Vertical cell span.
Winson Chungaafa03c2010-06-11 17:34:16 -07001042 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001043 * @return True if a vacant cell was found
1044 */
1045 public boolean getVacantCell(int[] vacant, int spanX, int spanY) {
1046 final boolean portrait = mPortrait;
1047 final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
1048 final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
1049 final boolean[][] occupied = mOccupied;
1050
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001051 findOccupiedCells(xCount, yCount, occupied, null, true);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001052
1053 return findVacantCell(vacant, spanX, spanY, xCount, yCount, occupied);
1054 }
1055
1056 static boolean findVacantCell(int[] vacant, int spanX, int spanY,
1057 int xCount, int yCount, boolean[][] occupied) {
1058
1059 for (int x = 0; x < xCount; x++) {
1060 for (int y = 0; y < yCount; y++) {
1061 boolean available = !occupied[x][y];
1062out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) {
1063 for (int j = y; j < y + spanY - 1 && y < yCount; j++) {
1064 available = available && !occupied[i][j];
1065 if (!available) break out;
1066 }
1067 }
1068
1069 if (available) {
1070 vacant[0] = x;
1071 vacant[1] = y;
1072 return true;
1073 }
1074 }
1075 }
1076
1077 return false;
1078 }
1079
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001080 /**
1081 * Update the array of occupied cells (mOccupied), and return a flattened copy of the array.
1082 */
1083 boolean[] getOccupiedCellsFlattened() {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001084 final boolean portrait = mPortrait;
1085 final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
1086 final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
1087 final boolean[][] occupied = mOccupied;
1088
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001089 findOccupiedCells(xCount, yCount, occupied, null, true);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001090
1091 final boolean[] flat = new boolean[xCount * yCount];
1092 for (int y = 0; y < yCount; y++) {
1093 for (int x = 0; x < xCount; x++) {
1094 flat[y * xCount + x] = occupied[x][y];
1095 }
1096 }
1097
1098 return flat;
1099 }
1100
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001101 /**
1102 * Update the array of occupied cells.
1103 * @param ignoreView If non-null, the space occupied by this View is treated as vacant
1104 * @param ignoreFolders If true, a cell occupied by a Folder is treated as vacant
1105 */
1106 private void findOccupiedCells(
1107 int xCount, int yCount, boolean[][] occupied, View ignoreView, boolean ignoreFolders) {
1108
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001109 for (int x = 0; x < xCount; x++) {
1110 for (int y = 0; y < yCount; y++) {
1111 occupied[x][y] = false;
1112 }
1113 }
1114
1115 int count = getChildCount();
1116 for (int i = 0; i < count; i++) {
1117 View child = getChildAt(i);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001118 if ((ignoreFolders && child instanceof Folder) || child.equals(ignoreView)) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001119 continue;
1120 }
1121 LayoutParams lp = (LayoutParams) child.getLayoutParams();
1122
1123 for (int x = lp.cellX; x < lp.cellX + lp.cellHSpan && x < xCount; x++) {
1124 for (int y = lp.cellY; y < lp.cellY + lp.cellVSpan && y < yCount; y++) {
1125 occupied[x][y] = true;
1126 }
1127 }
1128 }
1129 }
1130
1131 @Override
1132 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
1133 return new CellLayout.LayoutParams(getContext(), attrs);
1134 }
1135
1136 @Override
1137 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
1138 return p instanceof CellLayout.LayoutParams;
1139 }
1140
1141 @Override
1142 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
1143 return new CellLayout.LayoutParams(p);
1144 }
1145
Winson Chungaafa03c2010-06-11 17:34:16 -07001146 public static class CellLayoutAnimationController extends LayoutAnimationController {
1147 public CellLayoutAnimationController(Animation animation, float delay) {
1148 super(animation, delay);
1149 }
1150
1151 @Override
1152 protected long getDelayForView(View view) {
1153 return (int) (Math.random() * 150);
1154 }
1155 }
1156
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001157 public static class LayoutParams extends ViewGroup.MarginLayoutParams {
1158 /**
1159 * Horizontal location of the item in the grid.
1160 */
1161 @ViewDebug.ExportedProperty
1162 public int cellX;
1163
1164 /**
1165 * Vertical location of the item in the grid.
1166 */
1167 @ViewDebug.ExportedProperty
1168 public int cellY;
1169
1170 /**
1171 * Number of cells spanned horizontally by the item.
1172 */
1173 @ViewDebug.ExportedProperty
1174 public int cellHSpan;
1175
1176 /**
1177 * Number of cells spanned vertically by the item.
1178 */
1179 @ViewDebug.ExportedProperty
1180 public int cellVSpan;
Winson Chungaafa03c2010-06-11 17:34:16 -07001181
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001182 /**
1183 * Is this item currently being dragged
1184 */
1185 public boolean isDragging;
1186
1187 // X coordinate of the view in the layout.
1188 @ViewDebug.ExportedProperty
1189 int x;
1190 // Y coordinate of the view in the layout.
1191 @ViewDebug.ExportedProperty
1192 int y;
1193
Romain Guy84f296c2009-11-04 15:00:44 -08001194 boolean dropped;
Romain Guyfcb9e712009-10-02 16:06:52 -07001195
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001196 public LayoutParams(Context c, AttributeSet attrs) {
1197 super(c, attrs);
1198 cellHSpan = 1;
1199 cellVSpan = 1;
1200 }
1201
1202 public LayoutParams(ViewGroup.LayoutParams source) {
1203 super(source);
1204 cellHSpan = 1;
1205 cellVSpan = 1;
1206 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001207
1208 public LayoutParams(LayoutParams source) {
1209 super(source);
1210 this.cellX = source.cellX;
1211 this.cellY = source.cellY;
1212 this.cellHSpan = source.cellHSpan;
1213 this.cellVSpan = source.cellVSpan;
1214 }
1215
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001216 public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
Romain Guy8f19cdd2010-01-08 15:07:00 -08001217 super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001218 this.cellX = cellX;
1219 this.cellY = cellY;
1220 this.cellHSpan = cellHSpan;
1221 this.cellVSpan = cellVSpan;
1222 }
1223
1224 public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap,
1225 int hStartPadding, int vStartPadding) {
Winson Chungaafa03c2010-06-11 17:34:16 -07001226
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001227 final int myCellHSpan = cellHSpan;
1228 final int myCellVSpan = cellVSpan;
1229 final int myCellX = cellX;
1230 final int myCellY = cellY;
Winson Chungaafa03c2010-06-11 17:34:16 -07001231
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001232 width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) -
1233 leftMargin - rightMargin;
1234 height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) -
1235 topMargin - bottomMargin;
1236
1237 x = hStartPadding + myCellX * (cellWidth + widthGap) + leftMargin;
1238 y = vStartPadding + myCellY * (cellHeight + heightGap) + topMargin;
1239 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001240
1241 public String toString() {
1242 return "(" + this.cellX + ", " + this.cellY + ")";
1243 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001244 }
1245
1246 static final class CellInfo implements ContextMenu.ContextMenuInfo {
1247 /**
Winson Chungaafa03c2010-06-11 17:34:16 -07001248 * See View.AttachInfo.InvalidateInfo for futher explanations about the
1249 * recycling mechanism. In this case, we recycle the vacant cells
1250 * instances because up to several hundreds can be instanciated when the
1251 * user long presses an empty cell.
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001252 */
1253 static final class VacantCell {
1254 int cellX;
1255 int cellY;
1256 int spanX;
1257 int spanY;
1258
1259 // We can create up to 523 vacant cells on a 4x4 grid, 100 seems
1260 // like a reasonable compromise given the size of a VacantCell and
1261 // the fact that the user is not likely to touch an empty 4x4 grid
Winson Chungaafa03c2010-06-11 17:34:16 -07001262 // very often
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001263 private static final int POOL_LIMIT = 100;
1264 private static final Object sLock = new Object();
1265
1266 private static int sAcquiredCount = 0;
1267 private static VacantCell sRoot;
1268
1269 private VacantCell next;
1270
1271 static VacantCell acquire() {
1272 synchronized (sLock) {
1273 if (sRoot == null) {
1274 return new VacantCell();
1275 }
1276
1277 VacantCell info = sRoot;
1278 sRoot = info.next;
1279 sAcquiredCount--;
1280
1281 return info;
1282 }
1283 }
1284
1285 void release() {
1286 synchronized (sLock) {
1287 if (sAcquiredCount < POOL_LIMIT) {
1288 sAcquiredCount++;
1289 next = sRoot;
1290 sRoot = this;
1291 }
1292 }
1293 }
1294
1295 @Override
1296 public String toString() {
Winson Chungaafa03c2010-06-11 17:34:16 -07001297 return "VacantCell[x=" + cellX + ", y=" + cellY + ", spanX="
1298 + spanX + ", spanY=" + spanY + "]";
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001299 }
1300 }
1301
1302 View cell;
1303 int cellX;
1304 int cellY;
1305 int spanX;
1306 int spanY;
1307 int screen;
1308 boolean valid;
1309
1310 final ArrayList<VacantCell> vacantCells = new ArrayList<VacantCell>(VacantCell.POOL_LIMIT);
1311 int maxVacantSpanX;
1312 int maxVacantSpanXSpanY;
1313 int maxVacantSpanY;
1314 int maxVacantSpanYSpanX;
1315 final Rect current = new Rect();
1316
Romain Guy4c58c482009-05-12 17:35:41 -07001317 void clearVacantCells() {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001318 final ArrayList<VacantCell> list = vacantCells;
1319 final int count = list.size();
1320
Winson Chungaafa03c2010-06-11 17:34:16 -07001321 for (int i = 0; i < count; i++) {
1322 list.get(i).release();
1323 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001324
1325 list.clear();
1326 }
1327
1328 void findVacantCellsFromOccupied(boolean[] occupied, int xCount, int yCount) {
1329 if (cellX < 0 || cellY < 0) {
1330 maxVacantSpanX = maxVacantSpanXSpanY = Integer.MIN_VALUE;
1331 maxVacantSpanY = maxVacantSpanYSpanX = Integer.MIN_VALUE;
1332 clearVacantCells();
1333 return;
1334 }
1335
1336 final boolean[][] unflattened = new boolean[xCount][yCount];
1337 for (int y = 0; y < yCount; y++) {
1338 for (int x = 0; x < xCount; x++) {
1339 unflattened[x][y] = occupied[y * xCount + x];
1340 }
1341 }
1342 CellLayout.findIntersectingVacantCells(this, cellX, cellY, xCount, yCount, unflattened);
1343 }
1344
1345 /**
1346 * This method can be called only once! Calling #findVacantCellsFromOccupied will
1347 * restore the ability to call this method.
1348 *
1349 * Finds the upper-left coordinate of the first rectangle in the grid that can
1350 * hold a cell of the specified dimensions.
1351 *
1352 * @param cellXY The array that will contain the position of a vacant cell if such a cell
1353 * can be found.
1354 * @param spanX The horizontal span of the cell we want to find.
1355 * @param spanY The vertical span of the cell we want to find.
1356 *
1357 * @return True if a vacant cell of the specified dimension was found, false otherwise.
1358 */
1359 boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
Romain Guy4c58c482009-05-12 17:35:41 -07001360 return findCellForSpan(cellXY, spanX, spanY, true);
1361 }
1362
1363 boolean findCellForSpan(int[] cellXY, int spanX, int spanY, boolean clear) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001364 final ArrayList<VacantCell> list = vacantCells;
1365 final int count = list.size();
1366
1367 boolean found = false;
1368
Michael Jurka0e260592010-06-30 17:07:39 -07001369 // return the span represented by the CellInfo only there is no view there
1370 // (this.cell == null) and there is enough space
1371 if (this.cell == null && this.spanX >= spanX && this.spanY >= spanY) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001372 cellXY[0] = cellX;
1373 cellXY[1] = cellY;
1374 found = true;
1375 }
1376
1377 // Look for an exact match first
1378 for (int i = 0; i < count; i++) {
1379 VacantCell cell = list.get(i);
1380 if (cell.spanX == spanX && cell.spanY == spanY) {
1381 cellXY[0] = cell.cellX;
1382 cellXY[1] = cell.cellY;
1383 found = true;
1384 break;
1385 }
1386 }
1387
1388 // Look for the first cell large enough
1389 for (int i = 0; i < count; i++) {
1390 VacantCell cell = list.get(i);
1391 if (cell.spanX >= spanX && cell.spanY >= spanY) {
1392 cellXY[0] = cell.cellX;
1393 cellXY[1] = cell.cellY;
1394 found = true;
1395 break;
1396 }
1397 }
1398
Winson Chungaafa03c2010-06-11 17:34:16 -07001399 if (clear) {
1400 clearVacantCells();
1401 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001402
1403 return found;
1404 }
1405
1406 @Override
1407 public String toString() {
Winson Chungaafa03c2010-06-11 17:34:16 -07001408 return "Cell[view=" + (cell == null ? "null" : cell.getClass())
1409 + ", x=" + cellX + ", y=" + cellY + "]";
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001410 }
1411 }
Mike Cleronf8bbd342009-10-23 16:15:16 -07001412
1413 public boolean lastDownOnOccupiedCell() {
1414 return mLastDownOnOccupiedCell;
1415 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001416}