diff options
Diffstat (limited to 'src/com/android/launcher/CellLayout.java')
-rw-r--r-- | src/com/android/launcher/CellLayout.java | 1010 |
1 files changed, 1010 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..ff8bff420 --- /dev/null +++ b/src/com/android/launcher/CellLayout.java @@ -0,0 +1,1010 @@ +/* + * 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(); + + private boolean mDirtyTag; + + 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 addView(View child, int index, ViewGroup.LayoutParams params) { + // Generate an id for each view, this assumes we have at most 256x256 cells + // per workspace screen + final LayoutParams cellParams = (LayoutParams) params; + child.setId(((getId() & 0xFF) << 16) | + (cellParams.cellX & 0xFF) << 8 | (cellParams.cellY & 0xFF)); + + super.addView(child, index, params); + } + + @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; + mDirtyTag = false; + 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]]; + + // Instead of finding the interesting vacant cells here, wait until a + // caller invokes getTag() to retrieve the result. Finding the vacant + // cells is a bit expensive and can generate many new objects, it's + // therefore better to defer it until we know we actually need it. + + mDirtyTag = true; + } + 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; + mDirtyTag = false; + setTag(cellInfo); + } + + return false; + } + + @Override + public CellInfo getTag() { + final CellInfo info = (CellInfo) super.getTag(); + if (mDirtyTag && info.valid) { + final boolean portrait = mPortrait; + final int xCount = portrait ? mShortAxisCells : mLongAxisCells; + final int yCount = portrait ? mLongAxisCells : mShortAxisCells; + + final boolean[][] occupied = mOccupied; + findOccupiedCells(xCount, yCount, occupied); + + findIntersectingVacantCells(info, info.cellX, info.cellY, xCount, yCount, occupied); + + mDirtyTag = false; + } + return info; + } + + 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.clearVacantCells(); + + if (occupied[x][y]) { + return; + } + + cellInfo.current.set(x, y, x, y); + + findVacantCell(cellInfo.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 = CellInfo.VacantCell.acquire(); + 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.screen = mCellInfo.screen; + + Rect current = cellInfo.current; + + 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; + + // Assume the caller will perform their own cell searching, otherwise we + // risk causing an unnecessary rebuild after findCellForSpan() + + 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); + } + + /** + * Computes the required horizontal and vertical cell spans to always + * fit the given rectangle. + * + * @param width Width in pixels + * @param height Height in pixels + * @param Horizontal and vertical spans required + */ + public int[] rectToCell(int width, int height) { + // Always assume we're working with the smallest span to make sure we + // reserve enough space in both orientations. + int actualWidth = mCellWidth + mWidthGap; + int actualHeight = mCellHeight + mHeightGap; + int smallerSize = Math.min(actualWidth, actualHeight); + + // Always round up to next largest cell + int spanX = (width + smallerSize) / smallerSize; + int spanY = (height + smallerSize) / smallerSize; + return new int[] { spanX, spanY }; + } + + /** + * 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 { + /** + * See View.AttachInfo.InvalidateInfo for futher explanations about + * the recycling mechanism. In this case, we recycle the vacant cells + * instances because up to several hundreds can be instanciated when + * the user long presses an empty cell. + */ + static final class VacantCell { + int cellX; + int cellY; + int spanX; + int spanY; + + // We can create up to 523 vacant cells on a 4x4 grid, 100 seems + // like a reasonable compromise given the size of a VacantCell and + // the fact that the user is not likely to touch an empty 4x4 grid + // very often + private static final int POOL_LIMIT = 100; + private static final Object sLock = new Object(); + + private static int sAcquiredCount = 0; + private static VacantCell sRoot; + + private VacantCell next; + + static VacantCell acquire() { + synchronized (sLock) { + if (sRoot == null) { + return new VacantCell(); + } + + VacantCell info = sRoot; + sRoot = info.next; + sAcquiredCount--; + + return info; + } + } + + void release() { + synchronized (sLock) { + if (sAcquiredCount < POOL_LIMIT) { + sAcquiredCount++; + next = sRoot; + sRoot = this; + } + } + } + + @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; + + final ArrayList<VacantCell> vacantCells = new ArrayList<VacantCell>(VacantCell.POOL_LIMIT); + int maxVacantSpanX; + int maxVacantSpanXSpanY; + int maxVacantSpanY; + int maxVacantSpanYSpanX; + final Rect current = new Rect(); + + private void clearVacantCells() { + final ArrayList<VacantCell> list = vacantCells; + final int count = list.size(); + + for (int i = 0; i < count; i++) list.get(i).release(); + + list.clear(); + } + + void findVacantCellsFromOccupied(boolean[] occupied, int xCount, int yCount) { + if (cellX < 0 || cellY < 0) { + maxVacantSpanX = maxVacantSpanXSpanY = Integer.MIN_VALUE; + maxVacantSpanY = maxVacantSpanYSpanX = Integer.MIN_VALUE; + clearVacantCells(); + 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); + } + + /** + * This method can be called only once! Calling #findVacantCellsFromOccupied will + * restore the ability to call this method. + * + * Finds the upper-left coordinate of the first rectangle in the grid that can + * hold a cell of the specified dimensions. + * + * @param cellXY The array that will contain the position of a vacant cell if such a cell + * can be found. + * @param spanX The horizontal span of the cell we want to find. + * @param spanY The vertical span of the cell we want to find. + * + * @return True if a vacant cell of the specified dimension was found, false otherwise. + */ + boolean findCellForSpan(int[] cellXY, int spanX, int spanY) { + final ArrayList<VacantCell> list = vacantCells; + final int count = list.size(); + + boolean found = false; + + if (this.spanX >= spanX && this.spanY >= spanY) { + cellXY[0] = cellX; + cellXY[1] = cellY; + found = true; + } + + // 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; + found = true; + break; + } + } + + // 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; + found = true; + break; + } + } + + clearVacantCells(); + + return found; + } + + @Override + public String toString() { + return "Cell[view=" + (cell == null ? "null" : cell.getClass()) + ", x=" + cellX + + ", y=" + cellY + "]"; + } + } +} + + |