blob: 03cf7fcef4298d1ebece4876a7a61f300df750fa [file] [log] [blame]
The Android Open Source Projectc8f00b62008-10-21 07:00:00 -07001/*
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 public CellLayout(Context context) {
60 this(context, null);
61 }
62
63 public CellLayout(Context context, AttributeSet attrs) {
64 this(context, attrs, 0);
65 }
66
67 public CellLayout(Context context, AttributeSet attrs, int defStyle) {
68 super(context, attrs, defStyle);
69 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0);
70
71 mCellWidth = a.getDimensionPixelSize(R.styleable.CellLayout_cellWidth, 10);
72 mCellHeight = a.getDimensionPixelSize(R.styleable.CellLayout_cellHeight, 10);
73
74 mLongAxisStartPadding =
75 a.getDimensionPixelSize(R.styleable.CellLayout_longAxisStartPadding, 10);
76 mLongAxisEndPadding =
77 a.getDimensionPixelSize(R.styleable.CellLayout_longAxisEndPadding, 10);
78 mShortAxisStartPadding =
79 a.getDimensionPixelSize(R.styleable.CellLayout_shortAxisStartPadding, 10);
80 mShortAxisEndPadding =
81 a.getDimensionPixelSize(R.styleable.CellLayout_shortAxisEndPadding, 10);
82
83 mShortAxisCells = a.getInt(R.styleable.CellLayout_shortAxisCells, 4);
84 mLongAxisCells = a.getInt(R.styleable.CellLayout_longAxisCells, 4);
85
86 a.recycle();
87
88 setAlwaysDrawnWithCacheEnabled(false);
89
90 if (mOccupied == null) {
91 if (mPortrait) {
92 mOccupied = new boolean[mShortAxisCells][mLongAxisCells];
93 } else {
94 mOccupied = new boolean[mLongAxisCells][mShortAxisCells];
95 }
96 }
97 }
98
99 int getCountX() {
100 return mPortrait ? mShortAxisCells : mLongAxisCells;
101 }
102
103 int getCountY() {
104 return mPortrait ? mLongAxisCells : mShortAxisCells;
105 }
106
107 @Override
108 public void requestChildFocus(View child, View focused) {
109 super.requestChildFocus(child, focused);
110 if (child != null) {
111 Rect r = new Rect();
112 child.getDrawingRect(r);
113 requestRectangleOnScreen(r);
114 }
115 }
116
117 @Override
118 protected void onAttachedToWindow() {
119 super.onAttachedToWindow();
120 mCellInfo.screen = ((ViewGroup) getParent()).indexOfChild(this);
121 }
122
123 @Override
124 public boolean onInterceptTouchEvent(MotionEvent ev) {
125 final int action = ev.getAction();
126 final CellInfo cellInfo = mCellInfo;
127
128 if (action == MotionEvent.ACTION_DOWN) {
129 final Rect frame = mRect;
130 final int x = (int) ev.getX() + mScrollX;
131 final int y = (int) ev.getY() + mScrollY;
132 final int count = getChildCount();
133
134 boolean found = false;
135 for (int i = count - 1; i >= 0; i--) {
136 final View child = getChildAt(i);
137
138 if ((child.getVisibility()) == VISIBLE || child.getAnimation() != null) {
139 child.getHitRect(frame);
140 if (frame.contains(x, y)) {
141 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
142 cellInfo.cell = child;
143 cellInfo.cellX = lp.cellX;
144 cellInfo.cellY = lp.cellY;
145 cellInfo.spanX = lp.cellHSpan;
146 cellInfo.spanY = lp.cellVSpan;
147 cellInfo.valid = true;
148 found = true;
149 break;
150 }
151 }
152 }
153
154 if (!found) {
155 int cellXY[] = mCellXY;
156 pointToCellExact(x, y, cellXY);
157
158 final boolean portrait = mPortrait;
159 final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
160 final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
161
162 final boolean[][] occupied = mOccupied;
163 findOccupiedCells(xCount, yCount, occupied);
164
165 cellInfo.cell = null;
166 cellInfo.cellX = cellXY[0];
167 cellInfo.cellY = cellXY[1];
168 cellInfo.spanX = 1;
169 cellInfo.spanY = 1;
170 cellInfo.valid = cellXY[0] >= 0 && cellXY[1] >= 0 && cellXY[0] < xCount &&
171 cellXY[1] < yCount && !occupied[cellXY[0]][cellXY[1]];
172
173 if (cellInfo.valid) {
174 findIntersectingVacantCells(cellInfo, cellXY[0], cellXY[1],
175 xCount, yCount, occupied);
176 }
177 }
178 setTag(cellInfo);
179 } else if (action == MotionEvent.ACTION_UP) {
180 cellInfo.cell = null;
181 cellInfo.cellX = -1;
182 cellInfo.cellY = -1;
183 cellInfo.spanX = 0;
184 cellInfo.spanY = 0;
185 cellInfo.valid = false;
186 setTag(cellInfo);
187 }
188
189 return false;
190 }
191
192 private static void findIntersectingVacantCells(CellInfo cellInfo, int x, int y,
193 int xCount, int yCount, boolean[][] occupied) {
194
195 cellInfo.maxVacantSpanX = Integer.MIN_VALUE;
196 cellInfo.maxVacantSpanXSpanY = Integer.MIN_VALUE;
197 cellInfo.maxVacantSpanY = Integer.MIN_VALUE;
198 cellInfo.maxVacantSpanYSpanX = Integer.MIN_VALUE;
199 cellInfo.vacantCells = new ArrayList<CellInfo.VacantCell>();
200
201 if (occupied[x][y]) {
202 return;
203 }
204
205 Rect current = new Rect(x, y, x, y);
206 findVacantCell(current, xCount, yCount, occupied, cellInfo);
207 }
208
209 private static void findVacantCell(Rect current, int xCount, int yCount, boolean[][] occupied,
210 CellInfo cellInfo) {
211
212 addVacantCell(current, cellInfo);
213
214 if (current.left > 0) {
215 if (isColumnEmpty(current.left - 1, current.top, current.bottom, occupied)) {
216 current.left--;
217 findVacantCell(current, xCount, yCount, occupied, cellInfo);
218 current.left++;
219 }
220 }
221
222 if (current.right < xCount - 1) {
223 if (isColumnEmpty(current.right + 1, current.top, current.bottom, occupied)) {
224 current.right++;
225 findVacantCell(current, xCount, yCount, occupied, cellInfo);
226 current.right--;
227 }
228 }
229
230 if (current.top > 0) {
231 if (isRowEmpty(current.top - 1, current.left, current.right, occupied)) {
232 current.top--;
233 findVacantCell(current, xCount, yCount, occupied, cellInfo);
234 current.top++;
235 }
236 }
237
238 if (current.bottom < yCount - 1) {
239 if (isRowEmpty(current.bottom + 1, current.left, current.right, occupied)) {
240 current.bottom++;
241 findVacantCell(current, xCount, yCount, occupied, cellInfo);
242 current.bottom--;
243 }
244 }
245 }
246
247 private static void addVacantCell(Rect current, CellInfo cellInfo) {
248 CellInfo.VacantCell cell = new CellInfo.VacantCell();
249 cell.cellX = current.left;
250 cell.cellY = current.top;
251 cell.spanX = current.right - current.left + 1;
252 cell.spanY = current.bottom - current.top + 1;
253 if (cell.spanX > cellInfo.maxVacantSpanX) {
254 cellInfo.maxVacantSpanX = cell.spanX;
255 cellInfo.maxVacantSpanXSpanY = cell.spanY;
256 }
257 if (cell.spanY > cellInfo.maxVacantSpanY) {
258 cellInfo.maxVacantSpanY = cell.spanY;
259 cellInfo.maxVacantSpanYSpanX = cell.spanX;
260 }
261 cellInfo.vacantCells.add(cell);
262 }
263
264 private static boolean isColumnEmpty(int x, int top, int bottom, boolean[][] occupied) {
265 for (int y = top; y <= bottom; y++) {
266 if (occupied[x][y]) {
267 return false;
268 }
269 }
270 return true;
271 }
272
273 private static boolean isRowEmpty(int y, int left, int right, boolean[][] occupied) {
274 for (int x = left; x <= right; x++) {
275 if (occupied[x][y]) {
276 return false;
277 }
278 }
279 return true;
280 }
281
282 CellInfo findAllVacantCells(boolean[] occupiedCells) {
283 final boolean portrait = mPortrait;
284 final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
285 final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
286
287 boolean[][] occupied = mOccupied;
288
289 if (occupiedCells != null) {
290 for (int y = 0; y < yCount; y++) {
291 for (int x = 0; x < xCount; x++) {
292 occupied[x][y] = occupiedCells[y * xCount + x];
293 }
294 }
295 } else {
296 findOccupiedCells(xCount, yCount, occupied);
297 }
298
299 CellInfo cellInfo = new CellInfo();
300
301 cellInfo.cellX = -1;
302 cellInfo.cellY = -1;
303 cellInfo.spanY = 0;
304 cellInfo.spanX = 0;
305 cellInfo.maxVacantSpanX = Integer.MIN_VALUE;
306 cellInfo.maxVacantSpanXSpanY = Integer.MIN_VALUE;
307 cellInfo.maxVacantSpanY = Integer.MIN_VALUE;
308 cellInfo.maxVacantSpanYSpanX = Integer.MIN_VALUE;
309 cellInfo.vacantCells = new ArrayList<CellInfo.VacantCell>();
310 cellInfo.screen = mCellInfo.screen;
311
312 Rect current = new Rect();
313
314 for (int x = 0; x < xCount; x++) {
315 for (int y = 0; y < yCount; y++) {
316 if (!occupied[x][y]) {
317 current.set(x, y, x, y);
318 findVacantCell(current, xCount, yCount, occupied, cellInfo);
319 occupied[x][y] = true;
320 }
321 }
322 }
323
324 cellInfo.valid = cellInfo.vacantCells.size() > 0;
325 if (cellInfo.valid) {
326 int[] xy = new int[2];
327 if (cellInfo.findCellForSpan(xy, 1, 1)) {
328 cellInfo.cellX = xy[0];
329 cellInfo.cellY = xy[1];
330 cellInfo.spanY = 1;
331 cellInfo.spanX = 1;
332 }
333 }
334
335 return cellInfo;
336 }
337
338 /**
339 * Given a point, return the cell that strictly encloses that point
340 * @param x X coordinate of the point
341 * @param y Y coordinate of the point
342 * @param result Array of 2 ints to hold the x and y coordinate of the cell
343 */
344 void pointToCellExact(int x, int y, int[] result) {
345 final boolean portrait = mPortrait;
346
347 final int hStartPadding = portrait ? mShortAxisStartPadding : mLongAxisStartPadding;
348 final int vStartPadding = portrait ? mLongAxisStartPadding : mShortAxisStartPadding;
349
350 result[0] = (x - hStartPadding) / (mCellWidth + mWidthGap);
351 result[1] = (y - vStartPadding) / (mCellHeight + mHeightGap);
352
353 final int xAxis = portrait ? mShortAxisCells : mLongAxisCells;
354 final int yAxis = portrait ? mLongAxisCells : mShortAxisCells;
355
356 if (result[0] < 0) result[0] = 0;
357 if (result[0] >= xAxis) result[0] = xAxis - 1;
358 if (result[1] < 0) result[1] = 0;
359 if (result[1] >= yAxis) result[1] = yAxis - 1;
360 }
361
362 /**
363 * Given a point, return the cell that most closely encloses that point
364 * @param x X coordinate of the point
365 * @param y Y coordinate of the point
366 * @param result Array of 2 ints to hold the x and y coordinate of the cell
367 */
368 void pointToCellRounded(int x, int y, int[] result) {
369 pointToCellExact(x + (mCellWidth / 2), y + (mCellHeight / 2), result);
370 }
371
372 /**
373 * Given a cell coordinate, return the point that represents the upper left corner of that cell
374 *
375 * @param cellX X coordinate of the cell
376 * @param cellY Y coordinate of the cell
377 *
378 * @param result Array of 2 ints to hold the x and y coordinate of the point
379 */
380 void cellToPoint(int cellX, int cellY, int[] result) {
381 final boolean portrait = mPortrait;
382
383 final int hStartPadding = portrait ? mShortAxisStartPadding : mLongAxisStartPadding;
384 final int vStartPadding = portrait ? mLongAxisStartPadding : mShortAxisStartPadding;
385
386
387 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap);
388 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap);
389 }
390
391 @Override
392 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
393 // TODO: currently ignoring padding
394
395 int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
396 int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
397
398 int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
399 int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
400
401 if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
402 throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
403 }
404
405 final int shortAxisCells = mShortAxisCells;
406 final int longAxisCells = mLongAxisCells;
407 final int longAxisStartPadding = mLongAxisStartPadding;
408 final int longAxisEndPadding = mLongAxisEndPadding;
409 final int shortAxisStartPadding = mShortAxisStartPadding;
410 final int shortAxisEndPadding = mShortAxisEndPadding;
411 final int cellWidth = mCellWidth;
412 final int cellHeight = mCellHeight;
413
414 mPortrait = heightSpecSize > widthSpecSize;
415
416 int numShortGaps = shortAxisCells - 1;
417 int numLongGaps = longAxisCells - 1;
418
419 if (mPortrait) {
420 int vSpaceLeft = heightSpecSize - longAxisStartPadding - longAxisEndPadding
421 - (cellHeight * longAxisCells);
422 mHeightGap = vSpaceLeft / numLongGaps;
423
424 int hSpaceLeft = widthSpecSize - shortAxisStartPadding - shortAxisEndPadding
425 - (cellWidth * shortAxisCells);
426 if (numShortGaps > 0) {
427 mWidthGap = hSpaceLeft / numShortGaps;
428 } else {
429 mWidthGap = 0;
430 }
431 } else {
432 int hSpaceLeft = widthSpecSize - longAxisStartPadding - longAxisEndPadding
433 - (cellWidth * longAxisCells);
434 mWidthGap = hSpaceLeft / numLongGaps;
435
436 int vSpaceLeft = heightSpecSize - shortAxisStartPadding - shortAxisEndPadding
437 - (cellHeight * shortAxisCells);
438 if (numShortGaps > 0) {
439 mHeightGap = vSpaceLeft / numShortGaps;
440 } else {
441 mHeightGap = 0;
442 }
443 }
444
445 int count = getChildCount();
446
447 for (int i = 0; i < count; i++) {
448 View child = getChildAt(i);
449 LayoutParams lp = (LayoutParams) child.getLayoutParams();
450
451 if (mPortrait) {
452 lp.setup(cellWidth, cellHeight, mWidthGap, mHeightGap, shortAxisStartPadding,
453 longAxisStartPadding);
454 } else {
455 lp.setup(cellWidth, cellHeight, mWidthGap, mHeightGap, longAxisStartPadding,
456 shortAxisStartPadding);
457 }
458
459 int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY);
460 int childheightMeasureSpec =
461 MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY);
462 child.measure(childWidthMeasureSpec, childheightMeasureSpec);
463 }
464
465 setMeasuredDimension(widthSpecSize, heightSpecSize);
466 }
467
468 @Override
469 protected void onLayout(boolean changed, int l, int t, int r, int b) {
470 int count = getChildCount();
471
472 for (int i = 0; i < count; i++) {
473 View child = getChildAt(i);
474 if (child.getVisibility() != GONE) {
475
476 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
477
478 int childLeft = lp.x;
479 int childTop = lp.y;
480 child.layout(childLeft, childTop, childLeft + lp.width, childTop + lp.height);
481 }
482 }
483 }
484
485 @Override
486 protected void setChildrenDrawingCacheEnabled(boolean enabled) {
487 final int count = getChildCount();
488 for (int i = 0; i < count; i++) {
489 final View view = getChildAt(i);
490 view.setDrawingCacheEnabled(enabled);
491 // Update the drawing caches
492 view.buildDrawingCache();
493 }
494 }
495
496 @Override
497 protected void setChildrenDrawnWithCacheEnabled(boolean enabled) {
498 super.setChildrenDrawnWithCacheEnabled(enabled);
499 }
500
501 boolean acceptChildDrop(int x, int y, int cellHSpan, int cellVSpan, View cell) {
502 int[] cellXY = mCellXY;
503 pointToCellRounded(x, y, cellXY);
504 int cellX = cellXY[0];
505 int cellY = cellXY[1];
506
507 return findCell(cellX, cellY, cellHSpan, cellVSpan, cell) == null;
508 }
509
510 /**
511 * Finds the first View intersecting with the specified cell. If the cell is outside
512 * of the layout, this is returned.
513 *
514 * @param cellX The X location of the cell to test.
515 * @param cellY The Y location of the cell to test.
516 * @param cellHSpan The horizontal span of the cell to test.
517 * @param cellVSpan The vertical span of the cell to test.
518 * @param ignoreCell View to ignore during the test.
519 *
520 * @return Returns the first View intersecting with the specified cell, this if the cell
521 * lies outside of this layout's grid or null if no View was found.
522 */
523 View findCell(int cellX, int cellY, int cellHSpan, int cellVSpan, View ignoreCell) {
524 if (cellX < 0 || cellX + cellHSpan > (mPortrait ? mShortAxisCells : mLongAxisCells) ||
525 cellY < 0 || cellY + cellVSpan > (mPortrait ? mLongAxisCells : mShortAxisCells)) {
526 return this;
527 }
528
529 final int count = getChildCount();
530 for (int i = 0; i < count; i++) {
531 final View view = getChildAt(i);
532 if (view == ignoreCell) {
533 continue;
534 }
535
536 final LayoutParams lp = (LayoutParams) view.getLayoutParams();
537 if (cellX < lp.cellX + lp.cellHSpan && lp.cellX < cellX + cellHSpan &&
538 cellY < lp.cellY + lp.cellVSpan && lp.cellY < cellY + cellVSpan) {
539 return view;
540 }
541 }
542
543 return null;
544 }
545
546 /**
547 * Drop a child at the specified position
548 *
549 * @param child The child that is being dropped
550 * @param cellX The child's new x location
551 * @param cellY The child's new y location
552 */
553 void onDropChild(View child, int cellX, int cellY) {
554 int[] cellXY = mCellXY;
555 pointToCellRounded(cellX, cellY, cellXY);
556 LayoutParams lp = (LayoutParams) child.getLayoutParams();
557 lp.cellX = cellXY[0];
558 lp.cellY = cellXY[1];
559 lp.isDragging = false;
560 mDragRect.setEmpty();
561 child.requestLayout();
562 invalidate();
563 }
564
565 void onDropAborted(View child) {
566 if (child != null) {
567 ((LayoutParams) child.getLayoutParams()).isDragging = false;
568 invalidate();
569 }
570 mDragRect.setEmpty();
571 }
572
573 /**
574 * Start dragging the specified child
575 *
576 * @param child The child that is being dragged
577 */
578 void onDragChild(View child) {
579 LayoutParams lp = (LayoutParams) child.getLayoutParams();
580 lp.isDragging = true;
581 mDragRect.setEmpty();
582 }
583
584 /**
585 * Drag a child over the specified position
586 *
587 * @param child The child that is being dropped
588 * @param cellX The child's new x cell location
589 * @param cellY The child's new y cell location
590 */
591 void onDragOverChild(View child, int cellX, int cellY) {
592 int[] cellXY = mCellXY;
593 pointToCellRounded(cellX, cellY, cellXY);
594 LayoutParams lp = (LayoutParams) child.getLayoutParams();
595 cellToRect(cellXY[0], cellXY[1], lp.cellHSpan, lp.cellVSpan, mDragRect);
596 invalidate();
597 }
598
599 /**
600 * Computes a bounding rectangle for a range of cells
601 *
602 * @param cellX X coordinate of upper left corner expressed as a cell position
603 * @param cellY Y coordinate of upper left corner expressed as a cell position
604 * @param cellHSpan Width in cells
605 * @param cellVSpan Height in cells
606 * @param dragRect Rectnagle into which to put the results
607 */
608 public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, RectF dragRect) {
609 final boolean portrait = mPortrait;
610 final int cellWidth = mCellWidth;
611 final int cellHeight = mCellHeight;
612 final int widthGap = mWidthGap;
613 final int heightGap = mHeightGap;
614
615 final int hStartPadding = portrait ? mShortAxisStartPadding : mLongAxisStartPadding;
616 final int vStartPadding = portrait ? mLongAxisStartPadding : mShortAxisStartPadding;
617
618 int width = cellHSpan * cellWidth + ((cellHSpan - 1) * widthGap);
619 int height = cellVSpan * cellHeight + ((cellVSpan - 1) * heightGap);
620
621 int x = hStartPadding + cellX * (cellWidth + widthGap);
622 int y = vStartPadding + cellY * (cellHeight + heightGap);
623
624 dragRect.set(x, y, x + width, y + height);
625 }
626
627 /**
628 * Find the first vacant cell, if there is one.
629 *
630 * @param vacant Holds the x and y coordinate of the vacant cell
631 * @param spanX Horizontal cell span.
632 * @param spanY Vertical cell span.
633 *
634 * @return True if a vacant cell was found
635 */
636 public boolean getVacantCell(int[] vacant, int spanX, int spanY) {
637 final boolean portrait = mPortrait;
638 final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
639 final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
640 final boolean[][] occupied = mOccupied;
641
642 findOccupiedCells(xCount, yCount, occupied);
643
644 return findVacantCell(vacant, spanX, spanY, xCount, yCount, occupied);
645 }
646
647 static boolean findVacantCell(int[] vacant, int spanX, int spanY,
648 int xCount, int yCount, boolean[][] occupied) {
649
650 for (int x = 0; x < xCount; x++) {
651 for (int y = 0; y < yCount; y++) {
652 boolean available = !occupied[x][y];
653out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) {
654 for (int j = y; j < y + spanY - 1 && y < yCount; j++) {
655 available = available && !occupied[i][j];
656 if (!available) break out;
657 }
658 }
659
660 if (available) {
661 vacant[0] = x;
662 vacant[1] = y;
663 return true;
664 }
665 }
666 }
667
668 return false;
669 }
670
671 boolean[] getOccupiedCells() {
672 final boolean portrait = mPortrait;
673 final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
674 final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
675 final boolean[][] occupied = mOccupied;
676
677 findOccupiedCells(xCount, yCount, occupied);
678
679 final boolean[] flat = new boolean[xCount * yCount];
680 for (int y = 0; y < yCount; y++) {
681 for (int x = 0; x < xCount; x++) {
682 flat[y * xCount + x] = occupied[x][y];
683 }
684 }
685
686 return flat;
687 }
688
689 private void findOccupiedCells(int xCount, int yCount, boolean[][] occupied) {
690 for (int x = 0; x < xCount; x++) {
691 for (int y = 0; y < yCount; y++) {
692 occupied[x][y] = false;
693 }
694 }
695
696 int count = getChildCount();
697 for (int i = 0; i < count; i++) {
698 View child = getChildAt(i);
699 if (child instanceof Folder) {
700 continue;
701 }
702 LayoutParams lp = (LayoutParams) child.getLayoutParams();
703
704 for (int x = lp.cellX; x < lp.cellX + lp.cellHSpan && x < xCount; x++) {
705 for (int y = lp.cellY; y < lp.cellY + lp.cellVSpan && y < yCount; y++) {
706 occupied[x][y] = true;
707 }
708 }
709 }
710 }
711
712 @Override
713 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
714 return new CellLayout.LayoutParams(getContext(), attrs);
715 }
716
717 @Override
718 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
719 return p instanceof CellLayout.LayoutParams;
720 }
721
722 @Override
723 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
724 return new CellLayout.LayoutParams(p);
725 }
726
727 public static class LayoutParams extends ViewGroup.MarginLayoutParams {
728 /**
729 * Horizontal location of the item in the grid.
730 */
731 @ViewDebug.ExportedProperty
732 public int cellX;
733
734 /**
735 * Vertical location of the item in the grid.
736 */
737 @ViewDebug.ExportedProperty
738 public int cellY;
739
740 /**
741 * Number of cells spanned horizontally by the item.
742 */
743 @ViewDebug.ExportedProperty
744 public int cellHSpan;
745
746 /**
747 * Number of cells spanned vertically by the item.
748 */
749 @ViewDebug.ExportedProperty
750 public int cellVSpan;
751
752 /**
753 * Is this item currently being dragged
754 */
755 public boolean isDragging;
756
757 // X coordinate of the view in the layout.
758 @ViewDebug.ExportedProperty
759 int x;
760 // Y coordinate of the view in the layout.
761 @ViewDebug.ExportedProperty
762 int y;
763
764 public LayoutParams(Context c, AttributeSet attrs) {
765 super(c, attrs);
766 cellHSpan = 1;
767 cellVSpan = 1;
768 }
769
770 public LayoutParams(ViewGroup.LayoutParams source) {
771 super(source);
772 cellHSpan = 1;
773 cellVSpan = 1;
774 }
775
776 public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
777 super(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
778 this.cellX = cellX;
779 this.cellY = cellY;
780 this.cellHSpan = cellHSpan;
781 this.cellVSpan = cellVSpan;
782 }
783
784 public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap,
785 int hStartPadding, int vStartPadding) {
786
787 final int myCellHSpan = cellHSpan;
788 final int myCellVSpan = cellVSpan;
789 final int myCellX = cellX;
790 final int myCellY = cellY;
791
792 width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) -
793 leftMargin - rightMargin;
794 height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) -
795 topMargin - bottomMargin;
796
797 x = hStartPadding + myCellX * (cellWidth + widthGap) + leftMargin;
798 y = vStartPadding + myCellY * (cellHeight + heightGap) + topMargin;
799 }
800 }
801
802 static final class CellInfo implements ContextMenu.ContextMenuInfo {
803 static final class VacantCell {
804 int cellX;
805 int cellY;
806 int spanX;
807 int spanY;
808
809 @Override
810 public String toString() {
811 return "VacantCell[x=" + cellX + ", y=" + cellY + ", spanX=" + spanX +
812 ", spanY=" + spanY + "]";
813 }
814 }
815
816 View cell;
817 int cellX;
818 int cellY;
819 int spanX;
820 int spanY;
821 int screen;
822 boolean valid;
823
824 ArrayList<VacantCell> vacantCells;
825 int maxVacantSpanX;
826 int maxVacantSpanXSpanY;
827 int maxVacantSpanY;
828 int maxVacantSpanYSpanX;
829
830 void findVacantCellsFromOccupied(boolean[] occupied, int xCount, int yCount) {
831 if (cellX < 0 || cellY < 0) {
832 maxVacantSpanX = maxVacantSpanXSpanY = Integer.MIN_VALUE;
833 maxVacantSpanY = maxVacantSpanYSpanX = Integer.MIN_VALUE;
834 vacantCells = new ArrayList<VacantCell>();
835 return;
836 }
837
838 final boolean[][] unflattened = new boolean[xCount][yCount];
839 for (int y = 0; y < yCount; y++) {
840 for (int x = 0; x < xCount; x++) {
841 unflattened[x][y] = occupied[y * xCount + x];
842 }
843 }
844 CellLayout.findIntersectingVacantCells(this, cellX, cellY, xCount, yCount, unflattened);
845 }
846
847 boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
848 if (vacantCells == null) {
849 return false;
850 }
851
852 if (this.spanX >= spanX && this.spanY >= spanY) {
853 cellXY[0] = cellX;
854 cellXY[1] = cellY;
855 return true;
856 }
857
858 final ArrayList<VacantCell> list = vacantCells;
859 final int count = list.size();
860 // Look for an exact match first
861 for (int i = 0; i < count; i++) {
862 VacantCell cell = list.get(i);
863 if (cell.spanX == spanX && cell.spanY == spanY) {
864 cellXY[0] = cell.cellX;
865 cellXY[1] = cell.cellY;
866 return true;
867 }
868 }
869
870 // Look for the first cell large enough
871 for (int i = 0; i < count; i++) {
872 VacantCell cell = list.get(i);
873 if (cell.spanX >= spanX && cell.spanY >= spanY) {
874 cellXY[0] = cell.cellX;
875 cellXY[1] = cell.cellY;
876 return true;
877 }
878 }
879
880 return false;
881 }
882
883 @Override
884 public String toString() {
885 return "Cell[view=" + (cell == null ? "null" : cell.getClass()) + ", x=" + cellX +
886 ", y=" + cellY + "]";
887 }
888 }
889}
890
891