summaryrefslogtreecommitdiffstats
path: root/src/com/android/launcher/CellLayout.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/launcher/CellLayout.java')
-rw-r--r--src/com/android/launcher/CellLayout.java891
1 files changed, 891 insertions, 0 deletions
diff --git a/src/com/android/launcher/CellLayout.java b/src/com/android/launcher/CellLayout.java
new file mode 100644
index 000000000..03cf7fcef
--- /dev/null
+++ b/src/com/android/launcher/CellLayout.java
@@ -0,0 +1,891 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import android.view.ContextMenu;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewDebug;
+import android.view.ViewGroup;
+
+import java.util.ArrayList;
+
+public class CellLayout extends ViewGroup {
+ private boolean mPortrait;
+
+ private int mCellWidth;
+ private int mCellHeight;
+
+ private int mLongAxisStartPadding;
+ private int mLongAxisEndPadding;
+
+ private int mShortAxisStartPadding;
+ private int mShortAxisEndPadding;
+
+ private int mShortAxisCells;
+ private int mLongAxisCells;
+
+ private int mWidthGap;
+ private int mHeightGap;
+
+ private final Rect mRect = new Rect();
+ private final CellInfo mCellInfo = new CellInfo();
+
+ int[] mCellXY = new int[2];
+
+ boolean[][] mOccupied;
+
+ private RectF mDragRect = new RectF();
+
+ public CellLayout(Context context) {
+ this(context, null);
+ }
+
+ public CellLayout(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public CellLayout(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0);
+
+ mCellWidth = a.getDimensionPixelSize(R.styleable.CellLayout_cellWidth, 10);
+ mCellHeight = a.getDimensionPixelSize(R.styleable.CellLayout_cellHeight, 10);
+
+ mLongAxisStartPadding =
+ a.getDimensionPixelSize(R.styleable.CellLayout_longAxisStartPadding, 10);
+ mLongAxisEndPadding =
+ a.getDimensionPixelSize(R.styleable.CellLayout_longAxisEndPadding, 10);
+ mShortAxisStartPadding =
+ a.getDimensionPixelSize(R.styleable.CellLayout_shortAxisStartPadding, 10);
+ mShortAxisEndPadding =
+ a.getDimensionPixelSize(R.styleable.CellLayout_shortAxisEndPadding, 10);
+
+ mShortAxisCells = a.getInt(R.styleable.CellLayout_shortAxisCells, 4);
+ mLongAxisCells = a.getInt(R.styleable.CellLayout_longAxisCells, 4);
+
+ a.recycle();
+
+ setAlwaysDrawnWithCacheEnabled(false);
+
+ if (mOccupied == null) {
+ if (mPortrait) {
+ mOccupied = new boolean[mShortAxisCells][mLongAxisCells];
+ } else {
+ mOccupied = new boolean[mLongAxisCells][mShortAxisCells];
+ }
+ }
+ }
+
+ int getCountX() {
+ return mPortrait ? mShortAxisCells : mLongAxisCells;
+ }
+
+ int getCountY() {
+ return mPortrait ? mLongAxisCells : mShortAxisCells;
+ }
+
+ @Override
+ public void requestChildFocus(View child, View focused) {
+ super.requestChildFocus(child, focused);
+ if (child != null) {
+ Rect r = new Rect();
+ child.getDrawingRect(r);
+ requestRectangleOnScreen(r);
+ }
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mCellInfo.screen = ((ViewGroup) getParent()).indexOfChild(this);
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ final int action = ev.getAction();
+ final CellInfo cellInfo = mCellInfo;
+
+ if (action == MotionEvent.ACTION_DOWN) {
+ final Rect frame = mRect;
+ final int x = (int) ev.getX() + mScrollX;
+ final int y = (int) ev.getY() + mScrollY;
+ final int count = getChildCount();
+
+ boolean found = false;
+ for (int i = count - 1; i >= 0; i--) {
+ final View child = getChildAt(i);
+
+ if ((child.getVisibility()) == VISIBLE || child.getAnimation() != null) {
+ child.getHitRect(frame);
+ if (frame.contains(x, y)) {
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ cellInfo.cell = child;
+ cellInfo.cellX = lp.cellX;
+ cellInfo.cellY = lp.cellY;
+ cellInfo.spanX = lp.cellHSpan;
+ cellInfo.spanY = lp.cellVSpan;
+ cellInfo.valid = true;
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (!found) {
+ int cellXY[] = mCellXY;
+ pointToCellExact(x, y, cellXY);
+
+ final boolean portrait = mPortrait;
+ final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
+ final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
+
+ final boolean[][] occupied = mOccupied;
+ findOccupiedCells(xCount, yCount, occupied);
+
+ cellInfo.cell = null;
+ cellInfo.cellX = cellXY[0];
+ cellInfo.cellY = cellXY[1];
+ cellInfo.spanX = 1;
+ cellInfo.spanY = 1;
+ cellInfo.valid = cellXY[0] >= 0 && cellXY[1] >= 0 && cellXY[0] < xCount &&
+ cellXY[1] < yCount && !occupied[cellXY[0]][cellXY[1]];
+
+ if (cellInfo.valid) {
+ findIntersectingVacantCells(cellInfo, cellXY[0], cellXY[1],
+ xCount, yCount, occupied);
+ }
+ }
+ setTag(cellInfo);
+ } else if (action == MotionEvent.ACTION_UP) {
+ cellInfo.cell = null;
+ cellInfo.cellX = -1;
+ cellInfo.cellY = -1;
+ cellInfo.spanX = 0;
+ cellInfo.spanY = 0;
+ cellInfo.valid = false;
+ setTag(cellInfo);
+ }
+
+ return false;
+ }
+
+ private static void findIntersectingVacantCells(CellInfo cellInfo, int x, int y,
+ int xCount, int yCount, boolean[][] occupied) {
+
+ cellInfo.maxVacantSpanX = Integer.MIN_VALUE;
+ cellInfo.maxVacantSpanXSpanY = Integer.MIN_VALUE;
+ cellInfo.maxVacantSpanY = Integer.MIN_VALUE;
+ cellInfo.maxVacantSpanYSpanX = Integer.MIN_VALUE;
+ cellInfo.vacantCells = new ArrayList<CellInfo.VacantCell>();
+
+ if (occupied[x][y]) {
+ return;
+ }
+
+ Rect current = new Rect(x, y, x, y);
+ findVacantCell(current, xCount, yCount, occupied, cellInfo);
+ }
+
+ private static void findVacantCell(Rect current, int xCount, int yCount, boolean[][] occupied,
+ CellInfo cellInfo) {
+
+ addVacantCell(current, cellInfo);
+
+ if (current.left > 0) {
+ if (isColumnEmpty(current.left - 1, current.top, current.bottom, occupied)) {
+ current.left--;
+ findVacantCell(current, xCount, yCount, occupied, cellInfo);
+ current.left++;
+ }
+ }
+
+ if (current.right < xCount - 1) {
+ if (isColumnEmpty(current.right + 1, current.top, current.bottom, occupied)) {
+ current.right++;
+ findVacantCell(current, xCount, yCount, occupied, cellInfo);
+ current.right--;
+ }
+ }
+
+ if (current.top > 0) {
+ if (isRowEmpty(current.top - 1, current.left, current.right, occupied)) {
+ current.top--;
+ findVacantCell(current, xCount, yCount, occupied, cellInfo);
+ current.top++;
+ }
+ }
+
+ if (current.bottom < yCount - 1) {
+ if (isRowEmpty(current.bottom + 1, current.left, current.right, occupied)) {
+ current.bottom++;
+ findVacantCell(current, xCount, yCount, occupied, cellInfo);
+ current.bottom--;
+ }
+ }
+ }
+
+ private static void addVacantCell(Rect current, CellInfo cellInfo) {
+ CellInfo.VacantCell cell = new CellInfo.VacantCell();
+ cell.cellX = current.left;
+ cell.cellY = current.top;
+ cell.spanX = current.right - current.left + 1;
+ cell.spanY = current.bottom - current.top + 1;
+ if (cell.spanX > cellInfo.maxVacantSpanX) {
+ cellInfo.maxVacantSpanX = cell.spanX;
+ cellInfo.maxVacantSpanXSpanY = cell.spanY;
+ }
+ if (cell.spanY > cellInfo.maxVacantSpanY) {
+ cellInfo.maxVacantSpanY = cell.spanY;
+ cellInfo.maxVacantSpanYSpanX = cell.spanX;
+ }
+ cellInfo.vacantCells.add(cell);
+ }
+
+ private static boolean isColumnEmpty(int x, int top, int bottom, boolean[][] occupied) {
+ for (int y = top; y <= bottom; y++) {
+ if (occupied[x][y]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static boolean isRowEmpty(int y, int left, int right, boolean[][] occupied) {
+ for (int x = left; x <= right; x++) {
+ if (occupied[x][y]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ CellInfo findAllVacantCells(boolean[] occupiedCells) {
+ final boolean portrait = mPortrait;
+ final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
+ final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
+
+ boolean[][] occupied = mOccupied;
+
+ if (occupiedCells != null) {
+ for (int y = 0; y < yCount; y++) {
+ for (int x = 0; x < xCount; x++) {
+ occupied[x][y] = occupiedCells[y * xCount + x];
+ }
+ }
+ } else {
+ findOccupiedCells(xCount, yCount, occupied);
+ }
+
+ CellInfo cellInfo = new CellInfo();
+
+ cellInfo.cellX = -1;
+ cellInfo.cellY = -1;
+ cellInfo.spanY = 0;
+ cellInfo.spanX = 0;
+ cellInfo.maxVacantSpanX = Integer.MIN_VALUE;
+ cellInfo.maxVacantSpanXSpanY = Integer.MIN_VALUE;
+ cellInfo.maxVacantSpanY = Integer.MIN_VALUE;
+ cellInfo.maxVacantSpanYSpanX = Integer.MIN_VALUE;
+ cellInfo.vacantCells = new ArrayList<CellInfo.VacantCell>();
+ cellInfo.screen = mCellInfo.screen;
+
+ Rect current = new Rect();
+
+ for (int x = 0; x < xCount; x++) {
+ for (int y = 0; y < yCount; y++) {
+ if (!occupied[x][y]) {
+ current.set(x, y, x, y);
+ findVacantCell(current, xCount, yCount, occupied, cellInfo);
+ occupied[x][y] = true;
+ }
+ }
+ }
+
+ cellInfo.valid = cellInfo.vacantCells.size() > 0;
+ if (cellInfo.valid) {
+ int[] xy = new int[2];
+ if (cellInfo.findCellForSpan(xy, 1, 1)) {
+ cellInfo.cellX = xy[0];
+ cellInfo.cellY = xy[1];
+ cellInfo.spanY = 1;
+ cellInfo.spanX = 1;
+ }
+ }
+
+ return cellInfo;
+ }
+
+ /**
+ * Given a point, return the cell that strictly encloses that point
+ * @param x X coordinate of the point
+ * @param y Y coordinate of the point
+ * @param result Array of 2 ints to hold the x and y coordinate of the cell
+ */
+ void pointToCellExact(int x, int y, int[] result) {
+ final boolean portrait = mPortrait;
+
+ final int hStartPadding = portrait ? mShortAxisStartPadding : mLongAxisStartPadding;
+ final int vStartPadding = portrait ? mLongAxisStartPadding : mShortAxisStartPadding;
+
+ result[0] = (x - hStartPadding) / (mCellWidth + mWidthGap);
+ result[1] = (y - vStartPadding) / (mCellHeight + mHeightGap);
+
+ final int xAxis = portrait ? mShortAxisCells : mLongAxisCells;
+ final int yAxis = portrait ? mLongAxisCells : mShortAxisCells;
+
+ if (result[0] < 0) result[0] = 0;
+ if (result[0] >= xAxis) result[0] = xAxis - 1;
+ if (result[1] < 0) result[1] = 0;
+ if (result[1] >= yAxis) result[1] = yAxis - 1;
+ }
+
+ /**
+ * Given a point, return the cell that most closely encloses that point
+ * @param x X coordinate of the point
+ * @param y Y coordinate of the point
+ * @param result Array of 2 ints to hold the x and y coordinate of the cell
+ */
+ void pointToCellRounded(int x, int y, int[] result) {
+ pointToCellExact(x + (mCellWidth / 2), y + (mCellHeight / 2), result);
+ }
+
+ /**
+ * Given a cell coordinate, return the point that represents the upper left corner of that cell
+ *
+ * @param cellX X coordinate of the cell
+ * @param cellY Y coordinate of the cell
+ *
+ * @param result Array of 2 ints to hold the x and y coordinate of the point
+ */
+ void cellToPoint(int cellX, int cellY, int[] result) {
+ final boolean portrait = mPortrait;
+
+ final int hStartPadding = portrait ? mShortAxisStartPadding : mLongAxisStartPadding;
+ final int vStartPadding = portrait ? mLongAxisStartPadding : mShortAxisStartPadding;
+
+
+ result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap);
+ result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ // TODO: currently ignoring padding
+
+ int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
+ int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
+
+ int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
+ int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
+
+ if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
+ throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
+ }
+
+ final int shortAxisCells = mShortAxisCells;
+ final int longAxisCells = mLongAxisCells;
+ final int longAxisStartPadding = mLongAxisStartPadding;
+ final int longAxisEndPadding = mLongAxisEndPadding;
+ final int shortAxisStartPadding = mShortAxisStartPadding;
+ final int shortAxisEndPadding = mShortAxisEndPadding;
+ final int cellWidth = mCellWidth;
+ final int cellHeight = mCellHeight;
+
+ mPortrait = heightSpecSize > widthSpecSize;
+
+ int numShortGaps = shortAxisCells - 1;
+ int numLongGaps = longAxisCells - 1;
+
+ if (mPortrait) {
+ int vSpaceLeft = heightSpecSize - longAxisStartPadding - longAxisEndPadding
+ - (cellHeight * longAxisCells);
+ mHeightGap = vSpaceLeft / numLongGaps;
+
+ int hSpaceLeft = widthSpecSize - shortAxisStartPadding - shortAxisEndPadding
+ - (cellWidth * shortAxisCells);
+ if (numShortGaps > 0) {
+ mWidthGap = hSpaceLeft / numShortGaps;
+ } else {
+ mWidthGap = 0;
+ }
+ } else {
+ int hSpaceLeft = widthSpecSize - longAxisStartPadding - longAxisEndPadding
+ - (cellWidth * longAxisCells);
+ mWidthGap = hSpaceLeft / numLongGaps;
+
+ int vSpaceLeft = heightSpecSize - shortAxisStartPadding - shortAxisEndPadding
+ - (cellHeight * shortAxisCells);
+ if (numShortGaps > 0) {
+ mHeightGap = vSpaceLeft / numShortGaps;
+ } else {
+ mHeightGap = 0;
+ }
+ }
+
+ int count = getChildCount();
+
+ for (int i = 0; i < count; i++) {
+ View child = getChildAt(i);
+ LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+ if (mPortrait) {
+ lp.setup(cellWidth, cellHeight, mWidthGap, mHeightGap, shortAxisStartPadding,
+ longAxisStartPadding);
+ } else {
+ lp.setup(cellWidth, cellHeight, mWidthGap, mHeightGap, longAxisStartPadding,
+ shortAxisStartPadding);
+ }
+
+ int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY);
+ int childheightMeasureSpec =
+ MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY);
+ child.measure(childWidthMeasureSpec, childheightMeasureSpec);
+ }
+
+ setMeasuredDimension(widthSpecSize, heightSpecSize);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ int count = getChildCount();
+
+ for (int i = 0; i < count; i++) {
+ View child = getChildAt(i);
+ if (child.getVisibility() != GONE) {
+
+ CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
+
+ int childLeft = lp.x;
+ int childTop = lp.y;
+ child.layout(childLeft, childTop, childLeft + lp.width, childTop + lp.height);
+ }
+ }
+ }
+
+ @Override
+ protected void setChildrenDrawingCacheEnabled(boolean enabled) {
+ final int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View view = getChildAt(i);
+ view.setDrawingCacheEnabled(enabled);
+ // Update the drawing caches
+ view.buildDrawingCache();
+ }
+ }
+
+ @Override
+ protected void setChildrenDrawnWithCacheEnabled(boolean enabled) {
+ super.setChildrenDrawnWithCacheEnabled(enabled);
+ }
+
+ boolean acceptChildDrop(int x, int y, int cellHSpan, int cellVSpan, View cell) {
+ int[] cellXY = mCellXY;
+ pointToCellRounded(x, y, cellXY);
+ int cellX = cellXY[0];
+ int cellY = cellXY[1];
+
+ return findCell(cellX, cellY, cellHSpan, cellVSpan, cell) == null;
+ }
+
+ /**
+ * Finds the first View intersecting with the specified cell. If the cell is outside
+ * of the layout, this is returned.
+ *
+ * @param cellX The X location of the cell to test.
+ * @param cellY The Y location of the cell to test.
+ * @param cellHSpan The horizontal span of the cell to test.
+ * @param cellVSpan The vertical span of the cell to test.
+ * @param ignoreCell View to ignore during the test.
+ *
+ * @return Returns the first View intersecting with the specified cell, this if the cell
+ * lies outside of this layout's grid or null if no View was found.
+ */
+ View findCell(int cellX, int cellY, int cellHSpan, int cellVSpan, View ignoreCell) {
+ if (cellX < 0 || cellX + cellHSpan > (mPortrait ? mShortAxisCells : mLongAxisCells) ||
+ cellY < 0 || cellY + cellVSpan > (mPortrait ? mLongAxisCells : mShortAxisCells)) {
+ return this;
+ }
+
+ final int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View view = getChildAt(i);
+ if (view == ignoreCell) {
+ continue;
+ }
+
+ final LayoutParams lp = (LayoutParams) view.getLayoutParams();
+ if (cellX < lp.cellX + lp.cellHSpan && lp.cellX < cellX + cellHSpan &&
+ cellY < lp.cellY + lp.cellVSpan && lp.cellY < cellY + cellVSpan) {
+ return view;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Drop a child at the specified position
+ *
+ * @param child The child that is being dropped
+ * @param cellX The child's new x location
+ * @param cellY The child's new y location
+ */
+ void onDropChild(View child, int cellX, int cellY) {
+ int[] cellXY = mCellXY;
+ pointToCellRounded(cellX, cellY, cellXY);
+ LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ lp.cellX = cellXY[0];
+ lp.cellY = cellXY[1];
+ lp.isDragging = false;
+ mDragRect.setEmpty();
+ child.requestLayout();
+ invalidate();
+ }
+
+ void onDropAborted(View child) {
+ if (child != null) {
+ ((LayoutParams) child.getLayoutParams()).isDragging = false;
+ invalidate();
+ }
+ mDragRect.setEmpty();
+ }
+
+ /**
+ * Start dragging the specified child
+ *
+ * @param child The child that is being dragged
+ */
+ void onDragChild(View child) {
+ LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ lp.isDragging = true;
+ mDragRect.setEmpty();
+ }
+
+ /**
+ * Drag a child over the specified position
+ *
+ * @param child The child that is being dropped
+ * @param cellX The child's new x cell location
+ * @param cellY The child's new y cell location
+ */
+ void onDragOverChild(View child, int cellX, int cellY) {
+ int[] cellXY = mCellXY;
+ pointToCellRounded(cellX, cellY, cellXY);
+ LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ cellToRect(cellXY[0], cellXY[1], lp.cellHSpan, lp.cellVSpan, mDragRect);
+ invalidate();
+ }
+
+ /**
+ * Computes a bounding rectangle for a range of cells
+ *
+ * @param cellX X coordinate of upper left corner expressed as a cell position
+ * @param cellY Y coordinate of upper left corner expressed as a cell position
+ * @param cellHSpan Width in cells
+ * @param cellVSpan Height in cells
+ * @param dragRect Rectnagle into which to put the results
+ */
+ public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, RectF dragRect) {
+ final boolean portrait = mPortrait;
+ final int cellWidth = mCellWidth;
+ final int cellHeight = mCellHeight;
+ final int widthGap = mWidthGap;
+ final int heightGap = mHeightGap;
+
+ final int hStartPadding = portrait ? mShortAxisStartPadding : mLongAxisStartPadding;
+ final int vStartPadding = portrait ? mLongAxisStartPadding : mShortAxisStartPadding;
+
+ int width = cellHSpan * cellWidth + ((cellHSpan - 1) * widthGap);
+ int height = cellVSpan * cellHeight + ((cellVSpan - 1) * heightGap);
+
+ int x = hStartPadding + cellX * (cellWidth + widthGap);
+ int y = vStartPadding + cellY * (cellHeight + heightGap);
+
+ dragRect.set(x, y, x + width, y + height);
+ }
+
+ /**
+ * Find the first vacant cell, if there is one.
+ *
+ * @param vacant Holds the x and y coordinate of the vacant cell
+ * @param spanX Horizontal cell span.
+ * @param spanY Vertical cell span.
+ *
+ * @return True if a vacant cell was found
+ */
+ public boolean getVacantCell(int[] vacant, int spanX, int spanY) {
+ final boolean portrait = mPortrait;
+ final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
+ final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
+ final boolean[][] occupied = mOccupied;
+
+ findOccupiedCells(xCount, yCount, occupied);
+
+ return findVacantCell(vacant, spanX, spanY, xCount, yCount, occupied);
+ }
+
+ static boolean findVacantCell(int[] vacant, int spanX, int spanY,
+ int xCount, int yCount, boolean[][] occupied) {
+
+ for (int x = 0; x < xCount; x++) {
+ for (int y = 0; y < yCount; y++) {
+ boolean available = !occupied[x][y];
+out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) {
+ for (int j = y; j < y + spanY - 1 && y < yCount; j++) {
+ available = available && !occupied[i][j];
+ if (!available) break out;
+ }
+ }
+
+ if (available) {
+ vacant[0] = x;
+ vacant[1] = y;
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ boolean[] getOccupiedCells() {
+ final boolean portrait = mPortrait;
+ final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
+ final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
+ final boolean[][] occupied = mOccupied;
+
+ findOccupiedCells(xCount, yCount, occupied);
+
+ final boolean[] flat = new boolean[xCount * yCount];
+ for (int y = 0; y < yCount; y++) {
+ for (int x = 0; x < xCount; x++) {
+ flat[y * xCount + x] = occupied[x][y];
+ }
+ }
+
+ return flat;
+ }
+
+ private void findOccupiedCells(int xCount, int yCount, boolean[][] occupied) {
+ for (int x = 0; x < xCount; x++) {
+ for (int y = 0; y < yCount; y++) {
+ occupied[x][y] = false;
+ }
+ }
+
+ int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ View child = getChildAt(i);
+ if (child instanceof Folder) {
+ continue;
+ }
+ LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+ for (int x = lp.cellX; x < lp.cellX + lp.cellHSpan && x < xCount; x++) {
+ for (int y = lp.cellY; y < lp.cellY + lp.cellVSpan && y < yCount; y++) {
+ occupied[x][y] = true;
+ }
+ }
+ }
+ }
+
+ @Override
+ public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
+ return new CellLayout.LayoutParams(getContext(), attrs);
+ }
+
+ @Override
+ protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
+ return p instanceof CellLayout.LayoutParams;
+ }
+
+ @Override
+ protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
+ return new CellLayout.LayoutParams(p);
+ }
+
+ public static class LayoutParams extends ViewGroup.MarginLayoutParams {
+ /**
+ * Horizontal location of the item in the grid.
+ */
+ @ViewDebug.ExportedProperty
+ public int cellX;
+
+ /**
+ * Vertical location of the item in the grid.
+ */
+ @ViewDebug.ExportedProperty
+ public int cellY;
+
+ /**
+ * Number of cells spanned horizontally by the item.
+ */
+ @ViewDebug.ExportedProperty
+ public int cellHSpan;
+
+ /**
+ * Number of cells spanned vertically by the item.
+ */
+ @ViewDebug.ExportedProperty
+ public int cellVSpan;
+
+ /**
+ * Is this item currently being dragged
+ */
+ public boolean isDragging;
+
+ // X coordinate of the view in the layout.
+ @ViewDebug.ExportedProperty
+ int x;
+ // Y coordinate of the view in the layout.
+ @ViewDebug.ExportedProperty
+ int y;
+
+ public LayoutParams(Context c, AttributeSet attrs) {
+ super(c, attrs);
+ cellHSpan = 1;
+ cellVSpan = 1;
+ }
+
+ public LayoutParams(ViewGroup.LayoutParams source) {
+ super(source);
+ cellHSpan = 1;
+ cellVSpan = 1;
+ }
+
+ public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
+ super(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
+ this.cellX = cellX;
+ this.cellY = cellY;
+ this.cellHSpan = cellHSpan;
+ this.cellVSpan = cellVSpan;
+ }
+
+ public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap,
+ int hStartPadding, int vStartPadding) {
+
+ final int myCellHSpan = cellHSpan;
+ final int myCellVSpan = cellVSpan;
+ final int myCellX = cellX;
+ final int myCellY = cellY;
+
+ width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) -
+ leftMargin - rightMargin;
+ height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) -
+ topMargin - bottomMargin;
+
+ x = hStartPadding + myCellX * (cellWidth + widthGap) + leftMargin;
+ y = vStartPadding + myCellY * (cellHeight + heightGap) + topMargin;
+ }
+ }
+
+ static final class CellInfo implements ContextMenu.ContextMenuInfo {
+ static final class VacantCell {
+ int cellX;
+ int cellY;
+ int spanX;
+ int spanY;
+
+ @Override
+ public String toString() {
+ return "VacantCell[x=" + cellX + ", y=" + cellY + ", spanX=" + spanX +
+ ", spanY=" + spanY + "]";
+ }
+ }
+
+ View cell;
+ int cellX;
+ int cellY;
+ int spanX;
+ int spanY;
+ int screen;
+ boolean valid;
+
+ ArrayList<VacantCell> vacantCells;
+ int maxVacantSpanX;
+ int maxVacantSpanXSpanY;
+ int maxVacantSpanY;
+ int maxVacantSpanYSpanX;
+
+ void findVacantCellsFromOccupied(boolean[] occupied, int xCount, int yCount) {
+ if (cellX < 0 || cellY < 0) {
+ maxVacantSpanX = maxVacantSpanXSpanY = Integer.MIN_VALUE;
+ maxVacantSpanY = maxVacantSpanYSpanX = Integer.MIN_VALUE;
+ vacantCells = new ArrayList<VacantCell>();
+ return;
+ }
+
+ final boolean[][] unflattened = new boolean[xCount][yCount];
+ for (int y = 0; y < yCount; y++) {
+ for (int x = 0; x < xCount; x++) {
+ unflattened[x][y] = occupied[y * xCount + x];
+ }
+ }
+ CellLayout.findIntersectingVacantCells(this, cellX, cellY, xCount, yCount, unflattened);
+ }
+
+ boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
+ if (vacantCells == null) {
+ return false;
+ }
+
+ if (this.spanX >= spanX && this.spanY >= spanY) {
+ cellXY[0] = cellX;
+ cellXY[1] = cellY;
+ return true;
+ }
+
+ final ArrayList<VacantCell> list = vacantCells;
+ final int count = list.size();
+ // Look for an exact match first
+ for (int i = 0; i < count; i++) {
+ VacantCell cell = list.get(i);
+ if (cell.spanX == spanX && cell.spanY == spanY) {
+ cellXY[0] = cell.cellX;
+ cellXY[1] = cell.cellY;
+ return true;
+ }
+ }
+
+ // Look for the first cell large enough
+ for (int i = 0; i < count; i++) {
+ VacantCell cell = list.get(i);
+ if (cell.spanX >= spanX && cell.spanY >= spanY) {
+ cellXY[0] = cell.cellX;
+ cellXY[1] = cell.cellY;
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return "Cell[view=" + (cell == null ? "null" : cell.getClass()) + ", x=" + cellX +
+ ", y=" + cellY + "]";
+ }
+ }
+}
+
+