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