blob: 73481c2482fb860abbb12163cbd767ac50661f03 [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
Romain Guy1c01b6a2010-04-14 14:56:34 -0700139 final LayoutParams lp = (LayoutParams) params;
140 child.setId(((getId() & 0xFF) << 16) | (lp.cellX & 0xFF) << 8 | (lp.cellY & 0xFF));
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
Michael Jurkaaf442092010-06-10 17:01:57 -0700161 public void setTagToCellInfoForPoint(int touchX, int touchY) {
162 final CellInfo cellInfo = mCellInfo;
163 final Rect frame = mRect;
164 final int x = touchX + mScrollX;
165 final int y = touchY + mScrollY;
166 final int count = getChildCount();
167
168 boolean found = false;
169 for (int i = count - 1; i >= 0; i--) {
170 final View child = getChildAt(i);
171
172 if ((child.getVisibility()) == VISIBLE || child.getAnimation() != null) {
173 child.getHitRect(frame);
174 if (frame.contains(x, y)) {
175 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
176 cellInfo.cell = child;
177 cellInfo.cellX = lp.cellX;
178 cellInfo.cellY = lp.cellY;
179 cellInfo.spanX = lp.cellHSpan;
180 cellInfo.spanY = lp.cellVSpan;
181 cellInfo.valid = true;
182 found = true;
183 mDirtyTag = false;
184 break;
185 }
186 }
187 }
188
189 mLastDownOnOccupiedCell = found;
190
191 if (!found) {
192 int cellXY[] = mCellXY;
193 pointToCellExact(x, y, cellXY);
194
195 final boolean portrait = mPortrait;
196 final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
197 final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
198
199 final boolean[][] occupied = mOccupied;
200 findOccupiedCells(xCount, yCount, occupied, null);
201
202 cellInfo.cell = null;
203 cellInfo.cellX = cellXY[0];
204 cellInfo.cellY = cellXY[1];
205 cellInfo.spanX = 1;
206 cellInfo.spanY = 1;
207 cellInfo.valid = cellXY[0] >= 0 && cellXY[1] >= 0 && cellXY[0] < xCount &&
208 cellXY[1] < yCount && !occupied[cellXY[0]][cellXY[1]];
209
210 // Instead of finding the interesting vacant cells here, wait until a
211 // caller invokes getTag() to retrieve the result. Finding the vacant
212 // cells is a bit expensive and can generate many new objects, it's
213 // therefore better to defer it until we know we actually need it.
214
215 mDirtyTag = true;
216 }
217 setTag(cellInfo);
218 }
219
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800220 @Override
221 public boolean onInterceptTouchEvent(MotionEvent ev) {
222 final int action = ev.getAction();
223 final CellInfo cellInfo = mCellInfo;
224
225 if (action == MotionEvent.ACTION_DOWN) {
Michael Jurkaaf442092010-06-10 17:01:57 -0700226 setTagToCellInfoForPoint((int) ev.getX(), (int) ev.getY());
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800227 } else if (action == MotionEvent.ACTION_UP) {
228 cellInfo.cell = null;
229 cellInfo.cellX = -1;
230 cellInfo.cellY = -1;
231 cellInfo.spanX = 0;
232 cellInfo.spanY = 0;
233 cellInfo.valid = false;
234 mDirtyTag = false;
235 setTag(cellInfo);
236 }
237
238 return false;
239 }
240
241 @Override
242 public CellInfo getTag() {
243 final CellInfo info = (CellInfo) super.getTag();
244 if (mDirtyTag && info.valid) {
245 final boolean portrait = mPortrait;
246 final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
247 final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
248
249 final boolean[][] occupied = mOccupied;
Jeff Sharkey70864282009-04-07 21:08:40 -0700250 findOccupiedCells(xCount, yCount, occupied, null);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800251
252 findIntersectingVacantCells(info, info.cellX, info.cellY, xCount, yCount, occupied);
253
254 mDirtyTag = false;
255 }
256 return info;
257 }
258
259 private static void findIntersectingVacantCells(CellInfo cellInfo, int x, int y,
260 int xCount, int yCount, boolean[][] occupied) {
261
262 cellInfo.maxVacantSpanX = Integer.MIN_VALUE;
263 cellInfo.maxVacantSpanXSpanY = Integer.MIN_VALUE;
264 cellInfo.maxVacantSpanY = Integer.MIN_VALUE;
265 cellInfo.maxVacantSpanYSpanX = Integer.MIN_VALUE;
266 cellInfo.clearVacantCells();
267
268 if (occupied[x][y]) {
269 return;
270 }
271
272 cellInfo.current.set(x, y, x, y);
273
274 findVacantCell(cellInfo.current, xCount, yCount, occupied, cellInfo);
275 }
276
277 private static void findVacantCell(Rect current, int xCount, int yCount, boolean[][] occupied,
278 CellInfo cellInfo) {
279
280 addVacantCell(current, cellInfo);
281
282 if (current.left > 0) {
283 if (isColumnEmpty(current.left - 1, current.top, current.bottom, occupied)) {
284 current.left--;
285 findVacantCell(current, xCount, yCount, occupied, cellInfo);
286 current.left++;
287 }
288 }
289
290 if (current.right < xCount - 1) {
291 if (isColumnEmpty(current.right + 1, current.top, current.bottom, occupied)) {
292 current.right++;
293 findVacantCell(current, xCount, yCount, occupied, cellInfo);
294 current.right--;
295 }
296 }
297
298 if (current.top > 0) {
299 if (isRowEmpty(current.top - 1, current.left, current.right, occupied)) {
300 current.top--;
301 findVacantCell(current, xCount, yCount, occupied, cellInfo);
302 current.top++;
303 }
304 }
305
306 if (current.bottom < yCount - 1) {
307 if (isRowEmpty(current.bottom + 1, current.left, current.right, occupied)) {
308 current.bottom++;
309 findVacantCell(current, xCount, yCount, occupied, cellInfo);
310 current.bottom--;
311 }
312 }
313 }
314
315 private static void addVacantCell(Rect current, CellInfo cellInfo) {
316 CellInfo.VacantCell cell = CellInfo.VacantCell.acquire();
317 cell.cellX = current.left;
318 cell.cellY = current.top;
319 cell.spanX = current.right - current.left + 1;
320 cell.spanY = current.bottom - current.top + 1;
321 if (cell.spanX > cellInfo.maxVacantSpanX) {
322 cellInfo.maxVacantSpanX = cell.spanX;
323 cellInfo.maxVacantSpanXSpanY = cell.spanY;
324 }
325 if (cell.spanY > cellInfo.maxVacantSpanY) {
326 cellInfo.maxVacantSpanY = cell.spanY;
327 cellInfo.maxVacantSpanYSpanX = cell.spanX;
328 }
329 cellInfo.vacantCells.add(cell);
330 }
331
332 private static boolean isColumnEmpty(int x, int top, int bottom, boolean[][] occupied) {
333 for (int y = top; y <= bottom; y++) {
334 if (occupied[x][y]) {
335 return false;
336 }
337 }
338 return true;
339 }
340
341 private static boolean isRowEmpty(int y, int left, int right, boolean[][] occupied) {
342 for (int x = left; x <= right; x++) {
343 if (occupied[x][y]) {
344 return false;
345 }
346 }
347 return true;
348 }
349
Jeff Sharkey70864282009-04-07 21:08:40 -0700350 CellInfo findAllVacantCells(boolean[] occupiedCells, View ignoreView) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800351 final boolean portrait = mPortrait;
352 final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
353 final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
354
355 boolean[][] occupied = mOccupied;
356
357 if (occupiedCells != null) {
358 for (int y = 0; y < yCount; y++) {
359 for (int x = 0; x < xCount; x++) {
360 occupied[x][y] = occupiedCells[y * xCount + x];
361 }
362 }
363 } else {
Jeff Sharkey70864282009-04-07 21:08:40 -0700364 findOccupiedCells(xCount, yCount, occupied, ignoreView);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800365 }
366
367 CellInfo cellInfo = new CellInfo();
368
369 cellInfo.cellX = -1;
370 cellInfo.cellY = -1;
371 cellInfo.spanY = 0;
372 cellInfo.spanX = 0;
373 cellInfo.maxVacantSpanX = Integer.MIN_VALUE;
374 cellInfo.maxVacantSpanXSpanY = Integer.MIN_VALUE;
375 cellInfo.maxVacantSpanY = Integer.MIN_VALUE;
376 cellInfo.maxVacantSpanYSpanX = Integer.MIN_VALUE;
377 cellInfo.screen = mCellInfo.screen;
378
379 Rect current = cellInfo.current;
380
381 for (int x = 0; x < xCount; x++) {
382 for (int y = 0; y < yCount; y++) {
383 if (!occupied[x][y]) {
384 current.set(x, y, x, y);
385 findVacantCell(current, xCount, yCount, occupied, cellInfo);
386 occupied[x][y] = true;
387 }
388 }
389 }
390
391 cellInfo.valid = cellInfo.vacantCells.size() > 0;
392
393 // Assume the caller will perform their own cell searching, otherwise we
394 // risk causing an unnecessary rebuild after findCellForSpan()
395
396 return cellInfo;
397 }
398
399 /**
400 * Given a point, return the cell that strictly encloses that point
401 * @param x X coordinate of the point
402 * @param y Y coordinate of the point
403 * @param result Array of 2 ints to hold the x and y coordinate of the cell
404 */
405 void pointToCellExact(int x, int y, int[] result) {
406 final boolean portrait = mPortrait;
407
408 final int hStartPadding = portrait ? mShortAxisStartPadding : mLongAxisStartPadding;
409 final int vStartPadding = portrait ? mLongAxisStartPadding : mShortAxisStartPadding;
410
411 result[0] = (x - hStartPadding) / (mCellWidth + mWidthGap);
412 result[1] = (y - vStartPadding) / (mCellHeight + mHeightGap);
413
414 final int xAxis = portrait ? mShortAxisCells : mLongAxisCells;
415 final int yAxis = portrait ? mLongAxisCells : mShortAxisCells;
416
417 if (result[0] < 0) result[0] = 0;
418 if (result[0] >= xAxis) result[0] = xAxis - 1;
419 if (result[1] < 0) result[1] = 0;
420 if (result[1] >= yAxis) result[1] = yAxis - 1;
421 }
422
423 /**
424 * Given a point, return the cell that most closely encloses that point
425 * @param x X coordinate of the point
426 * @param y Y coordinate of the point
427 * @param result Array of 2 ints to hold the x and y coordinate of the cell
428 */
429 void pointToCellRounded(int x, int y, int[] result) {
430 pointToCellExact(x + (mCellWidth / 2), y + (mCellHeight / 2), result);
431 }
432
433 /**
434 * Given a cell coordinate, return the point that represents the upper left corner of that cell
435 *
436 * @param cellX X coordinate of the cell
437 * @param cellY Y coordinate of the cell
438 *
439 * @param result Array of 2 ints to hold the x and y coordinate of the point
440 */
441 void cellToPoint(int cellX, int cellY, int[] result) {
442 final boolean portrait = mPortrait;
443
444 final int hStartPadding = portrait ? mShortAxisStartPadding : mLongAxisStartPadding;
445 final int vStartPadding = portrait ? mLongAxisStartPadding : mShortAxisStartPadding;
446
447
448 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap);
449 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap);
450 }
451
Romain Guy84f296c2009-11-04 15:00:44 -0800452 int getCellWidth() {
453 return mCellWidth;
454 }
455
456 int getCellHeight() {
457 return mCellHeight;
458 }
459
Romain Guy1a304a12009-11-10 00:02:32 -0800460 int getLeftPadding() {
461 return mPortrait ? mShortAxisStartPadding : mLongAxisStartPadding;
462 }
463
464 int getTopPadding() {
465 return mPortrait ? mLongAxisStartPadding : mShortAxisStartPadding;
466 }
467
468 int getRightPadding() {
469 return mPortrait ? mShortAxisEndPadding : mLongAxisEndPadding;
470 }
471
472 int getBottomPadding() {
473 return mPortrait ? mLongAxisEndPadding : mShortAxisEndPadding;
474 }
475
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800476 @Override
477 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
478 // TODO: currently ignoring padding
479
480 int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
481 int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
482
483 int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
484 int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
485
486 if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
487 throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
488 }
489
490 final int shortAxisCells = mShortAxisCells;
491 final int longAxisCells = mLongAxisCells;
492 final int longAxisStartPadding = mLongAxisStartPadding;
493 final int longAxisEndPadding = mLongAxisEndPadding;
494 final int shortAxisStartPadding = mShortAxisStartPadding;
495 final int shortAxisEndPadding = mShortAxisEndPadding;
496 final int cellWidth = mCellWidth;
497 final int cellHeight = mCellHeight;
498
499 mPortrait = heightSpecSize > widthSpecSize;
500
501 int numShortGaps = shortAxisCells - 1;
502 int numLongGaps = longAxisCells - 1;
503
504 if (mPortrait) {
505 int vSpaceLeft = heightSpecSize - longAxisStartPadding - longAxisEndPadding
506 - (cellHeight * longAxisCells);
507 mHeightGap = vSpaceLeft / numLongGaps;
508
509 int hSpaceLeft = widthSpecSize - shortAxisStartPadding - shortAxisEndPadding
510 - (cellWidth * shortAxisCells);
511 if (numShortGaps > 0) {
512 mWidthGap = hSpaceLeft / numShortGaps;
513 } else {
514 mWidthGap = 0;
515 }
516 } else {
517 int hSpaceLeft = widthSpecSize - longAxisStartPadding - longAxisEndPadding
518 - (cellWidth * longAxisCells);
519 mWidthGap = hSpaceLeft / numLongGaps;
520
521 int vSpaceLeft = heightSpecSize - shortAxisStartPadding - shortAxisEndPadding
522 - (cellHeight * shortAxisCells);
523 if (numShortGaps > 0) {
524 mHeightGap = vSpaceLeft / numShortGaps;
525 } else {
526 mHeightGap = 0;
527 }
528 }
529
530 int count = getChildCount();
531
532 for (int i = 0; i < count; i++) {
533 View child = getChildAt(i);
534 LayoutParams lp = (LayoutParams) child.getLayoutParams();
535
536 if (mPortrait) {
537 lp.setup(cellWidth, cellHeight, mWidthGap, mHeightGap, shortAxisStartPadding,
538 longAxisStartPadding);
539 } else {
540 lp.setup(cellWidth, cellHeight, mWidthGap, mHeightGap, longAxisStartPadding,
541 shortAxisStartPadding);
542 }
Romain Guyfcb9e712009-10-02 16:06:52 -0700543
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800544 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 Guy84f296c2009-11-04 15:00:44 -0800892 boolean dropped;
Romain Guyfcb9e712009-10-02 16:06:52 -0700893
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800894 public LayoutParams(Context c, AttributeSet attrs) {
895 super(c, attrs);
896 cellHSpan = 1;
897 cellVSpan = 1;
898 }
899
900 public LayoutParams(ViewGroup.LayoutParams source) {
901 super(source);
902 cellHSpan = 1;
903 cellVSpan = 1;
904 }
905
906 public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
Romain Guy8f19cdd2010-01-08 15:07:00 -0800907 super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
The Android Open Source Project31dd5032009-03-03 19:32:27 -0800908 this.cellX = cellX;
909 this.cellY = cellY;
910 this.cellHSpan = cellHSpan;
911 this.cellVSpan = cellVSpan;
912 }
913
914 public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap,
915 int hStartPadding, int vStartPadding) {
916
917 final int myCellHSpan = cellHSpan;
918 final int myCellVSpan = cellVSpan;
919 final int myCellX = cellX;
920 final int myCellY = cellY;
921
922 width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) -
923 leftMargin - rightMargin;
924 height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) -
925 topMargin - bottomMargin;
926
927 x = hStartPadding + myCellX * (cellWidth + widthGap) + leftMargin;
928 y = vStartPadding + myCellY * (cellHeight + heightGap) + topMargin;
929 }
930 }
931
932 static final class CellInfo implements ContextMenu.ContextMenuInfo {
933 /**
934 * See View.AttachInfo.InvalidateInfo for futher explanations about
935 * the recycling mechanism. In this case, we recycle the vacant cells
936 * instances because up to several hundreds can be instanciated when
937 * the user long presses an empty cell.
938 */
939 static final class VacantCell {
940 int cellX;
941 int cellY;
942 int spanX;
943 int spanY;
944
945 // We can create up to 523 vacant cells on a 4x4 grid, 100 seems
946 // like a reasonable compromise given the size of a VacantCell and
947 // the fact that the user is not likely to touch an empty 4x4 grid
948 // very often
949 private static final int POOL_LIMIT = 100;
950 private static final Object sLock = new Object();
951
952 private static int sAcquiredCount = 0;
953 private static VacantCell sRoot;
954
955 private VacantCell next;
956
957 static VacantCell acquire() {
958 synchronized (sLock) {
959 if (sRoot == null) {
960 return new VacantCell();
961 }
962
963 VacantCell info = sRoot;
964 sRoot = info.next;
965 sAcquiredCount--;
966
967 return info;
968 }
969 }
970
971 void release() {
972 synchronized (sLock) {
973 if (sAcquiredCount < POOL_LIMIT) {
974 sAcquiredCount++;
975 next = sRoot;
976 sRoot = this;
977 }
978 }
979 }
980
981 @Override
982 public String toString() {
983 return "VacantCell[x=" + cellX + ", y=" + cellY + ", spanX=" + spanX +
984 ", spanY=" + spanY + "]";
985 }
986 }
987
988 View cell;
989 int cellX;
990 int cellY;
991 int spanX;
992 int spanY;
993 int screen;
994 boolean valid;
995
996 final ArrayList<VacantCell> vacantCells = new ArrayList<VacantCell>(VacantCell.POOL_LIMIT);
997 int maxVacantSpanX;
998 int maxVacantSpanXSpanY;
999 int maxVacantSpanY;
1000 int maxVacantSpanYSpanX;
1001 final Rect current = new Rect();
1002
Romain Guy4c58c482009-05-12 17:35:41 -07001003 void clearVacantCells() {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001004 final ArrayList<VacantCell> list = vacantCells;
1005 final int count = list.size();
1006
1007 for (int i = 0; i < count; i++) list.get(i).release();
1008
1009 list.clear();
1010 }
1011
1012 void findVacantCellsFromOccupied(boolean[] occupied, int xCount, int yCount) {
1013 if (cellX < 0 || cellY < 0) {
1014 maxVacantSpanX = maxVacantSpanXSpanY = Integer.MIN_VALUE;
1015 maxVacantSpanY = maxVacantSpanYSpanX = Integer.MIN_VALUE;
1016 clearVacantCells();
1017 return;
1018 }
1019
1020 final boolean[][] unflattened = new boolean[xCount][yCount];
1021 for (int y = 0; y < yCount; y++) {
1022 for (int x = 0; x < xCount; x++) {
1023 unflattened[x][y] = occupied[y * xCount + x];
1024 }
1025 }
1026 CellLayout.findIntersectingVacantCells(this, cellX, cellY, xCount, yCount, unflattened);
1027 }
1028
1029 /**
1030 * This method can be called only once! Calling #findVacantCellsFromOccupied will
1031 * restore the ability to call this method.
1032 *
1033 * Finds the upper-left coordinate of the first rectangle in the grid that can
1034 * hold a cell of the specified dimensions.
1035 *
1036 * @param cellXY The array that will contain the position of a vacant cell if such a cell
1037 * can be found.
1038 * @param spanX The horizontal span of the cell we want to find.
1039 * @param spanY The vertical span of the cell we want to find.
1040 *
1041 * @return True if a vacant cell of the specified dimension was found, false otherwise.
1042 */
1043 boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
Romain Guy4c58c482009-05-12 17:35:41 -07001044 return findCellForSpan(cellXY, spanX, spanY, true);
1045 }
1046
1047 boolean findCellForSpan(int[] cellXY, int spanX, int spanY, boolean clear) {
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001048 final ArrayList<VacantCell> list = vacantCells;
1049 final int count = list.size();
1050
1051 boolean found = false;
1052
1053 if (this.spanX >= spanX && this.spanY >= spanY) {
1054 cellXY[0] = cellX;
1055 cellXY[1] = cellY;
1056 found = true;
1057 }
1058
1059 // Look for an exact match first
1060 for (int i = 0; i < count; i++) {
1061 VacantCell cell = list.get(i);
1062 if (cell.spanX == spanX && cell.spanY == spanY) {
1063 cellXY[0] = cell.cellX;
1064 cellXY[1] = cell.cellY;
1065 found = true;
1066 break;
1067 }
1068 }
1069
1070 // Look for the first cell large enough
1071 for (int i = 0; i < count; i++) {
1072 VacantCell cell = list.get(i);
1073 if (cell.spanX >= spanX && cell.spanY >= spanY) {
1074 cellXY[0] = cell.cellX;
1075 cellXY[1] = cell.cellY;
1076 found = true;
1077 break;
1078 }
1079 }
1080
Romain Guy4c58c482009-05-12 17:35:41 -07001081 if (clear) clearVacantCells();
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001082
1083 return found;
1084 }
1085
1086 @Override
1087 public String toString() {
1088 return "Cell[view=" + (cell == null ? "null" : cell.getClass()) + ", x=" + cellX +
1089 ", y=" + cellY + "]";
1090 }
1091 }
Mike Cleronf8bbd342009-10-23 16:15:16 -07001092
1093 public boolean lastDownOnOccupiedCell() {
1094 return mLastDownOnOccupiedCell;
1095 }
The Android Open Source Project31dd5032009-03-03 19:32:27 -08001096}
1097
1098