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