blob: 7ca549e84fa55d9ee8ef27d15d7395d928402211 [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;
25import android.graphics.Canvas;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080026import android.graphics.Rect;
27import android.graphics.RectF;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -070028import android.graphics.drawable.Drawable;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080029import android.util.AttributeSet;
30import android.view.ContextMenu;
31import android.view.MotionEvent;
32import android.view.View;
33import android.view.ViewDebug;
34import android.view.ViewGroup;
Winson Chungaafa03c2010-06-11 17:34:16 -070035import android.view.animation.Animation;
36import android.view.animation.LayoutAnimationController;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080037
Patrick Dubroy6569f2c2010-07-12 14:25:18 -070038import java.util.ArrayList;
39import java.util.Arrays;
Romain Guyedcce092010-03-04 13:03:17 -080040
The Android Open Source Project31dd5032009-03-03 19:32:27 -080041public class CellLayout extends ViewGroup {
Winson Chungaafa03c2010-06-11 17:34:16 -070042 static final String TAG = "CellLayout";
43
The Android Open Source Project31dd5032009-03-03 19:32:27 -080044 private boolean mPortrait;
45
46 private int mCellWidth;
47 private int mCellHeight;
Winson Chungaafa03c2010-06-11 17:34:16 -070048
The Android Open Source Project31dd5032009-03-03 19:32:27 -080049 private int mLongAxisStartPadding;
50 private int mLongAxisEndPadding;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080051 private int mShortAxisStartPadding;
52 private int mShortAxisEndPadding;
53
Winson Chungaafa03c2010-06-11 17:34:16 -070054 private int mLeftPadding;
55 private int mRightPadding;
56 private int mTopPadding;
57 private int mBottomPadding;
58
The Android Open Source Project31dd5032009-03-03 19:32:27 -080059 private int mShortAxisCells;
60 private int mLongAxisCells;
61
62 private int mWidthGap;
63 private int mHeightGap;
64
65 private final Rect mRect = new Rect();
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -070066 private final RectF mRectF = new RectF();
The Android Open Source Project31dd5032009-03-03 19:32:27 -080067 private final CellInfo mCellInfo = new CellInfo();
Winson Chungaafa03c2010-06-11 17:34:16 -070068
Patrick Dubroy6569f2c2010-07-12 14:25:18 -070069 // This is a temporary variable to prevent having to allocate a new object just to
70 // return an (x, y) value from helper functions. Do NOT use it to maintain other state.
71 private final int[] mTmpCellXY = new int[2];
72
The Android Open Source Project31dd5032009-03-03 19:32:27 -080073 boolean[][] mOccupied;
74
Patrick Dubroy6569f2c2010-07-12 14:25:18 -070075 private final RectF mDragRect = new RectF();
76
77 // When dragging, used to indicate a vacant drop location
78 private Drawable mVacantDrawable;
79
80 // When dragging, used to indicate an occupied drop location
81 private Drawable mOccupiedDrawable;
82
83 // Updated to point to mVacantDrawable or mOccupiedDrawable, as appropriate
84 private Drawable mDragRectDrawable;
85
86 // When a drag operation is in progress, holds the nearest cell to the touch point
87 private final int[] mDragCell = new int[2];
The Android Open Source Project31dd5032009-03-03 19:32:27 -080088
89 private boolean mDirtyTag;
Mike Cleronf8bbd342009-10-23 16:15:16 -070090 private boolean mLastDownOnOccupiedCell = false;
Winson Chungaafa03c2010-06-11 17:34:16 -070091
92 private final WallpaperManager mWallpaperManager;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080093
94 public CellLayout(Context context) {
95 this(context, null);
96 }
97
98 public CellLayout(Context context, AttributeSet attrs) {
99 this(context, attrs, 0);
100 }
101
102 public CellLayout(Context context, AttributeSet attrs, int defStyle) {
103 super(context, attrs, defStyle);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700104
105 // A ViewGroup usually does not draw, but CellLayout needs to draw a rectangle to show
106 // the user where a dragged item will land when dropped.
107 setWillNotDraw(false);
108 mVacantDrawable = getResources().getDrawable(R.drawable.rounded_rect_green);
109 mOccupiedDrawable = getResources().getDrawable(R.drawable.rounded_rect_red);
110
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800111 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0);
112
113 mCellWidth = a.getDimensionPixelSize(R.styleable.CellLayout_cellWidth, 10);
114 mCellHeight = a.getDimensionPixelSize(R.styleable.CellLayout_cellHeight, 10);
Winson Chungaafa03c2010-06-11 17:34:16 -0700115
116 mLongAxisStartPadding =
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800117 a.getDimensionPixelSize(R.styleable.CellLayout_longAxisStartPadding, 10);
Winson Chungaafa03c2010-06-11 17:34:16 -0700118 mLongAxisEndPadding =
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800119 a.getDimensionPixelSize(R.styleable.CellLayout_longAxisEndPadding, 10);
120 mShortAxisStartPadding =
121 a.getDimensionPixelSize(R.styleable.CellLayout_shortAxisStartPadding, 10);
Winson Chungaafa03c2010-06-11 17:34:16 -0700122 mShortAxisEndPadding =
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800123 a.getDimensionPixelSize(R.styleable.CellLayout_shortAxisEndPadding, 10);
Winson Chungaafa03c2010-06-11 17:34:16 -0700124
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800125 mShortAxisCells = a.getInt(R.styleable.CellLayout_shortAxisCells, 4);
126 mLongAxisCells = a.getInt(R.styleable.CellLayout_longAxisCells, 4);
127
128 a.recycle();
129
130 setAlwaysDrawnWithCacheEnabled(false);
131
Romain Guy84f296c2009-11-04 15:00:44 -0800132 mWallpaperManager = WallpaperManager.getInstance(getContext());
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800133 }
134
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700135 @Override
Romain Guya6abce82009-11-10 02:54:41 -0800136 public void dispatchDraw(Canvas canvas) {
137 super.dispatchDraw(canvas);
138 }
139
140 @Override
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700141 protected void onDraw(Canvas canvas) {
142 if (!mDragRect.isEmpty()) {
143 mDragRectDrawable.setBounds(
144 (int)mDragRect.left,
145 (int)mDragRect.top,
146 (int)mDragRect.right,
147 (int)mDragRect.bottom);
148 mDragRectDrawable.draw(canvas);
149 }
150 }
151
152 @Override
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700153 public void cancelLongPress() {
154 super.cancelLongPress();
155
156 // Cancel long press for all children
157 final int count = getChildCount();
158 for (int i = 0; i < count; i++) {
159 final View child = getChildAt(i);
160 child.cancelLongPress();
161 }
162 }
163
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800164 int getCountX() {
165 return mPortrait ? mShortAxisCells : mLongAxisCells;
166 }
167
168 int getCountY() {
169 return mPortrait ? mLongAxisCells : mShortAxisCells;
170 }
171
Winson Chungaafa03c2010-06-11 17:34:16 -0700172 // Takes canonical layout parameters
173 public boolean addViewToCellLayout(View child, int index, int childId, LayoutParams params) {
174 final LayoutParams lp = params;
175
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800176 // Generate an id for each view, this assumes we have at most 256x256 cells
177 // per workspace screen
Winson Chungaafa03c2010-06-11 17:34:16 -0700178 if (lp.cellX >= 0 && lp.cellX <= getCountX() - 1 && lp.cellY >= 0 && lp.cellY <= getCountY() - 1) {
179 // If the horizontal or vertical span is set to -1, it is taken to
180 // mean that it spans the extent of the CellLayout
181 if (lp.cellHSpan < 0) lp.cellHSpan = getCountX();
182 if (lp.cellVSpan < 0) lp.cellVSpan = getCountY();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800183
Winson Chungaafa03c2010-06-11 17:34:16 -0700184 child.setId(childId);
185
186 addView(child, index, lp);
187 return true;
188 }
189 return false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800190 }
191
192 @Override
193 public void requestChildFocus(View child, View focused) {
194 super.requestChildFocus(child, focused);
195 if (child != null) {
196 Rect r = new Rect();
197 child.getDrawingRect(r);
198 requestRectangleOnScreen(r);
199 }
200 }
201
202 @Override
203 protected void onAttachedToWindow() {
204 super.onAttachedToWindow();
205 mCellInfo.screen = ((ViewGroup) getParent()).indexOfChild(this);
206 }
207
Michael Jurkaaf442092010-06-10 17:01:57 -0700208 public void setTagToCellInfoForPoint(int touchX, int touchY) {
209 final CellInfo cellInfo = mCellInfo;
210 final Rect frame = mRect;
211 final int x = touchX + mScrollX;
212 final int y = touchY + mScrollY;
213 final int count = getChildCount();
214
215 boolean found = false;
216 for (int i = count - 1; i >= 0; i--) {
217 final View child = getChildAt(i);
218
219 if ((child.getVisibility()) == VISIBLE || child.getAnimation() != null) {
220 child.getHitRect(frame);
221 if (frame.contains(x, y)) {
222 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
223 cellInfo.cell = child;
224 cellInfo.cellX = lp.cellX;
225 cellInfo.cellY = lp.cellY;
226 cellInfo.spanX = lp.cellHSpan;
227 cellInfo.spanY = lp.cellVSpan;
228 cellInfo.valid = true;
229 found = true;
230 mDirtyTag = false;
231 break;
232 }
233 }
234 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700235
Michael Jurkaaf442092010-06-10 17:01:57 -0700236 mLastDownOnOccupiedCell = found;
237
238 if (!found) {
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700239 final int cellXY[] = mTmpCellXY;
Michael Jurkaaf442092010-06-10 17:01:57 -0700240 pointToCellExact(x, y, cellXY);
241
242 final boolean portrait = mPortrait;
243 final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
244 final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
245
246 final boolean[][] occupied = mOccupied;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700247 findOccupiedCells(xCount, yCount, occupied, null, true);
Michael Jurkaaf442092010-06-10 17:01:57 -0700248
249 cellInfo.cell = null;
250 cellInfo.cellX = cellXY[0];
251 cellInfo.cellY = cellXY[1];
252 cellInfo.spanX = 1;
253 cellInfo.spanY = 1;
254 cellInfo.valid = cellXY[0] >= 0 && cellXY[1] >= 0 && cellXY[0] < xCount &&
255 cellXY[1] < yCount && !occupied[cellXY[0]][cellXY[1]];
256
257 // Instead of finding the interesting vacant cells here, wait until a
258 // caller invokes getTag() to retrieve the result. Finding the vacant
259 // cells is a bit expensive and can generate many new objects, it's
260 // therefore better to defer it until we know we actually need it.
261
262 mDirtyTag = true;
263 }
264 setTag(cellInfo);
265 }
266
Winson Chungaafa03c2010-06-11 17:34:16 -0700267
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800268 @Override
269 public boolean onInterceptTouchEvent(MotionEvent ev) {
270 final int action = ev.getAction();
271 final CellInfo cellInfo = mCellInfo;
272
273 if (action == MotionEvent.ACTION_DOWN) {
Michael Jurkaaf442092010-06-10 17:01:57 -0700274 setTagToCellInfoForPoint((int) ev.getX(), (int) ev.getY());
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800275 } else if (action == MotionEvent.ACTION_UP) {
276 cellInfo.cell = null;
277 cellInfo.cellX = -1;
278 cellInfo.cellY = -1;
279 cellInfo.spanX = 0;
280 cellInfo.spanY = 0;
281 cellInfo.valid = false;
282 mDirtyTag = false;
283 setTag(cellInfo);
284 }
285
286 return false;
287 }
288
289 @Override
290 public CellInfo getTag() {
291 final CellInfo info = (CellInfo) super.getTag();
292 if (mDirtyTag && info.valid) {
293 final boolean portrait = mPortrait;
294 final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
295 final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
296
297 final boolean[][] occupied = mOccupied;
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700298 findOccupiedCells(xCount, yCount, occupied, null, true);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800299
300 findIntersectingVacantCells(info, info.cellX, info.cellY, xCount, yCount, occupied);
301
302 mDirtyTag = false;
303 }
304 return info;
305 }
306
Winson Chungaafa03c2010-06-11 17:34:16 -0700307 private static void findIntersectingVacantCells(CellInfo cellInfo, int x,
308 int y, int xCount, int yCount, boolean[][] occupied) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800309
310 cellInfo.maxVacantSpanX = Integer.MIN_VALUE;
311 cellInfo.maxVacantSpanXSpanY = Integer.MIN_VALUE;
312 cellInfo.maxVacantSpanY = Integer.MIN_VALUE;
313 cellInfo.maxVacantSpanYSpanX = Integer.MIN_VALUE;
314 cellInfo.clearVacantCells();
315
316 if (occupied[x][y]) {
317 return;
318 }
319
320 cellInfo.current.set(x, y, x, y);
321
322 findVacantCell(cellInfo.current, xCount, yCount, occupied, cellInfo);
323 }
324
325 private static void findVacantCell(Rect current, int xCount, int yCount, boolean[][] occupied,
326 CellInfo cellInfo) {
327
328 addVacantCell(current, cellInfo);
329
330 if (current.left > 0) {
331 if (isColumnEmpty(current.left - 1, current.top, current.bottom, occupied)) {
332 current.left--;
333 findVacantCell(current, xCount, yCount, occupied, cellInfo);
334 current.left++;
335 }
336 }
337
338 if (current.right < xCount - 1) {
339 if (isColumnEmpty(current.right + 1, current.top, current.bottom, occupied)) {
340 current.right++;
341 findVacantCell(current, xCount, yCount, occupied, cellInfo);
342 current.right--;
343 }
344 }
345
346 if (current.top > 0) {
347 if (isRowEmpty(current.top - 1, current.left, current.right, occupied)) {
348 current.top--;
349 findVacantCell(current, xCount, yCount, occupied, cellInfo);
350 current.top++;
351 }
352 }
353
354 if (current.bottom < yCount - 1) {
355 if (isRowEmpty(current.bottom + 1, current.left, current.right, occupied)) {
356 current.bottom++;
357 findVacantCell(current, xCount, yCount, occupied, cellInfo);
358 current.bottom--;
359 }
360 }
361 }
362
363 private static void addVacantCell(Rect current, CellInfo cellInfo) {
364 CellInfo.VacantCell cell = CellInfo.VacantCell.acquire();
365 cell.cellX = current.left;
366 cell.cellY = current.top;
367 cell.spanX = current.right - current.left + 1;
368 cell.spanY = current.bottom - current.top + 1;
369 if (cell.spanX > cellInfo.maxVacantSpanX) {
370 cellInfo.maxVacantSpanX = cell.spanX;
371 cellInfo.maxVacantSpanXSpanY = cell.spanY;
372 }
373 if (cell.spanY > cellInfo.maxVacantSpanY) {
374 cellInfo.maxVacantSpanY = cell.spanY;
375 cellInfo.maxVacantSpanYSpanX = cell.spanX;
376 }
377 cellInfo.vacantCells.add(cell);
378 }
379
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700380 /**
381 * Check if the column 'x' is empty from rows 'top' to 'bottom', inclusive.
382 */
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800383 private static boolean isColumnEmpty(int x, int top, int bottom, boolean[][] occupied) {
384 for (int y = top; y <= bottom; y++) {
385 if (occupied[x][y]) {
386 return false;
387 }
388 }
389 return true;
390 }
391
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700392 /**
393 * Check if the row 'y' is empty from columns 'left' to 'right', inclusive.
394 */
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800395 private static boolean isRowEmpty(int y, int left, int right, boolean[][] occupied) {
396 for (int x = left; x <= right; x++) {
397 if (occupied[x][y]) {
398 return false;
399 }
400 }
401 return true;
402 }
403
Jeff Sharkey70864282009-04-07 21:08:40 -0700404 CellInfo findAllVacantCells(boolean[] occupiedCells, View ignoreView) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800405 final boolean portrait = mPortrait;
406 final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
407 final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
408
409 boolean[][] occupied = mOccupied;
410
411 if (occupiedCells != null) {
412 for (int y = 0; y < yCount; y++) {
413 for (int x = 0; x < xCount; x++) {
414 occupied[x][y] = occupiedCells[y * xCount + x];
415 }
416 }
417 } else {
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700418 findOccupiedCells(xCount, yCount, occupied, ignoreView, true);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800419 }
420
421 CellInfo cellInfo = new CellInfo();
422
423 cellInfo.cellX = -1;
424 cellInfo.cellY = -1;
425 cellInfo.spanY = 0;
426 cellInfo.spanX = 0;
427 cellInfo.maxVacantSpanX = Integer.MIN_VALUE;
428 cellInfo.maxVacantSpanXSpanY = Integer.MIN_VALUE;
429 cellInfo.maxVacantSpanY = Integer.MIN_VALUE;
430 cellInfo.maxVacantSpanYSpanX = Integer.MIN_VALUE;
431 cellInfo.screen = mCellInfo.screen;
432
433 Rect current = cellInfo.current;
434
435 for (int x = 0; x < xCount; x++) {
436 for (int y = 0; y < yCount; y++) {
437 if (!occupied[x][y]) {
438 current.set(x, y, x, y);
439 findVacantCell(current, xCount, yCount, occupied, cellInfo);
440 occupied[x][y] = true;
441 }
442 }
443 }
444
445 cellInfo.valid = cellInfo.vacantCells.size() > 0;
446
447 // Assume the caller will perform their own cell searching, otherwise we
448 // risk causing an unnecessary rebuild after findCellForSpan()
Winson Chungaafa03c2010-06-11 17:34:16 -0700449
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800450 return cellInfo;
451 }
452
453 /**
Winson Chungaafa03c2010-06-11 17:34:16 -0700454 * Given a point, return the cell that strictly encloses that point
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800455 * @param x X coordinate of the point
456 * @param y Y coordinate of the point
457 * @param result Array of 2 ints to hold the x and y coordinate of the cell
458 */
459 void pointToCellExact(int x, int y, int[] result) {
460 final boolean portrait = mPortrait;
Winson Chungaafa03c2010-06-11 17:34:16 -0700461
462 final int hStartPadding = getLeftPadding();
463 final int vStartPadding = getTopPadding();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800464
465 result[0] = (x - hStartPadding) / (mCellWidth + mWidthGap);
466 result[1] = (y - vStartPadding) / (mCellHeight + mHeightGap);
467
468 final int xAxis = portrait ? mShortAxisCells : mLongAxisCells;
469 final int yAxis = portrait ? mLongAxisCells : mShortAxisCells;
470
471 if (result[0] < 0) result[0] = 0;
472 if (result[0] >= xAxis) result[0] = xAxis - 1;
473 if (result[1] < 0) result[1] = 0;
474 if (result[1] >= yAxis) result[1] = yAxis - 1;
475 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700476
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800477 /**
478 * Given a point, return the cell that most closely encloses that point
479 * @param x X coordinate of the point
480 * @param y Y coordinate of the point
481 * @param result Array of 2 ints to hold the x and y coordinate of the cell
482 */
483 void pointToCellRounded(int x, int y, int[] result) {
484 pointToCellExact(x + (mCellWidth / 2), y + (mCellHeight / 2), result);
485 }
486
487 /**
488 * Given a cell coordinate, return the point that represents the upper left corner of that cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700489 *
490 * @param cellX X coordinate of the cell
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800491 * @param cellY Y coordinate of the cell
Winson Chungaafa03c2010-06-11 17:34:16 -0700492 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800493 * @param result Array of 2 ints to hold the x and y coordinate of the point
494 */
495 void cellToPoint(int cellX, int cellY, int[] result) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700496 final int hStartPadding = getLeftPadding();
497 final int vStartPadding = getTopPadding();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800498
499 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap);
500 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap);
501 }
502
Romain Guy84f296c2009-11-04 15:00:44 -0800503 int getCellWidth() {
504 return mCellWidth;
505 }
506
507 int getCellHeight() {
508 return mCellHeight;
509 }
510
Romain Guy1a304a12009-11-10 00:02:32 -0800511 int getLeftPadding() {
Winson Chungaafa03c2010-06-11 17:34:16 -0700512 return mLeftPadding;
Romain Guy1a304a12009-11-10 00:02:32 -0800513 }
514
515 int getTopPadding() {
Winson Chungaafa03c2010-06-11 17:34:16 -0700516 return mTopPadding;
Romain Guy1a304a12009-11-10 00:02:32 -0800517 }
518
519 int getRightPadding() {
Winson Chungaafa03c2010-06-11 17:34:16 -0700520 return mRightPadding;
Romain Guy1a304a12009-11-10 00:02:32 -0800521 }
522
523 int getBottomPadding() {
Winson Chungaafa03c2010-06-11 17:34:16 -0700524 return mBottomPadding;
Romain Guy1a304a12009-11-10 00:02:32 -0800525 }
526
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800527 @Override
528 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
529 // TODO: currently ignoring padding
Winson Chungaafa03c2010-06-11 17:34:16 -0700530
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800531 int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
Winson Chungaafa03c2010-06-11 17:34:16 -0700532 int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
533
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800534 int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
535 int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
Winson Chungaafa03c2010-06-11 17:34:16 -0700536
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800537 if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
538 throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
539 }
540
541 final int shortAxisCells = mShortAxisCells;
542 final int longAxisCells = mLongAxisCells;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800543 final int cellWidth = mCellWidth;
544 final int cellHeight = mCellHeight;
545
Winson Chungaafa03c2010-06-11 17:34:16 -0700546 boolean portrait = heightSpecSize > widthSpecSize;
547 if (portrait != mPortrait || mOccupied == null) {
548 if (portrait) {
549 mOccupied = new boolean[mShortAxisCells][mLongAxisCells];
550 } else {
551 mOccupied = new boolean[mLongAxisCells][mShortAxisCells];
552 }
553 }
554 mPortrait = portrait;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800555
556 int numShortGaps = shortAxisCells - 1;
557 int numLongGaps = longAxisCells - 1;
558
559 if (mPortrait) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700560 int vSpaceLeft = heightSpecSize - mLongAxisStartPadding
561 - mLongAxisEndPadding - (cellHeight * longAxisCells);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800562 mHeightGap = vSpaceLeft / numLongGaps;
563
Winson Chungaafa03c2010-06-11 17:34:16 -0700564 int hSpaceLeft = widthSpecSize - mShortAxisStartPadding
565 - mShortAxisEndPadding - (cellWidth * shortAxisCells);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800566 if (numShortGaps > 0) {
567 mWidthGap = hSpaceLeft / numShortGaps;
568 } else {
569 mWidthGap = 0;
570 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700571
572 if (LauncherApplication.isInPlaceRotationEnabled()) {
573 mWidthGap = mHeightGap = Math.min(mHeightGap, mWidthGap);
574 mLeftPadding = mRightPadding = (widthSpecSize - cellWidth
575 * shortAxisCells - (shortAxisCells - 1) * mWidthGap) / 2;
576 mTopPadding = mBottomPadding = (heightSpecSize - cellHeight
577 * longAxisCells - (longAxisCells - 1) * mHeightGap) / 2;
578 } else {
579 mLeftPadding = mShortAxisStartPadding;
580 mRightPadding = mShortAxisEndPadding;
581 mTopPadding = mLongAxisStartPadding;
582 mBottomPadding = mLongAxisEndPadding;
583 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800584 } else {
Winson Chungaafa03c2010-06-11 17:34:16 -0700585 int hSpaceLeft = widthSpecSize - mLongAxisStartPadding
586 - mLongAxisEndPadding - (cellWidth * longAxisCells);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800587 mWidthGap = hSpaceLeft / numLongGaps;
588
Winson Chungaafa03c2010-06-11 17:34:16 -0700589 int vSpaceLeft = heightSpecSize - mShortAxisStartPadding
590 - mShortAxisEndPadding - (cellHeight * shortAxisCells);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800591 if (numShortGaps > 0) {
592 mHeightGap = vSpaceLeft / numShortGaps;
593 } else {
594 mHeightGap = 0;
595 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700596
597 if (LauncherApplication.isScreenXLarge()) {
598 mWidthGap = mHeightGap = Math.min(mHeightGap, mWidthGap);
599 mLeftPadding = mRightPadding = (widthSpecSize - cellWidth
600 * longAxisCells - (longAxisCells - 1) * mWidthGap) / 2 ;
601 mTopPadding = mBottomPadding = (heightSpecSize - cellHeight
602 * shortAxisCells - (shortAxisCells - 1) * mHeightGap) / 2;
603 } else {
604 mLeftPadding = mLongAxisStartPadding;
605 mRightPadding = mLongAxisEndPadding;
606 mTopPadding = mShortAxisStartPadding;
607 mBottomPadding = mShortAxisEndPadding;
608 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800609 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800610 int count = getChildCount();
611
612 for (int i = 0; i < count; i++) {
613 View child = getChildAt(i);
614 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Winson Chungaafa03c2010-06-11 17:34:16 -0700615 lp.setup(cellWidth, cellHeight, mWidthGap, mHeightGap,
616 mLeftPadding, mTopPadding);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800617
Winson Chungaafa03c2010-06-11 17:34:16 -0700618 int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.width,
619 MeasureSpec.EXACTLY);
620 int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(lp.height,
621 MeasureSpec.EXACTLY);
622
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800623 child.measure(childWidthMeasureSpec, childheightMeasureSpec);
624 }
625
626 setMeasuredDimension(widthSpecSize, heightSpecSize);
627 }
628
629 @Override
630 protected void onLayout(boolean changed, int l, int t, int r, int b) {
631 int count = getChildCount();
632
633 for (int i = 0; i < count; i++) {
634 View child = getChildAt(i);
635 if (child.getVisibility() != GONE) {
636
637 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
638
639 int childLeft = lp.x;
640 int childTop = lp.y;
641 child.layout(childLeft, childTop, childLeft + lp.width, childTop + lp.height);
Romain Guy84f296c2009-11-04 15:00:44 -0800642
643 if (lp.dropped) {
644 lp.dropped = false;
645
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700646 final int[] cellXY = mTmpCellXY;
Romain Guy06762ab2010-01-25 16:51:08 -0800647 getLocationOnScreen(cellXY);
Romain Guy84f296c2009-11-04 15:00:44 -0800648 mWallpaperManager.sendWallpaperCommand(getWindowToken(), "android.home.drop",
Romain Guy06762ab2010-01-25 16:51:08 -0800649 cellXY[0] + childLeft + lp.width / 2,
650 cellXY[1] + childTop + lp.height / 2, 0, null);
Romain Guy84f296c2009-11-04 15:00:44 -0800651 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800652 }
653 }
654 }
655
656 @Override
657 protected void setChildrenDrawingCacheEnabled(boolean enabled) {
658 final int count = getChildCount();
659 for (int i = 0; i < count; i++) {
660 final View view = getChildAt(i);
661 view.setDrawingCacheEnabled(enabled);
662 // Update the drawing caches
Adam Powellfefa0ce2010-05-03 10:23:50 -0700663 view.buildDrawingCache(true);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800664 }
665 }
666
667 @Override
668 protected void setChildrenDrawnWithCacheEnabled(boolean enabled) {
669 super.setChildrenDrawnWithCacheEnabled(enabled);
670 }
671
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700672 private boolean isVacant(int originX, int originY, int spanX, int spanY) {
673 for (int i = 0; i < spanY; i++) {
674 if (!isRowEmpty(originY + i, originX, originX + spanX - 1, mOccupied)) {
675 return false;
676 }
677 }
678 return true;
679 }
680
Patrick Dubroy440c3602010-07-13 17:50:32 -0700681 public View getChildAt(int x, int y) {
682 final int count = getChildCount();
683 for (int i = 0; i < count; i++) {
684 View child = getChildAt(i);
685 LayoutParams lp = (LayoutParams) child.getLayoutParams();
686
687 if ((lp.cellX <= x) && (x < lp.cellX + lp.cellHSpan) &&
688 (lp.cellY <= y) && (y < lp.cellY + lp.cellHSpan)) {
689 return child;
690 }
691 }
692 return null;
693 }
694
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700695 /**
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -0700696 * Estimate the size that a child with the given dimensions will take in the layout.
697 */
698 void estimateChildSize(int minWidth, int minHeight, int[] result) {
699 // Assuming it's placed at 0, 0, find where the bottom right cell will land
700 rectToCell(minWidth, minHeight, result);
701
702 // Then figure out the rect it will occupy
703 cellToRect(0, 0, result[0], result[1], mRectF);
704 result[0] = (int)mRectF.width();
705 result[1] = (int)mRectF.height();
706 }
707
708 /**
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700709 * Estimate where the top left cell of the dragged item will land if it is dropped.
710 *
711 * @param originX The X value of the top left corner of the item
712 * @param originY The Y value of the top left corner of the item
713 * @param spanX The number of horizontal cells that the item spans
714 * @param spanY The number of vertical cells that the item spans
715 * @param result The estimated drop cell X and Y.
716 */
717 void estimateDropCell(int originX, int originY, int spanX, int spanY, int[] result) {
718 final int countX = getCountX();
719 final int countY = getCountY();
720
Patrick Dubroy6d5c6ec2010-07-14 11:14:47 -0700721 pointToCellRounded(originX, originY, result);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700722
723 // If the item isn't fully on this screen, snap to the edges
724 int rightOverhang = result[0] + spanX - countX;
725 if (rightOverhang > 0) {
726 result[0] -= rightOverhang; // Snap to right
727 }
728 result[0] = Math.max(0, result[0]); // Snap to left
729 int bottomOverhang = result[1] + spanY - countY;
730 if (bottomOverhang > 0) {
731 result[1] -= bottomOverhang; // Snap to bottom
732 }
733 result[1] = Math.max(0, result[1]); // Snap to top
734 }
735
736 void visualizeDropLocation(View view, int originX, int originY, int spanX, int spanY) {
737 final int[] originCell = mDragCell;
738 final int[] cellXY = mTmpCellXY;
739 estimateDropCell(originX, originY, spanX, spanY, cellXY);
740
741 // Only recalculate the bounding rect when necessary
742 if (!Arrays.equals(cellXY, originCell)) {
743 originCell[0] = cellXY[0];
744 originCell[1] = cellXY[1];
745
746 // Find the top left corner of the rect the object will occupy
747 final int[] topLeft = mTmpCellXY;
748 cellToPoint(originCell[0], originCell[1], topLeft);
749 final int left = topLeft[0];
750 final int top = topLeft[1];
751
752 // Now find the bottom right
753 final int[] bottomRight = mTmpCellXY;
754 cellToPoint(originCell[0] + spanX - 1, originCell[1] + spanY - 1, bottomRight);
755 bottomRight[0] += mCellWidth;
756 bottomRight[1] += mCellHeight;
757
758 final int countX = mPortrait ? mShortAxisCells : mLongAxisCells;
759 final int countY = mPortrait ? mLongAxisCells : mShortAxisCells;
760 // TODO: It's not necessary to do this every time, but it's not especially expensive
761 findOccupiedCells(countX, countY, mOccupied, view, false);
762
763 boolean vacant = isVacant(originCell[0], originCell[1], spanX, spanY);
764 mDragRectDrawable = vacant ? mVacantDrawable : mOccupiedDrawable;
765
766 // mDragRect will be rendered in onDraw()
767 mDragRect.set(left, top, bottomRight[0], bottomRight[1]);
768 invalidate();
769 }
770 }
771
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800772 /**
Jeff Sharkey70864282009-04-07 21:08:40 -0700773 * Find a vacant area that will fit the given bounds nearest the requested
774 * cell location. Uses Euclidean distance to score multiple vacant areas.
Winson Chungaafa03c2010-06-11 17:34:16 -0700775 *
Romain Guy51afc022009-05-04 18:03:43 -0700776 * @param pixelX The X location at which you want to search for a vacant area.
777 * @param pixelY The Y location at which you want to search for a vacant area.
Jeff Sharkey70864282009-04-07 21:08:40 -0700778 * @param spanX Horizontal span of the object.
779 * @param spanY Vertical span of the object.
780 * @param vacantCells Pre-computed set of vacant cells to search.
781 * @param recycle Previously returned value to possibly recycle.
782 * @return The X, Y cell of a vacant area that can contain this object,
783 * nearest the requested location.
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800784 */
Jeff Sharkey70864282009-04-07 21:08:40 -0700785 int[] findNearestVacantArea(int pixelX, int pixelY, int spanX, int spanY,
786 CellInfo vacantCells, int[] recycle) {
Winson Chungaafa03c2010-06-11 17:34:16 -0700787
Jeff Sharkey70864282009-04-07 21:08:40 -0700788 // Keep track of best-scoring drop area
789 final int[] bestXY = recycle != null ? recycle : new int[2];
Jeff Sharkey70864282009-04-07 21:08:40 -0700790 double bestDistance = Double.MAX_VALUE;
Winson Chungaafa03c2010-06-11 17:34:16 -0700791
Jeff Sharkey70864282009-04-07 21:08:40 -0700792 // Bail early if vacant cells aren't valid
793 if (!vacantCells.valid) {
794 return null;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800795 }
796
Jeff Sharkey70864282009-04-07 21:08:40 -0700797 // Look across all vacant cells for best fit
798 final int size = vacantCells.vacantCells.size();
799 for (int i = 0; i < size; i++) {
800 final CellInfo.VacantCell cell = vacantCells.vacantCells.get(i);
Winson Chungaafa03c2010-06-11 17:34:16 -0700801
Jeff Sharkey70864282009-04-07 21:08:40 -0700802 // Reject if vacant cell isn't our exact size
803 if (cell.spanX != spanX || cell.spanY != spanY) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800804 continue;
805 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700806
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700807 // Score is distance from requested pixel to the top left of each cell
808 final int[] cellXY = mTmpCellXY;
Jeff Sharkey70864282009-04-07 21:08:40 -0700809 cellToPoint(cell.cellX, cell.cellY, cellXY);
Winson Chungaafa03c2010-06-11 17:34:16 -0700810
811 double distance = Math.sqrt(Math.pow(cellXY[0] - pixelX, 2)
812 + Math.pow(cellXY[1] - pixelY, 2));
Jeff Sharkey70864282009-04-07 21:08:40 -0700813 if (distance <= bestDistance) {
814 bestDistance = distance;
815 bestXY[0] = cell.cellX;
816 bestXY[1] = cell.cellY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800817 }
818 }
819
Winson Chungaafa03c2010-06-11 17:34:16 -0700820 // Return null if no suitable location found
Jeff Sharkey70864282009-04-07 21:08:40 -0700821 if (bestDistance < Double.MAX_VALUE) {
822 return bestXY;
823 } else {
824 return null;
825 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800826 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700827
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800828 /**
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700829 * Called when a drag and drop operation has finished (successfully or not).
830 */
831 void onDragComplete() {
832 // Invalidate the drag data
833 mDragCell[0] = -1;
834 mDragCell[1] = -1;
835
836 mDragRect.setEmpty();
837 invalidate();
838 }
839
840 /**
Winson Chungaafa03c2010-06-11 17:34:16 -0700841 * Mark a child as having been dropped.
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800842 *
843 * @param child The child that is being dropped
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800844 */
Winson Chungaafa03c2010-06-11 17:34:16 -0700845 void onDropChild(View child) {
Romain Guyd94533d2009-08-17 10:01:15 -0700846 if (child != null) {
847 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Romain Guyd94533d2009-08-17 10:01:15 -0700848 lp.isDragging = false;
Romain Guy84f296c2009-11-04 15:00:44 -0800849 lp.dropped = true;
Romain Guyd94533d2009-08-17 10:01:15 -0700850 mDragRect.setEmpty();
851 child.requestLayout();
Romain Guyd94533d2009-08-17 10:01:15 -0700852 }
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700853 onDragComplete();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800854 }
855
856 void onDropAborted(View child) {
857 if (child != null) {
858 ((LayoutParams) child.getLayoutParams()).isDragging = false;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800859 }
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700860 onDragComplete();
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800861 }
862
863 /**
864 * Start dragging the specified child
Winson Chungaafa03c2010-06-11 17:34:16 -0700865 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800866 * @param child The child that is being dragged
867 */
868 void onDragChild(View child) {
869 LayoutParams lp = (LayoutParams) child.getLayoutParams();
870 lp.isDragging = true;
871 mDragRect.setEmpty();
872 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700873
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800874 /**
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800875 * Computes a bounding rectangle for a range of cells
Winson Chungaafa03c2010-06-11 17:34:16 -0700876 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800877 * @param cellX X coordinate of upper left corner expressed as a cell position
878 * @param cellY Y coordinate of upper left corner expressed as a cell position
Winson Chungaafa03c2010-06-11 17:34:16 -0700879 * @param cellHSpan Width in cells
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800880 * @param cellVSpan Height in cells
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700881 * @param resultRect Rect into which to put the results
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800882 */
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700883 public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, RectF resultRect) {
884 final boolean portrait = mPortrait;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800885 final int cellWidth = mCellWidth;
886 final int cellHeight = mCellHeight;
887 final int widthGap = mWidthGap;
888 final int heightGap = mHeightGap;
Winson Chungaafa03c2010-06-11 17:34:16 -0700889
890 final int hStartPadding = getLeftPadding();
891 final int vStartPadding = getTopPadding();
892
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800893 int width = cellHSpan * cellWidth + ((cellHSpan - 1) * widthGap);
894 int height = cellVSpan * cellHeight + ((cellVSpan - 1) * heightGap);
895
896 int x = hStartPadding + cellX * (cellWidth + widthGap);
897 int y = vStartPadding + cellY * (cellHeight + heightGap);
Winson Chungaafa03c2010-06-11 17:34:16 -0700898
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700899 resultRect.set(x, y, x + width, y + height);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800900 }
Winson Chungaafa03c2010-06-11 17:34:16 -0700901
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800902 /**
Winson Chungaafa03c2010-06-11 17:34:16 -0700903 * Computes the required horizontal and vertical cell spans to always
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800904 * fit the given rectangle.
Winson Chungaafa03c2010-06-11 17:34:16 -0700905 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800906 * @param width Width in pixels
907 * @param height Height in pixels
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -0700908 * @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 -0800909 */
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -0700910 public int[] rectToCell(int width, int height, int[] result) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800911 // Always assume we're working with the smallest span to make sure we
912 // reserve enough space in both orientations.
Joe Onorato79e56262009-09-21 15:23:04 -0400913 final Resources resources = getResources();
914 int actualWidth = resources.getDimensionPixelSize(R.dimen.workspace_cell_width);
915 int actualHeight = resources.getDimensionPixelSize(R.dimen.workspace_cell_height);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800916 int smallerSize = Math.min(actualWidth, actualHeight);
Joe Onorato79e56262009-09-21 15:23:04 -0400917
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800918 // Always round up to next largest cell
919 int spanX = (width + smallerSize) / smallerSize;
920 int spanY = (height + smallerSize) / smallerSize;
Joe Onorato79e56262009-09-21 15:23:04 -0400921
Patrick Dubroy8f86ddc2010-07-16 13:55:32 -0700922 if (result == null) {
923 return new int[] { spanX, spanY };
924 }
925 result[0] = spanX;
926 result[1] = spanY;
927 return result;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800928 }
929
930 /**
931 * Find the first vacant cell, if there is one.
932 *
933 * @param vacant Holds the x and y coordinate of the vacant cell
934 * @param spanX Horizontal cell span.
935 * @param spanY Vertical cell span.
Winson Chungaafa03c2010-06-11 17:34:16 -0700936 *
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800937 * @return True if a vacant cell was found
938 */
939 public boolean getVacantCell(int[] vacant, int spanX, int spanY) {
940 final boolean portrait = mPortrait;
941 final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
942 final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
943 final boolean[][] occupied = mOccupied;
944
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700945 findOccupiedCells(xCount, yCount, occupied, null, true);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800946
947 return findVacantCell(vacant, spanX, spanY, xCount, yCount, occupied);
948 }
949
950 static boolean findVacantCell(int[] vacant, int spanX, int spanY,
951 int xCount, int yCount, boolean[][] occupied) {
952
953 for (int x = 0; x < xCount; x++) {
954 for (int y = 0; y < yCount; y++) {
955 boolean available = !occupied[x][y];
956out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) {
957 for (int j = y; j < y + spanY - 1 && y < yCount; j++) {
958 available = available && !occupied[i][j];
959 if (!available) break out;
960 }
961 }
962
963 if (available) {
964 vacant[0] = x;
965 vacant[1] = y;
966 return true;
967 }
968 }
969 }
970
971 return false;
972 }
973
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700974 /**
975 * Update the array of occupied cells (mOccupied), and return a flattened copy of the array.
976 */
977 boolean[] getOccupiedCellsFlattened() {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800978 final boolean portrait = mPortrait;
979 final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
980 final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
981 final boolean[][] occupied = mOccupied;
982
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700983 findOccupiedCells(xCount, yCount, occupied, null, true);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800984
985 final boolean[] flat = new boolean[xCount * yCount];
986 for (int y = 0; y < yCount; y++) {
987 for (int x = 0; x < xCount; x++) {
988 flat[y * xCount + x] = occupied[x][y];
989 }
990 }
991
992 return flat;
993 }
994
Patrick Dubroy6569f2c2010-07-12 14:25:18 -0700995 /**
996 * Update the array of occupied cells.
997 * @param ignoreView If non-null, the space occupied by this View is treated as vacant
998 * @param ignoreFolders If true, a cell occupied by a Folder is treated as vacant
999 */
1000 private void findOccupiedCells(
1001 int xCount, int yCount, boolean[][] occupied, View ignoreView, boolean ignoreFolders) {
1002
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001003 for (int x = 0; x < xCount; x++) {
1004 for (int y = 0; y < yCount; y++) {
1005 occupied[x][y] = false;
1006 }
1007 }
1008
1009 int count = getChildCount();
1010 for (int i = 0; i < count; i++) {
1011 View child = getChildAt(i);
Patrick Dubroy6569f2c2010-07-12 14:25:18 -07001012 if ((ignoreFolders && child instanceof Folder) || child.equals(ignoreView)) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001013 continue;
1014 }
1015 LayoutParams lp = (LayoutParams) child.getLayoutParams();
1016
1017 for (int x = lp.cellX; x < lp.cellX + lp.cellHSpan && x < xCount; x++) {
1018 for (int y = lp.cellY; y < lp.cellY + lp.cellVSpan && y < yCount; y++) {
1019 occupied[x][y] = true;
1020 }
1021 }
1022 }
1023 }
1024
1025 @Override
1026 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
1027 return new CellLayout.LayoutParams(getContext(), attrs);
1028 }
1029
1030 @Override
1031 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
1032 return p instanceof CellLayout.LayoutParams;
1033 }
1034
1035 @Override
1036 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
1037 return new CellLayout.LayoutParams(p);
1038 }
1039
Winson Chungaafa03c2010-06-11 17:34:16 -07001040 public static class CellLayoutAnimationController extends LayoutAnimationController {
1041 public CellLayoutAnimationController(Animation animation, float delay) {
1042 super(animation, delay);
1043 }
1044
1045 @Override
1046 protected long getDelayForView(View view) {
1047 return (int) (Math.random() * 150);
1048 }
1049 }
1050
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001051 public static class LayoutParams extends ViewGroup.MarginLayoutParams {
1052 /**
1053 * Horizontal location of the item in the grid.
1054 */
1055 @ViewDebug.ExportedProperty
1056 public int cellX;
1057
1058 /**
1059 * Vertical location of the item in the grid.
1060 */
1061 @ViewDebug.ExportedProperty
1062 public int cellY;
1063
1064 /**
1065 * Number of cells spanned horizontally by the item.
1066 */
1067 @ViewDebug.ExportedProperty
1068 public int cellHSpan;
1069
1070 /**
1071 * Number of cells spanned vertically by the item.
1072 */
1073 @ViewDebug.ExportedProperty
1074 public int cellVSpan;
Winson Chungaafa03c2010-06-11 17:34:16 -07001075
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001076 /**
1077 * Is this item currently being dragged
1078 */
1079 public boolean isDragging;
1080
1081 // X coordinate of the view in the layout.
1082 @ViewDebug.ExportedProperty
1083 int x;
1084 // Y coordinate of the view in the layout.
1085 @ViewDebug.ExportedProperty
1086 int y;
1087
Romain Guy84f296c2009-11-04 15:00:44 -08001088 boolean dropped;
Romain Guyfcb9e712009-10-02 16:06:52 -07001089
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001090 public LayoutParams(Context c, AttributeSet attrs) {
1091 super(c, attrs);
1092 cellHSpan = 1;
1093 cellVSpan = 1;
1094 }
1095
1096 public LayoutParams(ViewGroup.LayoutParams source) {
1097 super(source);
1098 cellHSpan = 1;
1099 cellVSpan = 1;
1100 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001101
1102 public LayoutParams(LayoutParams source) {
1103 super(source);
1104 this.cellX = source.cellX;
1105 this.cellY = source.cellY;
1106 this.cellHSpan = source.cellHSpan;
1107 this.cellVSpan = source.cellVSpan;
1108 }
1109
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001110 public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
Romain Guy8f19cdd2010-01-08 15:07:00 -08001111 super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001112 this.cellX = cellX;
1113 this.cellY = cellY;
1114 this.cellHSpan = cellHSpan;
1115 this.cellVSpan = cellVSpan;
1116 }
1117
1118 public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap,
1119 int hStartPadding, int vStartPadding) {
Winson Chungaafa03c2010-06-11 17:34:16 -07001120
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001121 final int myCellHSpan = cellHSpan;
1122 final int myCellVSpan = cellVSpan;
1123 final int myCellX = cellX;
1124 final int myCellY = cellY;
Winson Chungaafa03c2010-06-11 17:34:16 -07001125
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001126 width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) -
1127 leftMargin - rightMargin;
1128 height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) -
1129 topMargin - bottomMargin;
1130
1131 x = hStartPadding + myCellX * (cellWidth + widthGap) + leftMargin;
1132 y = vStartPadding + myCellY * (cellHeight + heightGap) + topMargin;
1133 }
Winson Chungaafa03c2010-06-11 17:34:16 -07001134
1135 public String toString() {
1136 return "(" + this.cellX + ", " + this.cellY + ")";
1137 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001138 }
1139
1140 static final class CellInfo implements ContextMenu.ContextMenuInfo {
1141 /**
Winson Chungaafa03c2010-06-11 17:34:16 -07001142 * See View.AttachInfo.InvalidateInfo for futher explanations about the
1143 * recycling mechanism. In this case, we recycle the vacant cells
1144 * instances because up to several hundreds can be instanciated when the
1145 * user long presses an empty cell.
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001146 */
1147 static final class VacantCell {
1148 int cellX;
1149 int cellY;
1150 int spanX;
1151 int spanY;
1152
1153 // We can create up to 523 vacant cells on a 4x4 grid, 100 seems
1154 // like a reasonable compromise given the size of a VacantCell and
1155 // the fact that the user is not likely to touch an empty 4x4 grid
Winson Chungaafa03c2010-06-11 17:34:16 -07001156 // very often
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001157 private static final int POOL_LIMIT = 100;
1158 private static final Object sLock = new Object();
1159
1160 private static int sAcquiredCount = 0;
1161 private static VacantCell sRoot;
1162
1163 private VacantCell next;
1164
1165 static VacantCell acquire() {
1166 synchronized (sLock) {
1167 if (sRoot == null) {
1168 return new VacantCell();
1169 }
1170
1171 VacantCell info = sRoot;
1172 sRoot = info.next;
1173 sAcquiredCount--;
1174
1175 return info;
1176 }
1177 }
1178
1179 void release() {
1180 synchronized (sLock) {
1181 if (sAcquiredCount < POOL_LIMIT) {
1182 sAcquiredCount++;
1183 next = sRoot;
1184 sRoot = this;
1185 }
1186 }
1187 }
1188
1189 @Override
1190 public String toString() {
Winson Chungaafa03c2010-06-11 17:34:16 -07001191 return "VacantCell[x=" + cellX + ", y=" + cellY + ", spanX="
1192 + spanX + ", spanY=" + spanY + "]";
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001193 }
1194 }
1195
1196 View cell;
1197 int cellX;
1198 int cellY;
1199 int spanX;
1200 int spanY;
1201 int screen;
1202 boolean valid;
1203
1204 final ArrayList<VacantCell> vacantCells = new ArrayList<VacantCell>(VacantCell.POOL_LIMIT);
1205 int maxVacantSpanX;
1206 int maxVacantSpanXSpanY;
1207 int maxVacantSpanY;
1208 int maxVacantSpanYSpanX;
1209 final Rect current = new Rect();
1210
Romain Guy4c58c482009-05-12 17:35:41 -07001211 void clearVacantCells() {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001212 final ArrayList<VacantCell> list = vacantCells;
1213 final int count = list.size();
1214
Winson Chungaafa03c2010-06-11 17:34:16 -07001215 for (int i = 0; i < count; i++) {
1216 list.get(i).release();
1217 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001218
1219 list.clear();
1220 }
1221
1222 void findVacantCellsFromOccupied(boolean[] occupied, int xCount, int yCount) {
1223 if (cellX < 0 || cellY < 0) {
1224 maxVacantSpanX = maxVacantSpanXSpanY = Integer.MIN_VALUE;
1225 maxVacantSpanY = maxVacantSpanYSpanX = Integer.MIN_VALUE;
1226 clearVacantCells();
1227 return;
1228 }
1229
1230 final boolean[][] unflattened = new boolean[xCount][yCount];
1231 for (int y = 0; y < yCount; y++) {
1232 for (int x = 0; x < xCount; x++) {
1233 unflattened[x][y] = occupied[y * xCount + x];
1234 }
1235 }
1236 CellLayout.findIntersectingVacantCells(this, cellX, cellY, xCount, yCount, unflattened);
1237 }
1238
1239 /**
1240 * This method can be called only once! Calling #findVacantCellsFromOccupied will
1241 * restore the ability to call this method.
1242 *
1243 * Finds the upper-left coordinate of the first rectangle in the grid that can
1244 * hold a cell of the specified dimensions.
1245 *
1246 * @param cellXY The array that will contain the position of a vacant cell if such a cell
1247 * can be found.
1248 * @param spanX The horizontal span of the cell we want to find.
1249 * @param spanY The vertical span of the cell we want to find.
1250 *
1251 * @return True if a vacant cell of the specified dimension was found, false otherwise.
1252 */
1253 boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
Romain Guy4c58c482009-05-12 17:35:41 -07001254 return findCellForSpan(cellXY, spanX, spanY, true);
1255 }
1256
1257 boolean findCellForSpan(int[] cellXY, int spanX, int spanY, boolean clear) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001258 final ArrayList<VacantCell> list = vacantCells;
1259 final int count = list.size();
1260
1261 boolean found = false;
1262
Michael Jurka0e260592010-06-30 17:07:39 -07001263 // return the span represented by the CellInfo only there is no view there
1264 // (this.cell == null) and there is enough space
1265 if (this.cell == null && this.spanX >= spanX && this.spanY >= spanY) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001266 cellXY[0] = cellX;
1267 cellXY[1] = cellY;
1268 found = true;
1269 }
1270
1271 // Look for an exact match first
1272 for (int i = 0; i < count; i++) {
1273 VacantCell cell = list.get(i);
1274 if (cell.spanX == spanX && cell.spanY == spanY) {
1275 cellXY[0] = cell.cellX;
1276 cellXY[1] = cell.cellY;
1277 found = true;
1278 break;
1279 }
1280 }
1281
1282 // Look for the first cell large enough
1283 for (int i = 0; i < count; i++) {
1284 VacantCell cell = list.get(i);
1285 if (cell.spanX >= spanX && cell.spanY >= spanY) {
1286 cellXY[0] = cell.cellX;
1287 cellXY[1] = cell.cellY;
1288 found = true;
1289 break;
1290 }
1291 }
1292
Winson Chungaafa03c2010-06-11 17:34:16 -07001293 if (clear) {
1294 clearVacantCells();
1295 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001296
1297 return found;
1298 }
1299
1300 @Override
1301 public String toString() {
Winson Chungaafa03c2010-06-11 17:34:16 -07001302 return "Cell[view=" + (cell == null ? "null" : cell.getClass())
1303 + ", x=" + cellX + ", y=" + cellY + "]";
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001304 }
1305 }
Mike Cleronf8bbd342009-10-23 16:15:16 -07001306
1307 public boolean lastDownOnOccupiedCell() {
1308 return mLastDownOnOccupiedCell;
1309 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001310}