blob: 2488e6e326da915c91d840105280041886df62e9 [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
Winson Chungaafa03c2010-06-11 17:34:16 -070056 private int mLeftPadding;
57 private int mRightPadding;
58 private int mTopPadding;
59 private int mBottomPadding;
60
Adam Cohend22015c2010-07-26 22:02:18 -070061 private int mCountX;
62 private int mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080063
64 private int mWidthGap;
65 private int mHeightGap;
66
67 private final Rect mRect = new Rect();
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -070068 private final RectF mRectF = new RectF();
The Android Open Source Project31dd5032009-03-03 19:32:27 -080069 private final CellInfo mCellInfo = new CellInfo();
Winson Chungaafa03c2010-06-11 17:34:16 -070070
Patrick Dubroy6569f2c2010-07-12 14:25:18 -070071 // This is a temporary variable to prevent having to allocate a new object just to
72 // return an (x, y) value from helper functions. Do NOT use it to maintain other state.
73 private final int[] mTmpCellXY = new int[2];
74
The Android Open Source Project31dd5032009-03-03 19:32:27 -080075 boolean[][] mOccupied;
76
Michael Jurkadee05892010-07-27 10:01:56 -070077 private OnTouchListener mInterceptTouchListener;
78
79 // this is what the home screen fades to when it shrinks
80 // (ie in all apps and in home screen customize mode)
81 private Bitmap mDimmedBitmap;
82 private Canvas mDimmedBitmapCanvas;
83 private float mDimmedBitmapAlpha;
84 private boolean mDimmedBitmapDirty;
85 private final Paint mDimmedBitmapPaint = new Paint();
86 private final Rect mLayoutRect = new Rect();
87 private final Rect mDimmedBitmapRect = new Rect();
88
Patrick Dubroy6569f2c2010-07-12 14:25:18 -070089 private final RectF mDragRect = new RectF();
90
91 // When dragging, used to indicate a vacant drop location
92 private Drawable mVacantDrawable;
93
94 // When dragging, used to indicate an occupied drop location
95 private Drawable mOccupiedDrawable;
96
97 // Updated to point to mVacantDrawable or mOccupiedDrawable, as appropriate
98 private Drawable mDragRectDrawable;
99
100 // When a drag operation is in progress, holds the nearest cell to the touch point
101 private final int[] mDragCell = new int[2];
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800102
103 private boolean mDirtyTag;
Mike Cleronf8bbd342009-10-23 16:15:16 -0700104 private boolean mLastDownOnOccupiedCell = false;
Winson Chungaafa03c2010-06-11 17:34:16 -0700105
106 private final WallpaperManager mWallpaperManager;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800107
108 public CellLayout(Context context) {
109 this(context, null);
110 }
111
112 public CellLayout(Context context, AttributeSet attrs) {
113 this(context, attrs, 0);
114 }
115
116 public CellLayout(Context context, AttributeSet attrs, int defStyle) {
117 super(context, attrs, defStyle);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700118
119 // A ViewGroup usually does not draw, but CellLayout needs to draw a rectangle to show
120 // the user where a dragged item will land when dropped.
121 setWillNotDraw(false);
122 mVacantDrawable = getResources().getDrawable(R.drawable.rounded_rect_green);
123 mOccupiedDrawable = getResources().getDrawable(R.drawable.rounded_rect_red);
124
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800125 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0);
126
127 mCellWidth = a.getDimensionPixelSize(R.styleable.CellLayout_cellWidth, 10);
128 mCellHeight = a.getDimensionPixelSize(R.styleable.CellLayout_cellHeight, 10);
Winson Chungaafa03c2010-06-11 17:34:16 -0700129
Adam Cohend22015c2010-07-26 22:02:18 -0700130 mLeftPadding =
131 a.getDimensionPixelSize(R.styleable.CellLayout_xAxisStartPadding, 10);
132 mRightPadding =
133 a.getDimensionPixelSize(R.styleable.CellLayout_xAxisEndPadding, 10);
134 mTopPadding =
135 a.getDimensionPixelSize(R.styleable.CellLayout_yAxisStartPadding, 10);
136 mBottomPadding =
137 a.getDimensionPixelSize(R.styleable.CellLayout_yAxisEndPadding, 10);
Winson Chungaafa03c2010-06-11 17:34:16 -0700138
Adam Cohend22015c2010-07-26 22:02:18 -0700139 mCountX = LauncherModel.getCellCountX();
140 mCountY = LauncherModel.getCellCountY();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800141
142 a.recycle();
143
144 setAlwaysDrawnWithCacheEnabled(false);
145
Romain Guy84f296c2009-11-04 15:00:44 -0800146 mWallpaperManager = WallpaperManager.getInstance(getContext());
Michael Jurkadee05892010-07-27 10:01:56 -0700147
148 mDimmedBitmapPaint.setFilterBitmap(true);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800149 }
150
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700151 @Override
Romain Guya6abce82009-11-10 02:54:41 -0800152 public void dispatchDraw(Canvas canvas) {
153 super.dispatchDraw(canvas);
154 }
155
156 @Override
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700157 protected void onDraw(Canvas canvas) {
158 if (!mDragRect.isEmpty()) {
159 mDragRectDrawable.setBounds(
160 (int)mDragRect.left,
161 (int)mDragRect.top,
162 (int)mDragRect.right,
163 (int)mDragRect.bottom);
164 mDragRectDrawable.draw(canvas);
165 }
Michael Jurkadee05892010-07-27 10:01:56 -0700166 if (mDimmedBitmap != null && mDimmedBitmapAlpha > 0.0f) {
167 if (mDimmedBitmapDirty) {
168 updateDimmedBitmap();
169 mDimmedBitmapDirty = false;
170 }
171 mDimmedBitmapPaint.setAlpha((int) (mDimmedBitmapAlpha * 255));
172
173 canvas.drawBitmap(mDimmedBitmap, mDimmedBitmapRect, mLayoutRect, mDimmedBitmapPaint);
174 }
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700175 }
176
177 @Override
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700178 public void cancelLongPress() {
179 super.cancelLongPress();
180
181 // Cancel long press for all children
182 final int count = getChildCount();
183 for (int i = 0; i < count; i++) {
184 final View child = getChildAt(i);
185 child.cancelLongPress();
186 }
187 }
188
Michael Jurkadee05892010-07-27 10:01:56 -0700189 public void setOnInterceptTouchListener(View.OnTouchListener listener) {
190 mInterceptTouchListener = listener;
191 }
192
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800193 int getCountX() {
Adam Cohend22015c2010-07-26 22:02:18 -0700194 return mCountX;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800195 }
196
197 int getCountY() {
Adam Cohend22015c2010-07-26 22:02:18 -0700198 return mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800199 }
200
Winson Chungaafa03c2010-06-11 17:34:16 -0700201 // Takes canonical layout parameters
202 public boolean addViewToCellLayout(View child, int index, int childId, LayoutParams params) {
203 final LayoutParams lp = params;
204
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800205 // Generate an id for each view, this assumes we have at most 256x256 cells
206 // per workspace screen
Adam Cohend22015c2010-07-26 22:02:18 -0700207 if (lp.cellX >= 0 && lp.cellX <= mCountX - 1 && lp.cellY >= 0 && lp.cellY <= mCountY - 1) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700208 // If the horizontal or vertical span is set to -1, it is taken to
209 // mean that it spans the extent of the CellLayout
Adam Cohend22015c2010-07-26 22:02:18 -0700210 if (lp.cellHSpan < 0) lp.cellHSpan = mCountX;
211 if (lp.cellVSpan < 0) lp.cellVSpan = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800212
Winson Chungaafa03c2010-06-11 17:34:16 -0700213 child.setId(childId);
214
Michael Jurkadee05892010-07-27 10:01:56 -0700215 // We might be in the middle or end of shrinking/fading to a dimmed view
216 // Make sure this view's alpha is set the same as all the rest of the views
217 child.setAlpha(1.0f - mDimmedBitmapAlpha);
218
Winson Chungaafa03c2010-06-11 17:34:16 -0700219 addView(child, index, lp);
Michael Jurkadee05892010-07-27 10:01:56 -0700220
221 // next time we draw the dimmed bitmap we need to update it
222 mDimmedBitmapDirty = true;
Winson Chungaafa03c2010-06-11 17:34:16 -0700223 return true;
224 }
225 return false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800226 }
227
228 @Override
Michael Jurkadee05892010-07-27 10:01:56 -0700229 public void removeView(View view) {
230 super.removeView(view);
231 mDimmedBitmapDirty = true;
232 }
233
234 @Override
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800235 public void requestChildFocus(View child, View focused) {
236 super.requestChildFocus(child, focused);
237 if (child != null) {
238 Rect r = new Rect();
239 child.getDrawingRect(r);
240 requestRectangleOnScreen(r);
241 }
242 }
243
244 @Override
245 protected void onAttachedToWindow() {
246 super.onAttachedToWindow();
247 mCellInfo.screen = ((ViewGroup) getParent()).indexOfChild(this);
248 }
249
Michael Jurkaaf442092010-06-10 17:01:57 -0700250 public void setTagToCellInfoForPoint(int touchX, int touchY) {
251 final CellInfo cellInfo = mCellInfo;
252 final Rect frame = mRect;
253 final int x = touchX + mScrollX;
254 final int y = touchY + mScrollY;
255 final int count = getChildCount();
256
257 boolean found = false;
258 for (int i = count - 1; i >= 0; i--) {
259 final View child = getChildAt(i);
260
261 if ((child.getVisibility()) == VISIBLE || child.getAnimation() != null) {
262 child.getHitRect(frame);
263 if (frame.contains(x, y)) {
264 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
265 cellInfo.cell = child;
266 cellInfo.cellX = lp.cellX;
267 cellInfo.cellY = lp.cellY;
268 cellInfo.spanX = lp.cellHSpan;
269 cellInfo.spanY = lp.cellVSpan;
270 cellInfo.valid = true;
271 found = true;
272 mDirtyTag = false;
273 break;
274 }
275 }
276 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700277
Michael Jurkaaf442092010-06-10 17:01:57 -0700278 mLastDownOnOccupiedCell = found;
279
280 if (!found) {
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700281 final int cellXY[] = mTmpCellXY;
Michael Jurkaaf442092010-06-10 17:01:57 -0700282 pointToCellExact(x, y, cellXY);
283
284 final boolean portrait = mPortrait;
Adam Cohend22015c2010-07-26 22:02:18 -0700285 final int xCount = mCountX;
286 final int yCount = mCountY;
Michael Jurkaaf442092010-06-10 17:01:57 -0700287
288 final boolean[][] occupied = mOccupied;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700289 findOccupiedCells(xCount, yCount, occupied, null, true);
Michael Jurkaaf442092010-06-10 17:01:57 -0700290
291 cellInfo.cell = null;
292 cellInfo.cellX = cellXY[0];
293 cellInfo.cellY = cellXY[1];
294 cellInfo.spanX = 1;
295 cellInfo.spanY = 1;
296 cellInfo.valid = cellXY[0] >= 0 && cellXY[1] >= 0 && cellXY[0] < xCount &&
297 cellXY[1] < yCount && !occupied[cellXY[0]][cellXY[1]];
298
299 // Instead of finding the interesting vacant cells here, wait until a
300 // caller invokes getTag() to retrieve the result. Finding the vacant
301 // cells is a bit expensive and can generate many new objects, it's
302 // therefore better to defer it until we know we actually need it.
303
304 mDirtyTag = true;
305 }
306 setTag(cellInfo);
307 }
308
Winson Chungaafa03c2010-06-11 17:34:16 -0700309
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800310 @Override
311 public boolean onInterceptTouchEvent(MotionEvent ev) {
Michael Jurkadee05892010-07-27 10:01:56 -0700312 if (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev)) {
313 return true;
314 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800315 final int action = ev.getAction();
316 final CellInfo cellInfo = mCellInfo;
317
318 if (action == MotionEvent.ACTION_DOWN) {
Michael Jurkaaf442092010-06-10 17:01:57 -0700319 setTagToCellInfoForPoint((int) ev.getX(), (int) ev.getY());
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800320 } else if (action == MotionEvent.ACTION_UP) {
321 cellInfo.cell = null;
322 cellInfo.cellX = -1;
323 cellInfo.cellY = -1;
324 cellInfo.spanX = 0;
325 cellInfo.spanY = 0;
326 cellInfo.valid = false;
327 mDirtyTag = false;
328 setTag(cellInfo);
329 }
330
331 return false;
332 }
333
334 @Override
335 public CellInfo getTag() {
336 final CellInfo info = (CellInfo) super.getTag();
337 if (mDirtyTag && info.valid) {
Adam Cohend22015c2010-07-26 22:02:18 -0700338 final int xCount = mCountX;
339 final int yCount = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800340
341 final boolean[][] occupied = mOccupied;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700342 findOccupiedCells(xCount, yCount, occupied, null, true);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800343
344 findIntersectingVacantCells(info, info.cellX, info.cellY, xCount, yCount, occupied);
345
346 mDirtyTag = false;
347 }
348 return info;
349 }
350
Winson Chungaafa03c2010-06-11 17:34:16 -0700351 private static void findIntersectingVacantCells(CellInfo cellInfo, int x,
352 int y, int xCount, int yCount, boolean[][] occupied) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800353
354 cellInfo.maxVacantSpanX = Integer.MIN_VALUE;
355 cellInfo.maxVacantSpanXSpanY = Integer.MIN_VALUE;
356 cellInfo.maxVacantSpanY = Integer.MIN_VALUE;
357 cellInfo.maxVacantSpanYSpanX = Integer.MIN_VALUE;
358 cellInfo.clearVacantCells();
359
360 if (occupied[x][y]) {
361 return;
362 }
363
364 cellInfo.current.set(x, y, x, y);
365
366 findVacantCell(cellInfo.current, xCount, yCount, occupied, cellInfo);
367 }
368
369 private static void findVacantCell(Rect current, int xCount, int yCount, boolean[][] occupied,
370 CellInfo cellInfo) {
371
372 addVacantCell(current, cellInfo);
373
374 if (current.left > 0) {
375 if (isColumnEmpty(current.left - 1, current.top, current.bottom, occupied)) {
376 current.left--;
377 findVacantCell(current, xCount, yCount, occupied, cellInfo);
378 current.left++;
379 }
380 }
381
382 if (current.right < xCount - 1) {
383 if (isColumnEmpty(current.right + 1, current.top, current.bottom, occupied)) {
384 current.right++;
385 findVacantCell(current, xCount, yCount, occupied, cellInfo);
386 current.right--;
387 }
388 }
389
390 if (current.top > 0) {
391 if (isRowEmpty(current.top - 1, current.left, current.right, occupied)) {
392 current.top--;
393 findVacantCell(current, xCount, yCount, occupied, cellInfo);
394 current.top++;
395 }
396 }
397
398 if (current.bottom < yCount - 1) {
399 if (isRowEmpty(current.bottom + 1, current.left, current.right, occupied)) {
400 current.bottom++;
401 findVacantCell(current, xCount, yCount, occupied, cellInfo);
402 current.bottom--;
403 }
404 }
405 }
406
407 private static void addVacantCell(Rect current, CellInfo cellInfo) {
408 CellInfo.VacantCell cell = CellInfo.VacantCell.acquire();
409 cell.cellX = current.left;
410 cell.cellY = current.top;
411 cell.spanX = current.right - current.left + 1;
412 cell.spanY = current.bottom - current.top + 1;
413 if (cell.spanX > cellInfo.maxVacantSpanX) {
414 cellInfo.maxVacantSpanX = cell.spanX;
415 cellInfo.maxVacantSpanXSpanY = cell.spanY;
416 }
417 if (cell.spanY > cellInfo.maxVacantSpanY) {
418 cellInfo.maxVacantSpanY = cell.spanY;
419 cellInfo.maxVacantSpanYSpanX = cell.spanX;
420 }
421 cellInfo.vacantCells.add(cell);
422 }
423
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700424 /**
425 * Check if the column 'x' is empty from rows 'top' to 'bottom', inclusive.
426 */
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800427 private static boolean isColumnEmpty(int x, int top, int bottom, boolean[][] occupied) {
428 for (int y = top; y <= bottom; y++) {
429 if (occupied[x][y]) {
430 return false;
431 }
432 }
433 return true;
434 }
435
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700436 /**
437 * Check if the row 'y' is empty from columns 'left' to 'right', inclusive.
438 */
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800439 private static boolean isRowEmpty(int y, int left, int right, boolean[][] occupied) {
440 for (int x = left; x <= right; x++) {
441 if (occupied[x][y]) {
442 return false;
443 }
444 }
445 return true;
446 }
447
Jeff Sharkey70864282009-04-07 21:08:40 -0700448 CellInfo findAllVacantCells(boolean[] occupiedCells, View ignoreView) {
Adam Cohend22015c2010-07-26 22:02:18 -0700449 final int xCount = mCountX;
450 final int yCount = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800451
452 boolean[][] occupied = mOccupied;
453
454 if (occupiedCells != null) {
455 for (int y = 0; y < yCount; y++) {
456 for (int x = 0; x < xCount; x++) {
457 occupied[x][y] = occupiedCells[y * xCount + x];
458 }
459 }
460 } else {
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700461 findOccupiedCells(xCount, yCount, occupied, ignoreView, true);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800462 }
463
464 CellInfo cellInfo = new CellInfo();
465
466 cellInfo.cellX = -1;
467 cellInfo.cellY = -1;
468 cellInfo.spanY = 0;
469 cellInfo.spanX = 0;
470 cellInfo.maxVacantSpanX = Integer.MIN_VALUE;
471 cellInfo.maxVacantSpanXSpanY = Integer.MIN_VALUE;
472 cellInfo.maxVacantSpanY = Integer.MIN_VALUE;
473 cellInfo.maxVacantSpanYSpanX = Integer.MIN_VALUE;
474 cellInfo.screen = mCellInfo.screen;
475
476 Rect current = cellInfo.current;
477
478 for (int x = 0; x < xCount; x++) {
479 for (int y = 0; y < yCount; y++) {
480 if (!occupied[x][y]) {
481 current.set(x, y, x, y);
482 findVacantCell(current, xCount, yCount, occupied, cellInfo);
483 occupied[x][y] = true;
484 }
485 }
486 }
487
488 cellInfo.valid = cellInfo.vacantCells.size() > 0;
489
490 // Assume the caller will perform their own cell searching, otherwise we
491 // risk causing an unnecessary rebuild after findCellForSpan()
Winson Chungaafa03c2010-06-11 17:34:16 -0700492
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800493 return cellInfo;
494 }
495
496 /**
Winson Chungaafa03c2010-06-11 17:34:16 -0700497 * Given a point, return the cell that strictly encloses that point
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800498 * @param x X coordinate of the point
499 * @param y Y coordinate of the point
500 * @param result Array of 2 ints to hold the x and y coordinate of the cell
501 */
502 void pointToCellExact(int x, int y, int[] result) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700503 final int hStartPadding = getLeftPadding();
504 final int vStartPadding = getTopPadding();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800505
506 result[0] = (x - hStartPadding) / (mCellWidth + mWidthGap);
507 result[1] = (y - vStartPadding) / (mCellHeight + mHeightGap);
508
Adam Cohend22015c2010-07-26 22:02:18 -0700509 final int xAxis = mCountX;
510 final int yAxis = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800511
512 if (result[0] < 0) result[0] = 0;
513 if (result[0] >= xAxis) result[0] = xAxis - 1;
514 if (result[1] < 0) result[1] = 0;
515 if (result[1] >= yAxis) result[1] = yAxis - 1;
516 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700517
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800518 /**
519 * Given a point, return the cell that most closely encloses that point
520 * @param x X coordinate of the point
521 * @param y Y coordinate of the point
522 * @param result Array of 2 ints to hold the x and y coordinate of the cell
523 */
524 void pointToCellRounded(int x, int y, int[] result) {
525 pointToCellExact(x + (mCellWidth / 2), y + (mCellHeight / 2), result);
526 }
527
528 /**
529 * Given a cell coordinate, return the point that represents the upper left corner of that cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700530 *
531 * @param cellX X coordinate of the cell
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800532 * @param cellY Y coordinate of the cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700533 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800534 * @param result Array of 2 ints to hold the x and y coordinate of the point
535 */
536 void cellToPoint(int cellX, int cellY, int[] result) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700537 final int hStartPadding = getLeftPadding();
538 final int vStartPadding = getTopPadding();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800539
540 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap);
541 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap);
542 }
543
Romain Guy84f296c2009-11-04 15:00:44 -0800544 int getCellWidth() {
545 return mCellWidth;
546 }
547
548 int getCellHeight() {
549 return mCellHeight;
550 }
551
Romain Guy1a304a12009-11-10 00:02:32 -0800552 int getLeftPadding() {
Winson Chungaafa03c2010-06-11 17:34:16 -0700553 return mLeftPadding;
Romain Guy1a304a12009-11-10 00:02:32 -0800554 }
555
556 int getTopPadding() {
Winson Chungaafa03c2010-06-11 17:34:16 -0700557 return mTopPadding;
Romain Guy1a304a12009-11-10 00:02:32 -0800558 }
559
560 int getRightPadding() {
Winson Chungaafa03c2010-06-11 17:34:16 -0700561 return mRightPadding;
Romain Guy1a304a12009-11-10 00:02:32 -0800562 }
563
564 int getBottomPadding() {
Winson Chungaafa03c2010-06-11 17:34:16 -0700565 return mBottomPadding;
Romain Guy1a304a12009-11-10 00:02:32 -0800566 }
567
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800568 @Override
569 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
570 // TODO: currently ignoring padding
Winson Chungaafa03c2010-06-11 17:34:16 -0700571
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800572 int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
Winson Chungaafa03c2010-06-11 17:34:16 -0700573 int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
574
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800575 int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
576 int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
Winson Chungaafa03c2010-06-11 17:34:16 -0700577
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800578 if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
579 throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
580 }
581
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800582 final int cellWidth = mCellWidth;
583 final int cellHeight = mCellHeight;
584
Adam Cohend22015c2010-07-26 22:02:18 -0700585 if (mOccupied == null) {
586 mOccupied = new boolean[mCountX][mCountY];
Winson Chungaafa03c2010-06-11 17:34:16 -0700587 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800588
Adam Cohend22015c2010-07-26 22:02:18 -0700589 int numWidthGaps = mCountX - 1;
590 int numHeightGaps = mCountY - 1;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800591
Adam Cohend22015c2010-07-26 22:02:18 -0700592 int vSpaceLeft = heightSpecSize - mTopPadding
593 - mBottomPadding - (cellHeight * mCountY);
594 mHeightGap = vSpaceLeft / numHeightGaps;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800595
Adam Cohend22015c2010-07-26 22:02:18 -0700596 int hSpaceLeft = widthSpecSize - mLeftPadding
597 - mRightPadding - (cellWidth * mCountX);
598 mWidthGap = hSpaceLeft / numWidthGaps;
Winson Chungaafa03c2010-06-11 17:34:16 -0700599
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800600 int count = getChildCount();
601
602 for (int i = 0; i < count; i++) {
603 View child = getChildAt(i);
604 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Winson Chungaafa03c2010-06-11 17:34:16 -0700605 lp.setup(cellWidth, cellHeight, mWidthGap, mHeightGap,
606 mLeftPadding, mTopPadding);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800607
Winson Chungaafa03c2010-06-11 17:34:16 -0700608 int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.width,
609 MeasureSpec.EXACTLY);
610 int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(lp.height,
611 MeasureSpec.EXACTLY);
612
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800613 child.measure(childWidthMeasureSpec, childheightMeasureSpec);
614 }
615
616 setMeasuredDimension(widthSpecSize, heightSpecSize);
617 }
618
619 @Override
620 protected void onLayout(boolean changed, int l, int t, int r, int b) {
621 int count = getChildCount();
622
623 for (int i = 0; i < count; i++) {
624 View child = getChildAt(i);
625 if (child.getVisibility() != GONE) {
626
627 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
628
629 int childLeft = lp.x;
630 int childTop = lp.y;
631 child.layout(childLeft, childTop, childLeft + lp.width, childTop + lp.height);
Romain Guy84f296c2009-11-04 15:00:44 -0800632
633 if (lp.dropped) {
634 lp.dropped = false;
635
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700636 final int[] cellXY = mTmpCellXY;
Romain Guy06762ab2010-01-25 16:51:08 -0800637 getLocationOnScreen(cellXY);
Romain Guy84f296c2009-11-04 15:00:44 -0800638 mWallpaperManager.sendWallpaperCommand(getWindowToken(), "android.home.drop",
Romain Guy06762ab2010-01-25 16:51:08 -0800639 cellXY[0] + childLeft + lp.width / 2,
640 cellXY[1] + childTop + lp.height / 2, 0, null);
Romain Guy84f296c2009-11-04 15:00:44 -0800641 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800642 }
643 }
644 }
645
646 @Override
Michael Jurkadee05892010-07-27 10:01:56 -0700647 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
648 super.onSizeChanged(w, h, oldw, oldh);
649 mLayoutRect.set(0, 0, w, h);
650 mDimmedBitmapRect.set(0, 0, (int) (DIMMED_BITMAP_SCALE * w), (int) (DIMMED_BITMAP_SCALE * h));
651 }
652
653 @Override
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800654 protected void setChildrenDrawingCacheEnabled(boolean enabled) {
655 final int count = getChildCount();
656 for (int i = 0; i < count; i++) {
657 final View view = getChildAt(i);
658 view.setDrawingCacheEnabled(enabled);
659 // Update the drawing caches
Adam Powellfefa0ce2010-05-03 10:23:50 -0700660 view.buildDrawingCache(true);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800661 }
662 }
663
664 @Override
665 protected void setChildrenDrawnWithCacheEnabled(boolean enabled) {
666 super.setChildrenDrawnWithCacheEnabled(enabled);
667 }
668
Michael Jurkadee05892010-07-27 10:01:56 -0700669 public float getDimmedBitmapAlpha() {
670 return mDimmedBitmapAlpha;
671 }
672
673 public void setDimmedBitmapAlpha(float alpha) {
674 // If we're dimming the screen after it was not dimmed, refresh
675 // to allow for updated widgets. We don't continually refresh it
676 // after this point, however, as an optimization
677 if (mDimmedBitmapAlpha == 0.0f && alpha > 0.0f) {
678 updateDimmedBitmap();
679 }
680 mDimmedBitmapAlpha = alpha;
681 setChildrenAlpha(1.0f - mDimmedBitmapAlpha);
682 }
683
684 private void setChildrenAlpha(float alpha) {
685 for (int i = 0; i < getChildCount(); i++) {
686 getChildAt(i).setAlpha(alpha);
687 }
688 }
689
690 public void updateDimmedBitmap() {
691 if (mDimmedBitmap == null) {
692 mDimmedBitmap = Bitmap.createBitmap((int) (getWidth() * DIMMED_BITMAP_SCALE),
693 (int) (getHeight() * DIMMED_BITMAP_SCALE), Bitmap.Config.ARGB_8888);
694 mDimmedBitmapCanvas = new Canvas(mDimmedBitmap);
695 mDimmedBitmapCanvas.scale(DIMMED_BITMAP_SCALE, DIMMED_BITMAP_SCALE);
696 }
697 // clear the canvas
698 mDimmedBitmapCanvas.drawColor(0x00000000, PorterDuff.Mode.CLEAR);
699
700 // draw the screen into the bitmap
701 // just for drawing to the bitmap, make all the items on the screen opaque
702 setChildrenAlpha(1.0f);
703 dispatchDraw(mDimmedBitmapCanvas);
704 setChildrenAlpha(1.0f - mDimmedBitmapAlpha);
705
706 // make the bitmap 'dimmed' ie colored regions are dark grey,
707 // the rest is light grey
708 // We draw grey to the whole bitmap, but filter where we draw based on
709 // what regions are transparent or not (SRC_OUT), causing the intended effect
710
711 // First, draw light grey everywhere in the background (currently transparent) regions
712 // This will leave the regions with the widgets as mostly transparent
713 mDimmedBitmapCanvas.drawColor(0xCCCCCCCC, PorterDuff.Mode.SRC_OUT);
714 // Now, fill the the remaining transparent regions with dark grey
715 mDimmedBitmapCanvas.drawColor(0xCC333333, PorterDuff.Mode.SRC_OUT);
716 }
717
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700718 private boolean isVacant(int originX, int originY, int spanX, int spanY) {
719 for (int i = 0; i < spanY; i++) {
720 if (!isRowEmpty(originY + i, originX, originX + spanX - 1, mOccupied)) {
721 return false;
722 }
723 }
724 return true;
725 }
726
Patrick Dubroy440c3602010-07-13 17:50:32 -0700727 public View getChildAt(int x, int y) {
728 final int count = getChildCount();
729 for (int i = 0; i < count; i++) {
730 View child = getChildAt(i);
731 LayoutParams lp = (LayoutParams) child.getLayoutParams();
732
733 if ((lp.cellX <= x) && (x < lp.cellX + lp.cellHSpan) &&
734 (lp.cellY <= y) && (y < lp.cellY + lp.cellHSpan)) {
735 return child;
736 }
737 }
738 return null;
739 }
740
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700741 /**
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -0700742 * Estimate the size that a child with the given dimensions will take in the layout.
743 */
744 void estimateChildSize(int minWidth, int minHeight, int[] result) {
745 // Assuming it's placed at 0, 0, find where the bottom right cell will land
746 rectToCell(minWidth, minHeight, result);
747
748 // Then figure out the rect it will occupy
749 cellToRect(0, 0, result[0], result[1], mRectF);
750 result[0] = (int)mRectF.width();
751 result[1] = (int)mRectF.height();
752 }
753
754 /**
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700755 * Estimate where the top left cell of the dragged item will land if it is dropped.
756 *
757 * @param originX The X value of the top left corner of the item
758 * @param originY The Y value of the top left corner of the item
759 * @param spanX The number of horizontal cells that the item spans
760 * @param spanY The number of vertical cells that the item spans
761 * @param result The estimated drop cell X and Y.
762 */
763 void estimateDropCell(int originX, int originY, int spanX, int spanY, int[] result) {
Adam Cohend22015c2010-07-26 22:02:18 -0700764 final int countX = mCountX;
765 final int countY = mCountY;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700766
Patrick Dubroy976ebec2010-08-04 20:03:37 -0700767 // originX and originY give the top left of the cell, but pointToCellRounded
768 // compares center-to-center, so pass in the middle coordinates
769 pointToCellRounded(originX + (mCellWidth / 2), originY + (mCellHeight / 2), result);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700770
771 // If the item isn't fully on this screen, snap to the edges
772 int rightOverhang = result[0] + spanX - countX;
773 if (rightOverhang > 0) {
774 result[0] -= rightOverhang; // Snap to right
775 }
776 result[0] = Math.max(0, result[0]); // Snap to left
777 int bottomOverhang = result[1] + spanY - countY;
778 if (bottomOverhang > 0) {
779 result[1] -= bottomOverhang; // Snap to bottom
780 }
781 result[1] = Math.max(0, result[1]); // Snap to top
782 }
783
784 void visualizeDropLocation(View view, int originX, int originY, int spanX, int spanY) {
785 final int[] originCell = mDragCell;
786 final int[] cellXY = mTmpCellXY;
787 estimateDropCell(originX, originY, spanX, spanY, cellXY);
788
789 // Only recalculate the bounding rect when necessary
790 if (!Arrays.equals(cellXY, originCell)) {
791 originCell[0] = cellXY[0];
792 originCell[1] = cellXY[1];
793
794 // Find the top left corner of the rect the object will occupy
795 final int[] topLeft = mTmpCellXY;
796 cellToPoint(originCell[0], originCell[1], topLeft);
797 final int left = topLeft[0];
798 final int top = topLeft[1];
799
800 // Now find the bottom right
801 final int[] bottomRight = mTmpCellXY;
802 cellToPoint(originCell[0] + spanX - 1, originCell[1] + spanY - 1, bottomRight);
803 bottomRight[0] += mCellWidth;
804 bottomRight[1] += mCellHeight;
805
Adam Cohend22015c2010-07-26 22:02:18 -0700806 final int countX = mCountX;
807 final int countY = mCountY;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700808 // TODO: It's not necessary to do this every time, but it's not especially expensive
809 findOccupiedCells(countX, countY, mOccupied, view, false);
810
811 boolean vacant = isVacant(originCell[0], originCell[1], spanX, spanY);
812 mDragRectDrawable = vacant ? mVacantDrawable : mOccupiedDrawable;
813
814 // mDragRect will be rendered in onDraw()
815 mDragRect.set(left, top, bottomRight[0], bottomRight[1]);
816 invalidate();
817 }
818 }
819
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800820 /**
Jeff Sharkey70864282009-04-07 21:08:40 -0700821 * Find a vacant area that will fit the given bounds nearest the requested
822 * cell location. Uses Euclidean distance to score multiple vacant areas.
Winson Chungaafa03c2010-06-11 17:34:16 -0700823 *
Romain Guy51afc022009-05-04 18:03:43 -0700824 * @param pixelX The X location at which you want to search for a vacant area.
825 * @param pixelY The Y location at which you want to search for a vacant area.
Jeff Sharkey70864282009-04-07 21:08:40 -0700826 * @param spanX Horizontal span of the object.
827 * @param spanY Vertical span of the object.
828 * @param vacantCells Pre-computed set of vacant cells to search.
829 * @param recycle Previously returned value to possibly recycle.
830 * @return The X, Y cell of a vacant area that can contain this object,
831 * nearest the requested location.
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800832 */
Jeff Sharkey70864282009-04-07 21:08:40 -0700833 int[] findNearestVacantArea(int pixelX, int pixelY, int spanX, int spanY,
834 CellInfo vacantCells, int[] recycle) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700835
Jeff Sharkey70864282009-04-07 21:08:40 -0700836 // Keep track of best-scoring drop area
837 final int[] bestXY = recycle != null ? recycle : new int[2];
Jeff Sharkey70864282009-04-07 21:08:40 -0700838 double bestDistance = Double.MAX_VALUE;
Winson Chungaafa03c2010-06-11 17:34:16 -0700839
Jeff Sharkey70864282009-04-07 21:08:40 -0700840 // Bail early if vacant cells aren't valid
841 if (!vacantCells.valid) {
842 return null;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800843 }
844
Jeff Sharkey70864282009-04-07 21:08:40 -0700845 // Look across all vacant cells for best fit
846 final int size = vacantCells.vacantCells.size();
847 for (int i = 0; i < size; i++) {
848 final CellInfo.VacantCell cell = vacantCells.vacantCells.get(i);
Winson Chungaafa03c2010-06-11 17:34:16 -0700849
Jeff Sharkey70864282009-04-07 21:08:40 -0700850 // Reject if vacant cell isn't our exact size
851 if (cell.spanX != spanX || cell.spanY != spanY) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800852 continue;
853 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700854
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700855 // Score is distance from requested pixel to the top left of each cell
856 final int[] cellXY = mTmpCellXY;
Jeff Sharkey70864282009-04-07 21:08:40 -0700857 cellToPoint(cell.cellX, cell.cellY, cellXY);
Winson Chungaafa03c2010-06-11 17:34:16 -0700858
859 double distance = Math.sqrt(Math.pow(cellXY[0] - pixelX, 2)
860 + Math.pow(cellXY[1] - pixelY, 2));
Jeff Sharkey70864282009-04-07 21:08:40 -0700861 if (distance <= bestDistance) {
862 bestDistance = distance;
863 bestXY[0] = cell.cellX;
864 bestXY[1] = cell.cellY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800865 }
866 }
867
Winson Chungaafa03c2010-06-11 17:34:16 -0700868 // Return null if no suitable location found
Jeff Sharkey70864282009-04-07 21:08:40 -0700869 if (bestDistance < Double.MAX_VALUE) {
870 return bestXY;
871 } else {
872 return null;
873 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800874 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700875
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800876 /**
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700877 * Called when a drag and drop operation has finished (successfully or not).
878 */
879 void onDragComplete() {
880 // Invalidate the drag data
881 mDragCell[0] = -1;
882 mDragCell[1] = -1;
883
884 mDragRect.setEmpty();
885 invalidate();
886 }
887
888 /**
Winson Chungaafa03c2010-06-11 17:34:16 -0700889 * Mark a child as having been dropped.
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800890 *
891 * @param child The child that is being dropped
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800892 */
Winson Chungaafa03c2010-06-11 17:34:16 -0700893 void onDropChild(View child) {
Romain Guyd94533d2009-08-17 10:01:15 -0700894 if (child != null) {
895 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Romain Guyd94533d2009-08-17 10:01:15 -0700896 lp.isDragging = false;
Romain Guy84f296c2009-11-04 15:00:44 -0800897 lp.dropped = true;
Romain Guyd94533d2009-08-17 10:01:15 -0700898 mDragRect.setEmpty();
899 child.requestLayout();
Romain Guyd94533d2009-08-17 10:01:15 -0700900 }
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700901 onDragComplete();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800902 }
903
904 void onDropAborted(View child) {
905 if (child != null) {
906 ((LayoutParams) child.getLayoutParams()).isDragging = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800907 }
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700908 onDragComplete();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800909 }
910
911 /**
912 * Start dragging the specified child
Winson Chungaafa03c2010-06-11 17:34:16 -0700913 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800914 * @param child The child that is being dragged
915 */
916 void onDragChild(View child) {
917 LayoutParams lp = (LayoutParams) child.getLayoutParams();
918 lp.isDragging = true;
919 mDragRect.setEmpty();
920 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700921
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800922 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800923 * Computes a bounding rectangle for a range of cells
Winson Chungaafa03c2010-06-11 17:34:16 -0700924 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800925 * @param cellX X coordinate of upper left corner expressed as a cell position
926 * @param cellY Y coordinate of upper left corner expressed as a cell position
Winson Chungaafa03c2010-06-11 17:34:16 -0700927 * @param cellHSpan Width in cells
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800928 * @param cellVSpan Height in cells
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700929 * @param resultRect Rect into which to put the results
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800930 */
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700931 public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, RectF resultRect) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800932 final int cellWidth = mCellWidth;
933 final int cellHeight = mCellHeight;
934 final int widthGap = mWidthGap;
935 final int heightGap = mHeightGap;
Winson Chungaafa03c2010-06-11 17:34:16 -0700936
937 final int hStartPadding = getLeftPadding();
938 final int vStartPadding = getTopPadding();
939
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800940 int width = cellHSpan * cellWidth + ((cellHSpan - 1) * widthGap);
941 int height = cellVSpan * cellHeight + ((cellVSpan - 1) * heightGap);
942
943 int x = hStartPadding + cellX * (cellWidth + widthGap);
944 int y = vStartPadding + cellY * (cellHeight + heightGap);
Winson Chungaafa03c2010-06-11 17:34:16 -0700945
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700946 resultRect.set(x, y, x + width, y + height);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800947 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700948
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800949 /**
Winson Chungaafa03c2010-06-11 17:34:16 -0700950 * Computes the required horizontal and vertical cell spans to always
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800951 * fit the given rectangle.
Winson Chungaafa03c2010-06-11 17:34:16 -0700952 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800953 * @param width Width in pixels
954 * @param height Height in pixels
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -0700955 * @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 -0800956 */
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -0700957 public int[] rectToCell(int width, int height, int[] result) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800958 // Always assume we're working with the smallest span to make sure we
959 // reserve enough space in both orientations.
Joe Onorato79e56262009-09-21 15:23:04 -0400960 final Resources resources = getResources();
961 int actualWidth = resources.getDimensionPixelSize(R.dimen.workspace_cell_width);
962 int actualHeight = resources.getDimensionPixelSize(R.dimen.workspace_cell_height);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800963 int smallerSize = Math.min(actualWidth, actualHeight);
Joe Onorato79e56262009-09-21 15:23:04 -0400964
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800965 // Always round up to next largest cell
966 int spanX = (width + smallerSize) / smallerSize;
967 int spanY = (height + smallerSize) / smallerSize;
Joe Onorato79e56262009-09-21 15:23:04 -0400968
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -0700969 if (result == null) {
970 return new int[] { spanX, spanY };
971 }
972 result[0] = spanX;
973 result[1] = spanY;
974 return result;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800975 }
976
977 /**
978 * Find the first vacant cell, if there is one.
979 *
980 * @param vacant Holds the x and y coordinate of the vacant cell
981 * @param spanX Horizontal cell span.
982 * @param spanY Vertical cell span.
Winson Chungaafa03c2010-06-11 17:34:16 -0700983 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800984 * @return True if a vacant cell was found
985 */
986 public boolean getVacantCell(int[] vacant, int spanX, int spanY) {
Adam Cohend22015c2010-07-26 22:02:18 -0700987 final int xCount = mCountX;
988 final int yCount = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800989 final boolean[][] occupied = mOccupied;
990
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700991 findOccupiedCells(xCount, yCount, occupied, null, true);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800992
993 return findVacantCell(vacant, spanX, spanY, xCount, yCount, occupied);
994 }
995
996 static boolean findVacantCell(int[] vacant, int spanX, int spanY,
997 int xCount, int yCount, boolean[][] occupied) {
998
999 for (int x = 0; x < xCount; x++) {
1000 for (int y = 0; y < yCount; y++) {
1001 boolean available = !occupied[x][y];
1002out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) {
1003 for (int j = y; j < y + spanY - 1 && y < yCount; j++) {
1004 available = available && !occupied[i][j];
1005 if (!available) break out;
1006 }
1007 }
1008
1009 if (available) {
1010 vacant[0] = x;
1011 vacant[1] = y;
1012 return true;
1013 }
1014 }
1015 }
1016
1017 return false;
1018 }
1019
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001020 /**
1021 * Update the array of occupied cells (mOccupied), and return a flattened copy of the array.
1022 */
1023 boolean[] getOccupiedCellsFlattened() {
Adam Cohend22015c2010-07-26 22:02:18 -07001024 final int xCount = mCountX;
1025 final int yCount = mCountY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001026 final boolean[][] occupied = mOccupied;
1027
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001028 findOccupiedCells(xCount, yCount, occupied, null, true);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001029
1030 final boolean[] flat = new boolean[xCount * yCount];
1031 for (int y = 0; y < yCount; y++) {
1032 for (int x = 0; x < xCount; x++) {
1033 flat[y * xCount + x] = occupied[x][y];
1034 }
1035 }
1036
1037 return flat;
1038 }
1039
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001040 /**
1041 * Update the array of occupied cells.
1042 * @param ignoreView If non-null, the space occupied by this View is treated as vacant
1043 * @param ignoreFolders If true, a cell occupied by a Folder is treated as vacant
1044 */
1045 private void findOccupiedCells(
1046 int xCount, int yCount, boolean[][] occupied, View ignoreView, boolean ignoreFolders) {
1047
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001048 for (int x = 0; x < xCount; x++) {
1049 for (int y = 0; y < yCount; y++) {
1050 occupied[x][y] = false;
1051 }
1052 }
1053
1054 int count = getChildCount();
1055 for (int i = 0; i < count; i++) {
1056 View child = getChildAt(i);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001057 if ((ignoreFolders && child instanceof Folder) || child.equals(ignoreView)) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001058 continue;
1059 }
1060 LayoutParams lp = (LayoutParams) child.getLayoutParams();
1061
1062 for (int x = lp.cellX; x < lp.cellX + lp.cellHSpan && x < xCount; x++) {
1063 for (int y = lp.cellY; y < lp.cellY + lp.cellVSpan && y < yCount; y++) {
1064 occupied[x][y] = true;
1065 }
1066 }
1067 }
1068 }
1069
1070 @Override
1071 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
1072 return new CellLayout.LayoutParams(getContext(), attrs);
1073 }
1074
1075 @Override
1076 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
1077 return p instanceof CellLayout.LayoutParams;
1078 }
1079
1080 @Override
1081 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
1082 return new CellLayout.LayoutParams(p);
1083 }
1084
Winson Chungaafa03c2010-06-11 17:34:16 -07001085 public static class CellLayoutAnimationController extends LayoutAnimationController {
1086 public CellLayoutAnimationController(Animation animation, float delay) {
1087 super(animation, delay);
1088 }
1089
1090 @Override
1091 protected long getDelayForView(View view) {
1092 return (int) (Math.random() * 150);
1093 }
1094 }
1095
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001096 public static class LayoutParams extends ViewGroup.MarginLayoutParams {
1097 /**
1098 * Horizontal location of the item in the grid.
1099 */
1100 @ViewDebug.ExportedProperty
1101 public int cellX;
1102
1103 /**
1104 * Vertical location of the item in the grid.
1105 */
1106 @ViewDebug.ExportedProperty
1107 public int cellY;
1108
1109 /**
1110 * Number of cells spanned horizontally by the item.
1111 */
1112 @ViewDebug.ExportedProperty
1113 public int cellHSpan;
1114
1115 /**
1116 * Number of cells spanned vertically by the item.
1117 */
1118 @ViewDebug.ExportedProperty
1119 public int cellVSpan;
Winson Chungaafa03c2010-06-11 17:34:16 -07001120
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001121 /**
1122 * Is this item currently being dragged
1123 */
1124 public boolean isDragging;
1125
1126 // X coordinate of the view in the layout.
1127 @ViewDebug.ExportedProperty
1128 int x;
1129 // Y coordinate of the view in the layout.
1130 @ViewDebug.ExportedProperty
1131 int y;
1132
Romain Guy84f296c2009-11-04 15:00:44 -08001133 boolean dropped;
Romain Guyfcb9e712009-10-02 16:06:52 -07001134
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001135 public LayoutParams(Context c, AttributeSet attrs) {
1136 super(c, attrs);
1137 cellHSpan = 1;
1138 cellVSpan = 1;
1139 }
1140
1141 public LayoutParams(ViewGroup.LayoutParams source) {
1142 super(source);
1143 cellHSpan = 1;
1144 cellVSpan = 1;
1145 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001146
1147 public LayoutParams(LayoutParams source) {
1148 super(source);
1149 this.cellX = source.cellX;
1150 this.cellY = source.cellY;
1151 this.cellHSpan = source.cellHSpan;
1152 this.cellVSpan = source.cellVSpan;
1153 }
1154
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001155 public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
Romain Guy8f19cdd2010-01-08 15:07:00 -08001156 super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001157 this.cellX = cellX;
1158 this.cellY = cellY;
1159 this.cellHSpan = cellHSpan;
1160 this.cellVSpan = cellVSpan;
1161 }
1162
1163 public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap,
1164 int hStartPadding, int vStartPadding) {
Winson Chungaafa03c2010-06-11 17:34:16 -07001165
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001166 final int myCellHSpan = cellHSpan;
1167 final int myCellVSpan = cellVSpan;
1168 final int myCellX = cellX;
1169 final int myCellY = cellY;
Winson Chungaafa03c2010-06-11 17:34:16 -07001170
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001171 width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) -
1172 leftMargin - rightMargin;
1173 height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) -
1174 topMargin - bottomMargin;
1175
1176 x = hStartPadding + myCellX * (cellWidth + widthGap) + leftMargin;
1177 y = vStartPadding + myCellY * (cellHeight + heightGap) + topMargin;
1178 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001179
1180 public String toString() {
1181 return "(" + this.cellX + ", " + this.cellY + ")";
1182 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001183 }
1184
1185 static final class CellInfo implements ContextMenu.ContextMenuInfo {
1186 /**
Winson Chungaafa03c2010-06-11 17:34:16 -07001187 * See View.AttachInfo.InvalidateInfo for futher explanations about the
1188 * recycling mechanism. In this case, we recycle the vacant cells
1189 * instances because up to several hundreds can be instanciated when the
1190 * user long presses an empty cell.
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001191 */
1192 static final class VacantCell {
1193 int cellX;
1194 int cellY;
1195 int spanX;
1196 int spanY;
1197
1198 // We can create up to 523 vacant cells on a 4x4 grid, 100 seems
1199 // like a reasonable compromise given the size of a VacantCell and
1200 // the fact that the user is not likely to touch an empty 4x4 grid
Winson Chungaafa03c2010-06-11 17:34:16 -07001201 // very often
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001202 private static final int POOL_LIMIT = 100;
1203 private static final Object sLock = new Object();
1204
1205 private static int sAcquiredCount = 0;
1206 private static VacantCell sRoot;
1207
1208 private VacantCell next;
1209
1210 static VacantCell acquire() {
1211 synchronized (sLock) {
1212 if (sRoot == null) {
1213 return new VacantCell();
1214 }
1215
1216 VacantCell info = sRoot;
1217 sRoot = info.next;
1218 sAcquiredCount--;
1219
1220 return info;
1221 }
1222 }
1223
1224 void release() {
1225 synchronized (sLock) {
1226 if (sAcquiredCount < POOL_LIMIT) {
1227 sAcquiredCount++;
1228 next = sRoot;
1229 sRoot = this;
1230 }
1231 }
1232 }
1233
1234 @Override
1235 public String toString() {
Winson Chungaafa03c2010-06-11 17:34:16 -07001236 return "VacantCell[x=" + cellX + ", y=" + cellY + ", spanX="
1237 + spanX + ", spanY=" + spanY + "]";
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001238 }
1239 }
1240
1241 View cell;
1242 int cellX;
1243 int cellY;
1244 int spanX;
1245 int spanY;
1246 int screen;
1247 boolean valid;
1248
1249 final ArrayList<VacantCell> vacantCells = new ArrayList<VacantCell>(VacantCell.POOL_LIMIT);
1250 int maxVacantSpanX;
1251 int maxVacantSpanXSpanY;
1252 int maxVacantSpanY;
1253 int maxVacantSpanYSpanX;
1254 final Rect current = new Rect();
1255
Romain Guy4c58c482009-05-12 17:35:41 -07001256 void clearVacantCells() {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001257 final ArrayList<VacantCell> list = vacantCells;
1258 final int count = list.size();
1259
Winson Chungaafa03c2010-06-11 17:34:16 -07001260 for (int i = 0; i < count; i++) {
1261 list.get(i).release();
1262 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001263
1264 list.clear();
1265 }
1266
1267 void findVacantCellsFromOccupied(boolean[] occupied, int xCount, int yCount) {
1268 if (cellX < 0 || cellY < 0) {
1269 maxVacantSpanX = maxVacantSpanXSpanY = Integer.MIN_VALUE;
1270 maxVacantSpanY = maxVacantSpanYSpanX = Integer.MIN_VALUE;
1271 clearVacantCells();
1272 return;
1273 }
1274
1275 final boolean[][] unflattened = new boolean[xCount][yCount];
1276 for (int y = 0; y < yCount; y++) {
1277 for (int x = 0; x < xCount; x++) {
1278 unflattened[x][y] = occupied[y * xCount + x];
1279 }
1280 }
1281 CellLayout.findIntersectingVacantCells(this, cellX, cellY, xCount, yCount, unflattened);
1282 }
1283
1284 /**
1285 * This method can be called only once! Calling #findVacantCellsFromOccupied will
1286 * restore the ability to call this method.
1287 *
1288 * Finds the upper-left coordinate of the first rectangle in the grid that can
1289 * hold a cell of the specified dimensions.
1290 *
1291 * @param cellXY The array that will contain the position of a vacant cell if such a cell
1292 * can be found.
1293 * @param spanX The horizontal span of the cell we want to find.
1294 * @param spanY The vertical span of the cell we want to find.
1295 *
1296 * @return True if a vacant cell of the specified dimension was found, false otherwise.
1297 */
1298 boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
Romain Guy4c58c482009-05-12 17:35:41 -07001299 return findCellForSpan(cellXY, spanX, spanY, true);
1300 }
1301
1302 boolean findCellForSpan(int[] cellXY, int spanX, int spanY, boolean clear) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001303 final ArrayList<VacantCell> list = vacantCells;
1304 final int count = list.size();
1305
1306 boolean found = false;
1307
Michael Jurka0e260592010-06-30 17:07:39 -07001308 // return the span represented by the CellInfo only there is no view there
1309 // (this.cell == null) and there is enough space
1310 if (this.cell == null && this.spanX >= spanX && this.spanY >= spanY) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001311 cellXY[0] = cellX;
1312 cellXY[1] = cellY;
1313 found = true;
1314 }
1315
1316 // Look for an exact match first
1317 for (int i = 0; i < count; i++) {
1318 VacantCell cell = list.get(i);
1319 if (cell.spanX == spanX && cell.spanY == spanY) {
1320 cellXY[0] = cell.cellX;
1321 cellXY[1] = cell.cellY;
1322 found = true;
1323 break;
1324 }
1325 }
1326
1327 // Look for the first cell large enough
1328 for (int i = 0; i < count; i++) {
1329 VacantCell cell = list.get(i);
1330 if (cell.spanX >= spanX && cell.spanY >= spanY) {
1331 cellXY[0] = cell.cellX;
1332 cellXY[1] = cell.cellY;
1333 found = true;
1334 break;
1335 }
1336 }
1337
Winson Chungaafa03c2010-06-11 17:34:16 -07001338 if (clear) {
1339 clearVacantCells();
1340 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001341
1342 return found;
1343 }
1344
1345 @Override
1346 public String toString() {
Winson Chungaafa03c2010-06-11 17:34:16 -07001347 return "Cell[view=" + (cell == null ? "null" : cell.getClass())
1348 + ", x=" + cellX + ", y=" + cellY + "]";
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001349 }
1350 }
Mike Cleronf8bbd342009-10-23 16:15:16 -07001351
1352 public boolean lastDownOnOccupiedCell() {
1353 return mLastDownOnOccupiedCell;
1354 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001355}