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