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