blob: 9d39c2ca78841c827fb153b0242a4c102a15ea2f [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
19import android.content.Context;
20import android.content.res.TypedArray;
Joe Onorato79e56262009-09-21 15:23:04 -040021import android.content.res.Resources;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080022import android.graphics.Rect;
23import android.graphics.RectF;
Romain Guya6abce82009-11-10 02:54:41 -080024import android.graphics.Canvas;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080025import android.util.AttributeSet;
26import android.view.ContextMenu;
27import android.view.MotionEvent;
28import android.view.View;
29import android.view.ViewDebug;
30import android.view.ViewGroup;
Romain Guy84f296c2009-11-04 15:00:44 -080031import android.app.WallpaperManager;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080032
33import java.util.ArrayList;
34
Romain Guyedcce092010-03-04 13:03:17 -080035import com.android.launcher.R;
36
The Android Open Source Project31dd5032009-03-03 19:32:27 -080037public class CellLayout extends ViewGroup {
38 private boolean mPortrait;
39
40 private int mCellWidth;
41 private int mCellHeight;
42
43 private int mLongAxisStartPadding;
44 private int mLongAxisEndPadding;
45
46 private int mShortAxisStartPadding;
47 private int mShortAxisEndPadding;
48
49 private int mShortAxisCells;
50 private int mLongAxisCells;
51
52 private int mWidthGap;
53 private int mHeightGap;
54
55 private final Rect mRect = new Rect();
56 private final CellInfo mCellInfo = new CellInfo();
57
58 int[] mCellXY = new int[2];
The Android Open Source Project31dd5032009-03-03 19:32:27 -080059 boolean[][] mOccupied;
60
61 private RectF mDragRect = new RectF();
62
63 private boolean mDirtyTag;
Mike Cleronf8bbd342009-10-23 16:15:16 -070064 private boolean mLastDownOnOccupiedCell = false;
Romain Guy84f296c2009-11-04 15:00:44 -080065
66 private final WallpaperManager mWallpaperManager;
The Android Open Source Project31dd5032009-03-03 19:32:27 -080067
68 public CellLayout(Context context) {
69 this(context, null);
70 }
71
72 public CellLayout(Context context, AttributeSet attrs) {
73 this(context, attrs, 0);
74 }
75
76 public CellLayout(Context context, AttributeSet attrs, int defStyle) {
77 super(context, attrs, defStyle);
78 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0);
79
80 mCellWidth = a.getDimensionPixelSize(R.styleable.CellLayout_cellWidth, 10);
81 mCellHeight = a.getDimensionPixelSize(R.styleable.CellLayout_cellHeight, 10);
82
83 mLongAxisStartPadding =
84 a.getDimensionPixelSize(R.styleable.CellLayout_longAxisStartPadding, 10);
85 mLongAxisEndPadding =
86 a.getDimensionPixelSize(R.styleable.CellLayout_longAxisEndPadding, 10);
87 mShortAxisStartPadding =
88 a.getDimensionPixelSize(R.styleable.CellLayout_shortAxisStartPadding, 10);
89 mShortAxisEndPadding =
90 a.getDimensionPixelSize(R.styleable.CellLayout_shortAxisEndPadding, 10);
91
92 mShortAxisCells = a.getInt(R.styleable.CellLayout_shortAxisCells, 4);
93 mLongAxisCells = a.getInt(R.styleable.CellLayout_longAxisCells, 4);
94
95 a.recycle();
96
97 setAlwaysDrawnWithCacheEnabled(false);
98
99 if (mOccupied == null) {
100 if (mPortrait) {
101 mOccupied = new boolean[mShortAxisCells][mLongAxisCells];
102 } else {
103 mOccupied = new boolean[mLongAxisCells][mShortAxisCells];
104 }
105 }
Romain Guy84f296c2009-11-04 15:00:44 -0800106
107 mWallpaperManager = WallpaperManager.getInstance(getContext());
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800108 }
109
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700110 @Override
Romain Guya6abce82009-11-10 02:54:41 -0800111 public void dispatchDraw(Canvas canvas) {
112 super.dispatchDraw(canvas);
113 }
114
115 @Override
Jeff Sharkey83f111d2009-04-20 21:03:13 -0700116 public void cancelLongPress() {
117 super.cancelLongPress();
118
119 // Cancel long press for all children
120 final int count = getChildCount();
121 for (int i = 0; i < count; i++) {
122 final View child = getChildAt(i);
123 child.cancelLongPress();
124 }
125 }
126
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800127 int getCountX() {
128 return mPortrait ? mShortAxisCells : mLongAxisCells;
129 }
130
131 int getCountY() {
132 return mPortrait ? mLongAxisCells : mShortAxisCells;
133 }
134
135 @Override
136 public void addView(View child, int index, ViewGroup.LayoutParams params) {
137 // Generate an id for each view, this assumes we have at most 256x256 cells
138 // per workspace screen
139 final LayoutParams cellParams = (LayoutParams) params;
Romain Guyfcb9e712009-10-02 16:06:52 -0700140 cellParams.regenerateId = true;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800141
142 super.addView(child, index, params);
143 }
144
145 @Override
146 public void requestChildFocus(View child, View focused) {
147 super.requestChildFocus(child, focused);
148 if (child != null) {
149 Rect r = new Rect();
150 child.getDrawingRect(r);
151 requestRectangleOnScreen(r);
152 }
153 }
154
155 @Override
156 protected void onAttachedToWindow() {
157 super.onAttachedToWindow();
158 mCellInfo.screen = ((ViewGroup) getParent()).indexOfChild(this);
159 }
160
161 @Override
162 public boolean onInterceptTouchEvent(MotionEvent ev) {
163 final int action = ev.getAction();
164 final CellInfo cellInfo = mCellInfo;
165
166 if (action == MotionEvent.ACTION_DOWN) {
167 final Rect frame = mRect;
168 final int x = (int) ev.getX() + mScrollX;
169 final int y = (int) ev.getY() + mScrollY;
170 final int count = getChildCount();
171
172 boolean found = false;
173 for (int i = count - 1; i >= 0; i--) {
174 final View child = getChildAt(i);
175
176 if ((child.getVisibility()) == VISIBLE || child.getAnimation() != null) {
177 child.getHitRect(frame);
178 if (frame.contains(x, y)) {
179 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
180 cellInfo.cell = child;
181 cellInfo.cellX = lp.cellX;
182 cellInfo.cellY = lp.cellY;
183 cellInfo.spanX = lp.cellHSpan;
184 cellInfo.spanY = lp.cellVSpan;
185 cellInfo.valid = true;
186 found = true;
187 mDirtyTag = false;
188 break;
189 }
190 }
191 }
Mike Cleronf8bbd342009-10-23 16:15:16 -0700192
193 mLastDownOnOccupiedCell = found;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800194
195 if (!found) {
196 int cellXY[] = mCellXY;
197 pointToCellExact(x, y, cellXY);
198
199 final boolean portrait = mPortrait;
200 final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
201 final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
202
203 final boolean[][] occupied = mOccupied;
Jeff Sharkey70864282009-04-07 21:08:40 -0700204 findOccupiedCells(xCount, yCount, occupied, null);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800205
206 cellInfo.cell = null;
207 cellInfo.cellX = cellXY[0];
208 cellInfo.cellY = cellXY[1];
209 cellInfo.spanX = 1;
210 cellInfo.spanY = 1;
211 cellInfo.valid = cellXY[0] >= 0 && cellXY[1] >= 0 && cellXY[0] < xCount &&
212 cellXY[1] < yCount && !occupied[cellXY[0]][cellXY[1]];
213
214 // Instead of finding the interesting vacant cells here, wait until a
215 // caller invokes getTag() to retrieve the result. Finding the vacant
216 // cells is a bit expensive and can generate many new objects, it's
217 // therefore better to defer it until we know we actually need it.
218
219 mDirtyTag = true;
220 }
221 setTag(cellInfo);
222 } else if (action == MotionEvent.ACTION_UP) {
223 cellInfo.cell = null;
224 cellInfo.cellX = -1;
225 cellInfo.cellY = -1;
226 cellInfo.spanX = 0;
227 cellInfo.spanY = 0;
228 cellInfo.valid = false;
229 mDirtyTag = false;
230 setTag(cellInfo);
231 }
232
233 return false;
234 }
235
236 @Override
237 public CellInfo getTag() {
238 final CellInfo info = (CellInfo) super.getTag();
239 if (mDirtyTag && info.valid) {
240 final boolean portrait = mPortrait;
241 final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
242 final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
243
244 final boolean[][] occupied = mOccupied;
Jeff Sharkey70864282009-04-07 21:08:40 -0700245 findOccupiedCells(xCount, yCount, occupied, null);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800246
247 findIntersectingVacantCells(info, info.cellX, info.cellY, xCount, yCount, occupied);
248
249 mDirtyTag = false;
250 }
251 return info;
252 }
253
254 private static void findIntersectingVacantCells(CellInfo cellInfo, int x, int y,
255 int xCount, int yCount, boolean[][] occupied) {
256
257 cellInfo.maxVacantSpanX = Integer.MIN_VALUE;
258 cellInfo.maxVacantSpanXSpanY = Integer.MIN_VALUE;
259 cellInfo.maxVacantSpanY = Integer.MIN_VALUE;
260 cellInfo.maxVacantSpanYSpanX = Integer.MIN_VALUE;
261 cellInfo.clearVacantCells();
262
263 if (occupied[x][y]) {
264 return;
265 }
266
267 cellInfo.current.set(x, y, x, y);
268
269 findVacantCell(cellInfo.current, xCount, yCount, occupied, cellInfo);
270 }
271
272 private static void findVacantCell(Rect current, int xCount, int yCount, boolean[][] occupied,
273 CellInfo cellInfo) {
274
275 addVacantCell(current, cellInfo);
276
277 if (current.left > 0) {
278 if (isColumnEmpty(current.left - 1, current.top, current.bottom, occupied)) {
279 current.left--;
280 findVacantCell(current, xCount, yCount, occupied, cellInfo);
281 current.left++;
282 }
283 }
284
285 if (current.right < xCount - 1) {
286 if (isColumnEmpty(current.right + 1, current.top, current.bottom, occupied)) {
287 current.right++;
288 findVacantCell(current, xCount, yCount, occupied, cellInfo);
289 current.right--;
290 }
291 }
292
293 if (current.top > 0) {
294 if (isRowEmpty(current.top - 1, current.left, current.right, occupied)) {
295 current.top--;
296 findVacantCell(current, xCount, yCount, occupied, cellInfo);
297 current.top++;
298 }
299 }
300
301 if (current.bottom < yCount - 1) {
302 if (isRowEmpty(current.bottom + 1, current.left, current.right, occupied)) {
303 current.bottom++;
304 findVacantCell(current, xCount, yCount, occupied, cellInfo);
305 current.bottom--;
306 }
307 }
308 }
309
310 private static void addVacantCell(Rect current, CellInfo cellInfo) {
311 CellInfo.VacantCell cell = CellInfo.VacantCell.acquire();
312 cell.cellX = current.left;
313 cell.cellY = current.top;
314 cell.spanX = current.right - current.left + 1;
315 cell.spanY = current.bottom - current.top + 1;
316 if (cell.spanX > cellInfo.maxVacantSpanX) {
317 cellInfo.maxVacantSpanX = cell.spanX;
318 cellInfo.maxVacantSpanXSpanY = cell.spanY;
319 }
320 if (cell.spanY > cellInfo.maxVacantSpanY) {
321 cellInfo.maxVacantSpanY = cell.spanY;
322 cellInfo.maxVacantSpanYSpanX = cell.spanX;
323 }
324 cellInfo.vacantCells.add(cell);
325 }
326
327 private static boolean isColumnEmpty(int x, int top, int bottom, boolean[][] occupied) {
328 for (int y = top; y <= bottom; y++) {
329 if (occupied[x][y]) {
330 return false;
331 }
332 }
333 return true;
334 }
335
336 private static boolean isRowEmpty(int y, int left, int right, boolean[][] occupied) {
337 for (int x = left; x <= right; x++) {
338 if (occupied[x][y]) {
339 return false;
340 }
341 }
342 return true;
343 }
344
Jeff Sharkey70864282009-04-07 21:08:40 -0700345 CellInfo findAllVacantCells(boolean[] occupiedCells, View ignoreView) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800346 final boolean portrait = mPortrait;
347 final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
348 final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
349
350 boolean[][] occupied = mOccupied;
351
352 if (occupiedCells != null) {
353 for (int y = 0; y < yCount; y++) {
354 for (int x = 0; x < xCount; x++) {
355 occupied[x][y] = occupiedCells[y * xCount + x];
356 }
357 }
358 } else {
Jeff Sharkey70864282009-04-07 21:08:40 -0700359 findOccupiedCells(xCount, yCount, occupied, ignoreView);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800360 }
361
362 CellInfo cellInfo = new CellInfo();
363
364 cellInfo.cellX = -1;
365 cellInfo.cellY = -1;
366 cellInfo.spanY = 0;
367 cellInfo.spanX = 0;
368 cellInfo.maxVacantSpanX = Integer.MIN_VALUE;
369 cellInfo.maxVacantSpanXSpanY = Integer.MIN_VALUE;
370 cellInfo.maxVacantSpanY = Integer.MIN_VALUE;
371 cellInfo.maxVacantSpanYSpanX = Integer.MIN_VALUE;
372 cellInfo.screen = mCellInfo.screen;
373
374 Rect current = cellInfo.current;
375
376 for (int x = 0; x < xCount; x++) {
377 for (int y = 0; y < yCount; y++) {
378 if (!occupied[x][y]) {
379 current.set(x, y, x, y);
380 findVacantCell(current, xCount, yCount, occupied, cellInfo);
381 occupied[x][y] = true;
382 }
383 }
384 }
385
386 cellInfo.valid = cellInfo.vacantCells.size() > 0;
387
388 // Assume the caller will perform their own cell searching, otherwise we
389 // risk causing an unnecessary rebuild after findCellForSpan()
390
391 return cellInfo;
392 }
393
394 /**
395 * Given a point, return the cell that strictly encloses that point
396 * @param x X coordinate of the point
397 * @param y Y coordinate of the point
398 * @param result Array of 2 ints to hold the x and y coordinate of the cell
399 */
400 void pointToCellExact(int x, int y, int[] result) {
401 final boolean portrait = mPortrait;
402
403 final int hStartPadding = portrait ? mShortAxisStartPadding : mLongAxisStartPadding;
404 final int vStartPadding = portrait ? mLongAxisStartPadding : mShortAxisStartPadding;
405
406 result[0] = (x - hStartPadding) / (mCellWidth + mWidthGap);
407 result[1] = (y - vStartPadding) / (mCellHeight + mHeightGap);
408
409 final int xAxis = portrait ? mShortAxisCells : mLongAxisCells;
410 final int yAxis = portrait ? mLongAxisCells : mShortAxisCells;
411
412 if (result[0] < 0) result[0] = 0;
413 if (result[0] >= xAxis) result[0] = xAxis - 1;
414 if (result[1] < 0) result[1] = 0;
415 if (result[1] >= yAxis) result[1] = yAxis - 1;
416 }
417
418 /**
419 * Given a point, return the cell that most closely encloses that point
420 * @param x X coordinate of the point
421 * @param y Y coordinate of the point
422 * @param result Array of 2 ints to hold the x and y coordinate of the cell
423 */
424 void pointToCellRounded(int x, int y, int[] result) {
425 pointToCellExact(x + (mCellWidth / 2), y + (mCellHeight / 2), result);
426 }
427
428 /**
429 * Given a cell coordinate, return the point that represents the upper left corner of that cell
430 *
431 * @param cellX X coordinate of the cell
432 * @param cellY Y coordinate of the cell
433 *
434 * @param result Array of 2 ints to hold the x and y coordinate of the point
435 */
436 void cellToPoint(int cellX, int cellY, int[] result) {
437 final boolean portrait = mPortrait;
438
439 final int hStartPadding = portrait ? mShortAxisStartPadding : mLongAxisStartPadding;
440 final int vStartPadding = portrait ? mLongAxisStartPadding : mShortAxisStartPadding;
441
442
443 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap);
444 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap);
445 }
446
Romain Guy84f296c2009-11-04 15:00:44 -0800447 int getCellWidth() {
448 return mCellWidth;
449 }
450
451 int getCellHeight() {
452 return mCellHeight;
453 }
454
Romain Guy1a304a12009-11-10 00:02:32 -0800455 int getLeftPadding() {
456 return mPortrait ? mShortAxisStartPadding : mLongAxisStartPadding;
457 }
458
459 int getTopPadding() {
460 return mPortrait ? mLongAxisStartPadding : mShortAxisStartPadding;
461 }
462
463 int getRightPadding() {
464 return mPortrait ? mShortAxisEndPadding : mLongAxisEndPadding;
465 }
466
467 int getBottomPadding() {
468 return mPortrait ? mLongAxisEndPadding : mShortAxisEndPadding;
469 }
470
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800471 @Override
472 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
473 // TODO: currently ignoring padding
474
475 int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
476 int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
477
478 int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
479 int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
480
481 if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
482 throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
483 }
484
485 final int shortAxisCells = mShortAxisCells;
486 final int longAxisCells = mLongAxisCells;
487 final int longAxisStartPadding = mLongAxisStartPadding;
488 final int longAxisEndPadding = mLongAxisEndPadding;
489 final int shortAxisStartPadding = mShortAxisStartPadding;
490 final int shortAxisEndPadding = mShortAxisEndPadding;
491 final int cellWidth = mCellWidth;
492 final int cellHeight = mCellHeight;
493
494 mPortrait = heightSpecSize > widthSpecSize;
495
496 int numShortGaps = shortAxisCells - 1;
497 int numLongGaps = longAxisCells - 1;
498
499 if (mPortrait) {
500 int vSpaceLeft = heightSpecSize - longAxisStartPadding - longAxisEndPadding
501 - (cellHeight * longAxisCells);
502 mHeightGap = vSpaceLeft / numLongGaps;
503
504 int hSpaceLeft = widthSpecSize - shortAxisStartPadding - shortAxisEndPadding
505 - (cellWidth * shortAxisCells);
506 if (numShortGaps > 0) {
507 mWidthGap = hSpaceLeft / numShortGaps;
508 } else {
509 mWidthGap = 0;
510 }
511 } else {
512 int hSpaceLeft = widthSpecSize - longAxisStartPadding - longAxisEndPadding
513 - (cellWidth * longAxisCells);
514 mWidthGap = hSpaceLeft / numLongGaps;
515
516 int vSpaceLeft = heightSpecSize - shortAxisStartPadding - shortAxisEndPadding
517 - (cellHeight * shortAxisCells);
518 if (numShortGaps > 0) {
519 mHeightGap = vSpaceLeft / numShortGaps;
520 } else {
521 mHeightGap = 0;
522 }
523 }
524
525 int count = getChildCount();
526
527 for (int i = 0; i < count; i++) {
528 View child = getChildAt(i);
529 LayoutParams lp = (LayoutParams) child.getLayoutParams();
530
531 if (mPortrait) {
532 lp.setup(cellWidth, cellHeight, mWidthGap, mHeightGap, shortAxisStartPadding,
533 longAxisStartPadding);
534 } else {
535 lp.setup(cellWidth, cellHeight, mWidthGap, mHeightGap, longAxisStartPadding,
536 shortAxisStartPadding);
537 }
Romain Guyfcb9e712009-10-02 16:06:52 -0700538
539 if (lp.regenerateId) {
540 child.setId(((getId() & 0xFF) << 16) | (lp.cellX & 0xFF) << 8 | (lp.cellY & 0xFF));
541 lp.regenerateId = false;
542 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800543
544 int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY);
545 int childheightMeasureSpec =
546 MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY);
547 child.measure(childWidthMeasureSpec, childheightMeasureSpec);
548 }
549
550 setMeasuredDimension(widthSpecSize, heightSpecSize);
551 }
552
553 @Override
554 protected void onLayout(boolean changed, int l, int t, int r, int b) {
555 int count = getChildCount();
556
557 for (int i = 0; i < count; i++) {
558 View child = getChildAt(i);
559 if (child.getVisibility() != GONE) {
560
561 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
562
563 int childLeft = lp.x;
564 int childTop = lp.y;
565 child.layout(childLeft, childTop, childLeft + lp.width, childTop + lp.height);
Romain Guy84f296c2009-11-04 15:00:44 -0800566
567 if (lp.dropped) {
568 lp.dropped = false;
569
Romain Guy06762ab2010-01-25 16:51:08 -0800570 final int[] cellXY = mCellXY;
571 getLocationOnScreen(cellXY);
Romain Guy84f296c2009-11-04 15:00:44 -0800572 mWallpaperManager.sendWallpaperCommand(getWindowToken(), "android.home.drop",
Romain Guy06762ab2010-01-25 16:51:08 -0800573 cellXY[0] + childLeft + lp.width / 2,
574 cellXY[1] + childTop + lp.height / 2, 0, null);
Romain Guy84f296c2009-11-04 15:00:44 -0800575 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800576 }
577 }
578 }
579
580 @Override
581 protected void setChildrenDrawingCacheEnabled(boolean enabled) {
582 final int count = getChildCount();
583 for (int i = 0; i < count; i++) {
584 final View view = getChildAt(i);
585 view.setDrawingCacheEnabled(enabled);
586 // Update the drawing caches
Adam Powellfefa0ce2010-05-03 10:23:50 -0700587 view.buildDrawingCache(true);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800588 }
589 }
590
591 @Override
592 protected void setChildrenDrawnWithCacheEnabled(boolean enabled) {
593 super.setChildrenDrawnWithCacheEnabled(enabled);
594 }
595
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800596 /**
Jeff Sharkey70864282009-04-07 21:08:40 -0700597 * Find a vacant area that will fit the given bounds nearest the requested
598 * cell location. Uses Euclidean distance to score multiple vacant areas.
599 *
Romain Guy51afc022009-05-04 18:03:43 -0700600 * @param pixelX The X location at which you want to search for a vacant area.
601 * @param pixelY The Y location at which you want to search for a vacant area.
Jeff Sharkey70864282009-04-07 21:08:40 -0700602 * @param spanX Horizontal span of the object.
603 * @param spanY Vertical span of the object.
604 * @param vacantCells Pre-computed set of vacant cells to search.
605 * @param recycle Previously returned value to possibly recycle.
606 * @return The X, Y cell of a vacant area that can contain this object,
607 * nearest the requested location.
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800608 */
Jeff Sharkey70864282009-04-07 21:08:40 -0700609 int[] findNearestVacantArea(int pixelX, int pixelY, int spanX, int spanY,
610 CellInfo vacantCells, int[] recycle) {
611
612 // Keep track of best-scoring drop area
613 final int[] bestXY = recycle != null ? recycle : new int[2];
614 final int[] cellXY = mCellXY;
615 double bestDistance = Double.MAX_VALUE;
616
617 // Bail early if vacant cells aren't valid
618 if (!vacantCells.valid) {
619 return null;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800620 }
621
Jeff Sharkey70864282009-04-07 21:08:40 -0700622 // Look across all vacant cells for best fit
623 final int size = vacantCells.vacantCells.size();
624 for (int i = 0; i < size; i++) {
625 final CellInfo.VacantCell cell = vacantCells.vacantCells.get(i);
626
627 // Reject if vacant cell isn't our exact size
628 if (cell.spanX != spanX || cell.spanY != spanY) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800629 continue;
630 }
Jeff Sharkey70864282009-04-07 21:08:40 -0700631
632 // Score is center distance from requested pixel
633 cellToPoint(cell.cellX, cell.cellY, cellXY);
634
635 double distance = Math.sqrt(Math.pow(cellXY[0] - pixelX, 2) +
636 Math.pow(cellXY[1] - pixelY, 2));
637 if (distance <= bestDistance) {
638 bestDistance = distance;
639 bestXY[0] = cell.cellX;
640 bestXY[1] = cell.cellY;
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800641 }
642 }
643
Jeff Sharkey70864282009-04-07 21:08:40 -0700644 // Return null if no suitable location found
645 if (bestDistance < Double.MAX_VALUE) {
646 return bestXY;
647 } else {
648 return null;
649 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800650 }
Jeff Sharkey70864282009-04-07 21:08:40 -0700651
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800652 /**
653 * Drop a child at the specified position
654 *
655 * @param child The child that is being dropped
Jeff Sharkey70864282009-04-07 21:08:40 -0700656 * @param targetXY Destination area to move to
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800657 */
Jeff Sharkey70864282009-04-07 21:08:40 -0700658 void onDropChild(View child, int[] targetXY) {
Romain Guyd94533d2009-08-17 10:01:15 -0700659 if (child != null) {
660 LayoutParams lp = (LayoutParams) child.getLayoutParams();
661 lp.cellX = targetXY[0];
662 lp.cellY = targetXY[1];
663 lp.isDragging = false;
Romain Guy84f296c2009-11-04 15:00:44 -0800664 lp.dropped = true;
Romain Guyd94533d2009-08-17 10:01:15 -0700665 mDragRect.setEmpty();
666 child.requestLayout();
667 invalidate();
668 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800669 }
670
671 void onDropAborted(View child) {
672 if (child != null) {
673 ((LayoutParams) child.getLayoutParams()).isDragging = false;
674 invalidate();
675 }
676 mDragRect.setEmpty();
677 }
678
679 /**
680 * Start dragging the specified child
681 *
682 * @param child The child that is being dragged
683 */
684 void onDragChild(View child) {
685 LayoutParams lp = (LayoutParams) child.getLayoutParams();
686 lp.isDragging = true;
687 mDragRect.setEmpty();
688 }
689
690 /**
691 * Drag a child over the specified position
692 *
693 * @param child The child that is being dropped
694 * @param cellX The child's new x cell location
695 * @param cellY The child's new y cell location
696 */
697 void onDragOverChild(View child, int cellX, int cellY) {
698 int[] cellXY = mCellXY;
699 pointToCellRounded(cellX, cellY, cellXY);
700 LayoutParams lp = (LayoutParams) child.getLayoutParams();
701 cellToRect(cellXY[0], cellXY[1], lp.cellHSpan, lp.cellVSpan, mDragRect);
702 invalidate();
703 }
704
705 /**
706 * Computes a bounding rectangle for a range of cells
707 *
708 * @param cellX X coordinate of upper left corner expressed as a cell position
709 * @param cellY Y coordinate of upper left corner expressed as a cell position
710 * @param cellHSpan Width in cells
711 * @param cellVSpan Height in cells
712 * @param dragRect Rectnagle into which to put the results
713 */
714 public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, RectF dragRect) {
715 final boolean portrait = mPortrait;
716 final int cellWidth = mCellWidth;
717 final int cellHeight = mCellHeight;
718 final int widthGap = mWidthGap;
719 final int heightGap = mHeightGap;
720
721 final int hStartPadding = portrait ? mShortAxisStartPadding : mLongAxisStartPadding;
722 final int vStartPadding = portrait ? mLongAxisStartPadding : mShortAxisStartPadding;
723
724 int width = cellHSpan * cellWidth + ((cellHSpan - 1) * widthGap);
725 int height = cellVSpan * cellHeight + ((cellVSpan - 1) * heightGap);
726
727 int x = hStartPadding + cellX * (cellWidth + widthGap);
728 int y = vStartPadding + cellY * (cellHeight + heightGap);
729
730 dragRect.set(x, y, x + width, y + height);
731 }
732
733 /**
734 * Computes the required horizontal and vertical cell spans to always
735 * fit the given rectangle.
736 *
737 * @param width Width in pixels
738 * @param height Height in pixels
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800739 */
740 public int[] rectToCell(int width, int height) {
741 // Always assume we're working with the smallest span to make sure we
742 // reserve enough space in both orientations.
Joe Onorato79e56262009-09-21 15:23:04 -0400743 final Resources resources = getResources();
744 int actualWidth = resources.getDimensionPixelSize(R.dimen.workspace_cell_width);
745 int actualHeight = resources.getDimensionPixelSize(R.dimen.workspace_cell_height);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800746 int smallerSize = Math.min(actualWidth, actualHeight);
Joe Onorato79e56262009-09-21 15:23:04 -0400747
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800748 // Always round up to next largest cell
749 int spanX = (width + smallerSize) / smallerSize;
750 int spanY = (height + smallerSize) / smallerSize;
Joe Onorato79e56262009-09-21 15:23:04 -0400751
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800752 return new int[] { spanX, spanY };
753 }
754
755 /**
756 * Find the first vacant cell, if there is one.
757 *
758 * @param vacant Holds the x and y coordinate of the vacant cell
759 * @param spanX Horizontal cell span.
760 * @param spanY Vertical cell span.
761 *
762 * @return True if a vacant cell was found
763 */
764 public boolean getVacantCell(int[] vacant, int spanX, int spanY) {
765 final boolean portrait = mPortrait;
766 final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
767 final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
768 final boolean[][] occupied = mOccupied;
769
Jeff Sharkey70864282009-04-07 21:08:40 -0700770 findOccupiedCells(xCount, yCount, occupied, null);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800771
772 return findVacantCell(vacant, spanX, spanY, xCount, yCount, occupied);
773 }
774
775 static boolean findVacantCell(int[] vacant, int spanX, int spanY,
776 int xCount, int yCount, boolean[][] occupied) {
777
778 for (int x = 0; x < xCount; x++) {
779 for (int y = 0; y < yCount; y++) {
780 boolean available = !occupied[x][y];
781out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) {
782 for (int j = y; j < y + spanY - 1 && y < yCount; j++) {
783 available = available && !occupied[i][j];
784 if (!available) break out;
785 }
786 }
787
788 if (available) {
789 vacant[0] = x;
790 vacant[1] = y;
791 return true;
792 }
793 }
794 }
795
796 return false;
797 }
798
799 boolean[] getOccupiedCells() {
800 final boolean portrait = mPortrait;
801 final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
802 final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
803 final boolean[][] occupied = mOccupied;
804
Jeff Sharkey70864282009-04-07 21:08:40 -0700805 findOccupiedCells(xCount, yCount, occupied, null);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800806
807 final boolean[] flat = new boolean[xCount * yCount];
808 for (int y = 0; y < yCount; y++) {
809 for (int x = 0; x < xCount; x++) {
810 flat[y * xCount + x] = occupied[x][y];
811 }
812 }
813
814 return flat;
815 }
816
Jeff Sharkey70864282009-04-07 21:08:40 -0700817 private void findOccupiedCells(int xCount, int yCount, boolean[][] occupied, View ignoreView) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800818 for (int x = 0; x < xCount; x++) {
819 for (int y = 0; y < yCount; y++) {
820 occupied[x][y] = false;
821 }
822 }
823
824 int count = getChildCount();
825 for (int i = 0; i < count; i++) {
826 View child = getChildAt(i);
Jeff Sharkey70864282009-04-07 21:08:40 -0700827 if (child instanceof Folder || child.equals(ignoreView)) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800828 continue;
829 }
830 LayoutParams lp = (LayoutParams) child.getLayoutParams();
831
832 for (int x = lp.cellX; x < lp.cellX + lp.cellHSpan && x < xCount; x++) {
833 for (int y = lp.cellY; y < lp.cellY + lp.cellVSpan && y < yCount; y++) {
834 occupied[x][y] = true;
835 }
836 }
837 }
838 }
839
840 @Override
841 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
842 return new CellLayout.LayoutParams(getContext(), attrs);
843 }
844
845 @Override
846 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
847 return p instanceof CellLayout.LayoutParams;
848 }
849
850 @Override
851 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
852 return new CellLayout.LayoutParams(p);
853 }
854
855 public static class LayoutParams extends ViewGroup.MarginLayoutParams {
856 /**
857 * Horizontal location of the item in the grid.
858 */
859 @ViewDebug.ExportedProperty
860 public int cellX;
861
862 /**
863 * Vertical location of the item in the grid.
864 */
865 @ViewDebug.ExportedProperty
866 public int cellY;
867
868 /**
869 * Number of cells spanned horizontally by the item.
870 */
871 @ViewDebug.ExportedProperty
872 public int cellHSpan;
873
874 /**
875 * Number of cells spanned vertically by the item.
876 */
877 @ViewDebug.ExportedProperty
878 public int cellVSpan;
879
880 /**
881 * Is this item currently being dragged
882 */
883 public boolean isDragging;
884
885 // X coordinate of the view in the layout.
886 @ViewDebug.ExportedProperty
887 int x;
888 // Y coordinate of the view in the layout.
889 @ViewDebug.ExportedProperty
890 int y;
891
Romain Guyfcb9e712009-10-02 16:06:52 -0700892 boolean regenerateId;
Romain Guy84f296c2009-11-04 15:00:44 -0800893
894 boolean dropped;
Romain Guyfcb9e712009-10-02 16:06:52 -0700895
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800896 public LayoutParams(Context c, AttributeSet attrs) {
897 super(c, attrs);
898 cellHSpan = 1;
899 cellVSpan = 1;
900 }
901
902 public LayoutParams(ViewGroup.LayoutParams source) {
903 super(source);
904 cellHSpan = 1;
905 cellVSpan = 1;
906 }
907
908 public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
Romain Guy8f19cdd2010-01-08 15:07:00 -0800909 super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800910 this.cellX = cellX;
911 this.cellY = cellY;
912 this.cellHSpan = cellHSpan;
913 this.cellVSpan = cellVSpan;
914 }
915
916 public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap,
917 int hStartPadding, int vStartPadding) {
918
919 final int myCellHSpan = cellHSpan;
920 final int myCellVSpan = cellVSpan;
921 final int myCellX = cellX;
922 final int myCellY = cellY;
923
924 width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) -
925 leftMargin - rightMargin;
926 height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) -
927 topMargin - bottomMargin;
928
929 x = hStartPadding + myCellX * (cellWidth + widthGap) + leftMargin;
930 y = vStartPadding + myCellY * (cellHeight + heightGap) + topMargin;
931 }
932 }
933
934 static final class CellInfo implements ContextMenu.ContextMenuInfo {
935 /**
936 * See View.AttachInfo.InvalidateInfo for futher explanations about
937 * the recycling mechanism. In this case, we recycle the vacant cells
938 * instances because up to several hundreds can be instanciated when
939 * the user long presses an empty cell.
940 */
941 static final class VacantCell {
942 int cellX;
943 int cellY;
944 int spanX;
945 int spanY;
946
947 // We can create up to 523 vacant cells on a 4x4 grid, 100 seems
948 // like a reasonable compromise given the size of a VacantCell and
949 // the fact that the user is not likely to touch an empty 4x4 grid
950 // very often
951 private static final int POOL_LIMIT = 100;
952 private static final Object sLock = new Object();
953
954 private static int sAcquiredCount = 0;
955 private static VacantCell sRoot;
956
957 private VacantCell next;
958
959 static VacantCell acquire() {
960 synchronized (sLock) {
961 if (sRoot == null) {
962 return new VacantCell();
963 }
964
965 VacantCell info = sRoot;
966 sRoot = info.next;
967 sAcquiredCount--;
968
969 return info;
970 }
971 }
972
973 void release() {
974 synchronized (sLock) {
975 if (sAcquiredCount < POOL_LIMIT) {
976 sAcquiredCount++;
977 next = sRoot;
978 sRoot = this;
979 }
980 }
981 }
982
983 @Override
984 public String toString() {
985 return "VacantCell[x=" + cellX + ", y=" + cellY + ", spanX=" + spanX +
986 ", spanY=" + spanY + "]";
987 }
988 }
989
990 View cell;
991 int cellX;
992 int cellY;
993 int spanX;
994 int spanY;
995 int screen;
996 boolean valid;
997
998 final ArrayList<VacantCell> vacantCells = new ArrayList<VacantCell>(VacantCell.POOL_LIMIT);
999 int maxVacantSpanX;
1000 int maxVacantSpanXSpanY;
1001 int maxVacantSpanY;
1002 int maxVacantSpanYSpanX;
1003 final Rect current = new Rect();
1004
Romain Guy4c58c482009-05-12 17:35:41 -07001005 void clearVacantCells() {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001006 final ArrayList<VacantCell> list = vacantCells;
1007 final int count = list.size();
1008
1009 for (int i = 0; i < count; i++) list.get(i).release();
1010
1011 list.clear();
1012 }
1013
1014 void findVacantCellsFromOccupied(boolean[] occupied, int xCount, int yCount) {
1015 if (cellX < 0 || cellY < 0) {
1016 maxVacantSpanX = maxVacantSpanXSpanY = Integer.MIN_VALUE;
1017 maxVacantSpanY = maxVacantSpanYSpanX = Integer.MIN_VALUE;
1018 clearVacantCells();
1019 return;
1020 }
1021
1022 final boolean[][] unflattened = new boolean[xCount][yCount];
1023 for (int y = 0; y < yCount; y++) {
1024 for (int x = 0; x < xCount; x++) {
1025 unflattened[x][y] = occupied[y * xCount + x];
1026 }
1027 }
1028 CellLayout.findIntersectingVacantCells(this, cellX, cellY, xCount, yCount, unflattened);
1029 }
1030
1031 /**
1032 * This method can be called only once! Calling #findVacantCellsFromOccupied will
1033 * restore the ability to call this method.
1034 *
1035 * Finds the upper-left coordinate of the first rectangle in the grid that can
1036 * hold a cell of the specified dimensions.
1037 *
1038 * @param cellXY The array that will contain the position of a vacant cell if such a cell
1039 * can be found.
1040 * @param spanX The horizontal span of the cell we want to find.
1041 * @param spanY The vertical span of the cell we want to find.
1042 *
1043 * @return True if a vacant cell of the specified dimension was found, false otherwise.
1044 */
1045 boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
Romain Guy4c58c482009-05-12 17:35:41 -07001046 return findCellForSpan(cellXY, spanX, spanY, true);
1047 }
1048
1049 boolean findCellForSpan(int[] cellXY, int spanX, int spanY, boolean clear) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001050 final ArrayList<VacantCell> list = vacantCells;
1051 final int count = list.size();
1052
1053 boolean found = false;
1054
1055 if (this.spanX >= spanX && this.spanY >= spanY) {
1056 cellXY[0] = cellX;
1057 cellXY[1] = cellY;
1058 found = true;
1059 }
1060
1061 // Look for an exact match first
1062 for (int i = 0; i < count; i++) {
1063 VacantCell cell = list.get(i);
1064 if (cell.spanX == spanX && cell.spanY == spanY) {
1065 cellXY[0] = cell.cellX;
1066 cellXY[1] = cell.cellY;
1067 found = true;
1068 break;
1069 }
1070 }
1071
1072 // Look for the first cell large enough
1073 for (int i = 0; i < count; i++) {
1074 VacantCell cell = list.get(i);
1075 if (cell.spanX >= spanX && cell.spanY >= spanY) {
1076 cellXY[0] = cell.cellX;
1077 cellXY[1] = cell.cellY;
1078 found = true;
1079 break;
1080 }
1081 }
1082
Romain Guy4c58c482009-05-12 17:35:41 -07001083 if (clear) clearVacantCells();
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001084
1085 return found;
1086 }
1087
1088 @Override
1089 public String toString() {
1090 return "Cell[view=" + (cell == null ? "null" : cell.getClass()) + ", x=" + cellX +
1091 ", y=" + cellY + "]";
1092 }
1093 }
Mike Cleronf8bbd342009-10-23 16:15:16 -07001094
1095 public boolean lastDownOnOccupiedCell() {
1096 return mLastDownOnOccupiedCell;
1097 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001098}
1099
1100