2 * Copyright (C) 2008 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.launcher;
19 import android.content.Context;
20 import android.content.res.TypedArray;
21 import android.graphics.Rect;
22 import android.graphics.RectF;
23 import android.util.AttributeSet;
24 import android.view.ContextMenu;
25 import android.view.MotionEvent;
26 import android.view.View;
27 import android.view.ViewDebug;
28 import android.view.ViewGroup;
30 import java.util.ArrayList;
32 public class CellLayout extends ViewGroup {
33 private boolean mPortrait;
35 private int mCellWidth;
36 private int mCellHeight;
38 private int mLongAxisStartPadding;
39 private int mLongAxisEndPadding;
41 private int mShortAxisStartPadding;
42 private int mShortAxisEndPadding;
44 private int mShortAxisCells;
45 private int mLongAxisCells;
47 private int mWidthGap;
48 private int mHeightGap;
50 private final Rect mRect = new Rect();
51 private final CellInfo mCellInfo = new CellInfo();
53 int[] mCellXY = new int[2];
55 boolean[][] mOccupied;
57 private RectF mDragRect = new RectF();
59 private boolean mDirtyTag;
61 public CellLayout(Context context) {
65 public CellLayout(Context context, AttributeSet attrs) {
66 this(context, attrs, 0);
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);
73 mCellWidth = a.getDimensionPixelSize(R.styleable.CellLayout_cellWidth, 10);
74 mCellHeight = a.getDimensionPixelSize(R.styleable.CellLayout_cellHeight, 10);
76 mLongAxisStartPadding =
77 a.getDimensionPixelSize(R.styleable.CellLayout_longAxisStartPadding, 10);
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);
85 mShortAxisCells = a.getInt(R.styleable.CellLayout_shortAxisCells, 4);
86 mLongAxisCells = a.getInt(R.styleable.CellLayout_longAxisCells, 4);
90 setAlwaysDrawnWithCacheEnabled(false);
92 if (mOccupied == null) {
94 mOccupied = new boolean[mShortAxisCells][mLongAxisCells];
96 mOccupied = new boolean[mLongAxisCells][mShortAxisCells];
102 return mPortrait ? mShortAxisCells : mLongAxisCells;
106 return mPortrait ? mLongAxisCells : mShortAxisCells;
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));
117 super.addView(child, index, params);
121 public void requestChildFocus(View child, View focused) {
122 super.requestChildFocus(child, focused);
125 child.getDrawingRect(r);
126 requestRectangleOnScreen(r);
131 protected void onAttachedToWindow() {
132 super.onAttachedToWindow();
133 mCellInfo.screen = ((ViewGroup) getParent()).indexOfChild(this);
137 public boolean onInterceptTouchEvent(MotionEvent ev) {
138 final int action = ev.getAction();
139 final CellInfo cellInfo = mCellInfo;
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();
147 boolean found = false;
148 for (int i = count - 1; i >= 0; i--) {
149 final View child = getChildAt(i);
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;
169 int cellXY[] = mCellXY;
170 pointToCellExact(x, y, cellXY);
172 final boolean portrait = mPortrait;
173 final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
174 final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
176 final boolean[][] occupied = mOccupied;
177 findOccupiedCells(xCount, yCount, occupied);
179 cellInfo.cell = null;
180 cellInfo.cellX = cellXY[0];
181 cellInfo.cellY = cellXY[1];
184 cellInfo.valid = cellXY[0] >= 0 && cellXY[1] >= 0 && cellXY[0] < xCount &&
185 cellXY[1] < yCount && !occupied[cellXY[0]][cellXY[1]];
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.
195 } else if (action == MotionEvent.ACTION_UP) {
196 cellInfo.cell = null;
201 cellInfo.valid = false;
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;
217 final boolean[][] occupied = mOccupied;
218 findOccupiedCells(xCount, yCount, occupied);
220 findIntersectingVacantCells(info, info.cellX, info.cellY, xCount, yCount, occupied);
227 private static void findIntersectingVacantCells(CellInfo cellInfo, int x, int y,
228 int xCount, int yCount, boolean[][] occupied) {
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();
236 if (occupied[x][y]) {
240 cellInfo.current.set(x, y, x, y);
242 findVacantCell(cellInfo.current, xCount, yCount, occupied, cellInfo);
245 private static void findVacantCell(Rect current, int xCount, int yCount, boolean[][] occupied,
248 addVacantCell(current, cellInfo);
250 if (current.left > 0) {
251 if (isColumnEmpty(current.left - 1, current.top, current.bottom, occupied)) {
253 findVacantCell(current, xCount, yCount, occupied, cellInfo);
258 if (current.right < xCount - 1) {
259 if (isColumnEmpty(current.right + 1, current.top, current.bottom, occupied)) {
261 findVacantCell(current, xCount, yCount, occupied, cellInfo);
266 if (current.top > 0) {
267 if (isRowEmpty(current.top - 1, current.left, current.right, occupied)) {
269 findVacantCell(current, xCount, yCount, occupied, cellInfo);
274 if (current.bottom < yCount - 1) {
275 if (isRowEmpty(current.bottom + 1, current.left, current.right, occupied)) {
277 findVacantCell(current, xCount, yCount, occupied, cellInfo);
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;
293 if (cell.spanY > cellInfo.maxVacantSpanY) {
294 cellInfo.maxVacantSpanY = cell.spanY;
295 cellInfo.maxVacantSpanYSpanX = cell.spanX;
297 cellInfo.vacantCells.add(cell);
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]) {
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]) {
318 CellInfo findAllVacantCells(boolean[] occupiedCells) {
319 final boolean portrait = mPortrait;
320 final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
321 final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
323 boolean[][] occupied = mOccupied;
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];
332 findOccupiedCells(xCount, yCount, occupied);
335 CellInfo cellInfo = new CellInfo();
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;
347 Rect current = cellInfo.current;
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;
359 cellInfo.valid = cellInfo.vacantCells.size() > 0;
361 // Assume the caller will perform their own cell searching, otherwise we
362 // risk causing an unnecessary rebuild after findCellForSpan()
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
373 void pointToCellExact(int x, int y, int[] result) {
374 final boolean portrait = mPortrait;
376 final int hStartPadding = portrait ? mShortAxisStartPadding : mLongAxisStartPadding;
377 final int vStartPadding = portrait ? mLongAxisStartPadding : mShortAxisStartPadding;
379 result[0] = (x - hStartPadding) / (mCellWidth + mWidthGap);
380 result[1] = (y - vStartPadding) / (mCellHeight + mHeightGap);
382 final int xAxis = portrait ? mShortAxisCells : mLongAxisCells;
383 final int yAxis = portrait ? mLongAxisCells : mShortAxisCells;
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;
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
397 void pointToCellRounded(int x, int y, int[] result) {
398 pointToCellExact(x + (mCellWidth / 2), y + (mCellHeight / 2), result);
402 * Given a cell coordinate, return the point that represents the upper left corner of that cell
404 * @param cellX X coordinate of the cell
405 * @param cellY Y coordinate of the cell
407 * @param result Array of 2 ints to hold the x and y coordinate of the point
409 void cellToPoint(int cellX, int cellY, int[] result) {
410 final boolean portrait = mPortrait;
412 final int hStartPadding = portrait ? mShortAxisStartPadding : mLongAxisStartPadding;
413 final int vStartPadding = portrait ? mLongAxisStartPadding : mShortAxisStartPadding;
416 result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap);
417 result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap);
421 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
422 // TODO: currently ignoring padding
424 int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
425 int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
427 int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
428 int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
430 if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
431 throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
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;
443 mPortrait = heightSpecSize > widthSpecSize;
445 int numShortGaps = shortAxisCells - 1;
446 int numLongGaps = longAxisCells - 1;
449 int vSpaceLeft = heightSpecSize - longAxisStartPadding - longAxisEndPadding
450 - (cellHeight * longAxisCells);
451 mHeightGap = vSpaceLeft / numLongGaps;
453 int hSpaceLeft = widthSpecSize - shortAxisStartPadding - shortAxisEndPadding
454 - (cellWidth * shortAxisCells);
455 if (numShortGaps > 0) {
456 mWidthGap = hSpaceLeft / numShortGaps;
461 int hSpaceLeft = widthSpecSize - longAxisStartPadding - longAxisEndPadding
462 - (cellWidth * longAxisCells);
463 mWidthGap = hSpaceLeft / numLongGaps;
465 int vSpaceLeft = heightSpecSize - shortAxisStartPadding - shortAxisEndPadding
466 - (cellHeight * shortAxisCells);
467 if (numShortGaps > 0) {
468 mHeightGap = vSpaceLeft / numShortGaps;
474 int count = getChildCount();
476 for (int i = 0; i < count; i++) {
477 View child = getChildAt(i);
478 LayoutParams lp = (LayoutParams) child.getLayoutParams();
481 lp.setup(cellWidth, cellHeight, mWidthGap, mHeightGap, shortAxisStartPadding,
482 longAxisStartPadding);
484 lp.setup(cellWidth, cellHeight, mWidthGap, mHeightGap, longAxisStartPadding,
485 shortAxisStartPadding);
488 int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY);
489 int childheightMeasureSpec =
490 MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY);
491 child.measure(childWidthMeasureSpec, childheightMeasureSpec);
494 setMeasuredDimension(widthSpecSize, heightSpecSize);
498 protected void onLayout(boolean changed, int l, int t, int r, int b) {
499 int count = getChildCount();
501 for (int i = 0; i < count; i++) {
502 View child = getChildAt(i);
503 if (child.getVisibility() != GONE) {
505 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
507 int childLeft = lp.x;
509 child.layout(childLeft, childTop, childLeft + lp.width, childTop + lp.height);
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();
526 protected void setChildrenDrawnWithCacheEnabled(boolean enabled) {
527 super.setChildrenDrawnWithCacheEnabled(enabled);
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];
536 return findCell(cellX, cellY, cellHSpan, cellVSpan, cell) == null;
540 * Finds the first View intersecting with the specified cell. If the cell is outside
541 * of the layout, this is returned.
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.
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.
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)) {
558 final int count = getChildCount();
559 for (int i = 0; i < count; i++) {
560 final View view = getChildAt(i);
561 if (view == ignoreCell) {
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) {
576 * Drop a child at the specified position
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
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();
594 void onDropAborted(View child) {
596 ((LayoutParams) child.getLayoutParams()).isDragging = false;
599 mDragRect.setEmpty();
603 * Start dragging the specified child
605 * @param child The child that is being dragged
607 void onDragChild(View child) {
608 LayoutParams lp = (LayoutParams) child.getLayoutParams();
609 lp.isDragging = true;
610 mDragRect.setEmpty();
614 * Drag a child over the specified position
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
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);
629 * Computes a bounding rectangle for a range of cells
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
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;
644 final int hStartPadding = portrait ? mShortAxisStartPadding : mLongAxisStartPadding;
645 final int vStartPadding = portrait ? mLongAxisStartPadding : mShortAxisStartPadding;
647 int width = cellHSpan * cellWidth + ((cellHSpan - 1) * widthGap);
648 int height = cellVSpan * cellHeight + ((cellVSpan - 1) * heightGap);
650 int x = hStartPadding + cellX * (cellWidth + widthGap);
651 int y = vStartPadding + cellY * (cellHeight + heightGap);
653 dragRect.set(x, y, x + width, y + height);
657 * Computes the required horizontal and vertical cell spans to always
658 * fit the given rectangle.
660 * @param width Width in pixels
661 * @param height Height in pixels
662 * @param Horizontal and vertical spans required
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);
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 };
678 * Find the first vacant cell, if there is one.
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.
684 * @return True if a vacant cell was found
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;
692 findOccupiedCells(xCount, yCount, occupied);
694 return findVacantCell(vacant, spanX, spanY, xCount, yCount, occupied);
697 static boolean findVacantCell(int[] vacant, int spanX, int spanY,
698 int xCount, int yCount, boolean[][] occupied) {
700 for (int x = 0; x < xCount; x++) {
701 for (int y = 0; y < yCount; y++) {
702 boolean available = !occupied[x][y];
703 out: 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;
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;
727 findOccupiedCells(xCount, yCount, occupied);
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];
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;
746 int count = getChildCount();
747 for (int i = 0; i < count; i++) {
748 View child = getChildAt(i);
749 if (child instanceof Folder) {
752 LayoutParams lp = (LayoutParams) child.getLayoutParams();
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;
763 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
764 return new CellLayout.LayoutParams(getContext(), attrs);
768 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
769 return p instanceof CellLayout.LayoutParams;
773 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
774 return new CellLayout.LayoutParams(p);
777 public static class LayoutParams extends ViewGroup.MarginLayoutParams {
779 * Horizontal location of the item in the grid.
781 @ViewDebug.ExportedProperty
785 * Vertical location of the item in the grid.
787 @ViewDebug.ExportedProperty
791 * Number of cells spanned horizontally by the item.
793 @ViewDebug.ExportedProperty
794 public int cellHSpan;
797 * Number of cells spanned vertically by the item.
799 @ViewDebug.ExportedProperty
800 public int cellVSpan;
803 * Is this item currently being dragged
805 public boolean isDragging;
807 // X coordinate of the view in the layout.
808 @ViewDebug.ExportedProperty
810 // Y coordinate of the view in the layout.
811 @ViewDebug.ExportedProperty
814 public LayoutParams(Context c, AttributeSet attrs) {
820 public LayoutParams(ViewGroup.LayoutParams source) {
826 public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
827 super(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
830 this.cellHSpan = cellHSpan;
831 this.cellVSpan = cellVSpan;
834 public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap,
835 int hStartPadding, int vStartPadding) {
837 final int myCellHSpan = cellHSpan;
838 final int myCellVSpan = cellVSpan;
839 final int myCellX = cellX;
840 final int myCellY = cellY;
842 width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) -
843 leftMargin - rightMargin;
844 height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) -
845 topMargin - bottomMargin;
847 x = hStartPadding + myCellX * (cellWidth + widthGap) + leftMargin;
848 y = vStartPadding + myCellY * (cellHeight + heightGap) + topMargin;
852 static final class CellInfo implements ContextMenu.ContextMenuInfo {
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.
859 static final class VacantCell {
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
869 private static final int POOL_LIMIT = 100;
870 private static final Object sLock = new Object();
872 private static int sAcquiredCount = 0;
873 private static VacantCell sRoot;
875 private VacantCell next;
877 static VacantCell acquire() {
878 synchronized (sLock) {
880 return new VacantCell();
883 VacantCell info = sRoot;
892 synchronized (sLock) {
893 if (sAcquiredCount < POOL_LIMIT) {
902 public String toString() {
903 return "VacantCell[x=" + cellX + ", y=" + cellY + ", spanX=" + spanX +
904 ", spanY=" + spanY + "]";
916 final ArrayList<VacantCell> vacantCells = new ArrayList<VacantCell>(VacantCell.POOL_LIMIT);
918 int maxVacantSpanXSpanY;
920 int maxVacantSpanYSpanX;
921 final Rect current = new Rect();
923 private void clearVacantCells() {
924 final ArrayList<VacantCell> list = vacantCells;
925 final int count = list.size();
927 for (int i = 0; i < count; i++) list.get(i).release();
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;
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];
946 CellLayout.findIntersectingVacantCells(this, cellX, cellY, xCount, yCount, unflattened);
950 * This method can be called only once! Calling #findVacantCellsFromOccupied will
951 * restore the ability to call this method.
953 * Finds the upper-left coordinate of the first rectangle in the grid that can
954 * hold a cell of the specified dimensions.
956 * @param cellXY The array that will contain the position of a vacant cell if such a cell
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.
961 * @return True if a vacant cell of the specified dimension was found, false otherwise.
963 boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
964 final ArrayList<VacantCell> list = vacantCells;
965 final int count = list.size();
967 boolean found = false;
969 if (this.spanX >= spanX && this.spanY >= spanY) {
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;
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;
1003 public String toString() {
1004 return "Cell[view=" + (cell == null ? "null" : cell.getClass()) + ", x=" + cellX +
1005 ", y=" + cellY + "]";