diff options
Diffstat (limited to 'src/com/android/launcher2/CellLayout.java')
-rw-r--r-- | src/com/android/launcher2/CellLayout.java | 648 |
1 files changed, 429 insertions, 219 deletions
diff --git a/src/com/android/launcher2/CellLayout.java b/src/com/android/launcher2/CellLayout.java index 9d39c2ca7..7ca549e84 100644 --- a/src/com/android/launcher2/CellLayout.java +++ b/src/com/android/launcher2/CellLayout.java @@ -16,36 +16,46 @@ package com.android.launcher2; +import com.android.launcher.R; + +import android.app.WallpaperManager; import android.content.Context; -import android.content.res.TypedArray; import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Canvas; import android.graphics.Rect; import android.graphics.RectF; -import android.graphics.Canvas; +import android.graphics.drawable.Drawable; 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 android.app.WallpaperManager; +import android.view.animation.Animation; +import android.view.animation.LayoutAnimationController; import java.util.ArrayList; - -import com.android.launcher.R; +import java.util.Arrays; public class CellLayout extends ViewGroup { + static final String TAG = "CellLayout"; + private boolean mPortrait; private int mCellWidth; private int mCellHeight; - + private int mLongAxisStartPadding; private int mLongAxisEndPadding; - private int mShortAxisStartPadding; private int mShortAxisEndPadding; + private int mLeftPadding; + private int mRightPadding; + private int mTopPadding; + private int mBottomPadding; + private int mShortAxisCells; private int mLongAxisCells; @@ -53,17 +63,33 @@ public class CellLayout extends ViewGroup { private int mHeightGap; private final Rect mRect = new Rect(); + private final RectF mRectF = new RectF(); private final CellInfo mCellInfo = new CellInfo(); - - int[] mCellXY = new int[2]; + + // This is a temporary variable to prevent having to allocate a new object just to + // return an (x, y) value from helper functions. Do NOT use it to maintain other state. + private final int[] mTmpCellXY = new int[2]; + boolean[][] mOccupied; - private RectF mDragRect = new RectF(); + private final RectF mDragRect = new RectF(); + + // When dragging, used to indicate a vacant drop location + private Drawable mVacantDrawable; + + // When dragging, used to indicate an occupied drop location + private Drawable mOccupiedDrawable; + + // Updated to point to mVacantDrawable or mOccupiedDrawable, as appropriate + private Drawable mDragRectDrawable; + + // When a drag operation is in progress, holds the nearest cell to the touch point + private final int[] mDragCell = new int[2]; private boolean mDirtyTag; private boolean mLastDownOnOccupiedCell = false; - - private final WallpaperManager mWallpaperManager; + + private final WallpaperManager mWallpaperManager; public CellLayout(Context context) { this(context, null); @@ -75,20 +101,27 @@ public class CellLayout extends ViewGroup { public CellLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); + + // A ViewGroup usually does not draw, but CellLayout needs to draw a rectangle to show + // the user where a dragged item will land when dropped. + setWillNotDraw(false); + mVacantDrawable = getResources().getDrawable(R.drawable.rounded_rect_green); + mOccupiedDrawable = getResources().getDrawable(R.drawable.rounded_rect_red); + 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 = + + mLongAxisStartPadding = a.getDimensionPixelSize(R.styleable.CellLayout_longAxisStartPadding, 10); - mLongAxisEndPadding = + mLongAxisEndPadding = a.getDimensionPixelSize(R.styleable.CellLayout_longAxisEndPadding, 10); mShortAxisStartPadding = a.getDimensionPixelSize(R.styleable.CellLayout_shortAxisStartPadding, 10); - mShortAxisEndPadding = + mShortAxisEndPadding = a.getDimensionPixelSize(R.styleable.CellLayout_shortAxisEndPadding, 10); - + mShortAxisCells = a.getInt(R.styleable.CellLayout_shortAxisCells, 4); mLongAxisCells = a.getInt(R.styleable.CellLayout_longAxisCells, 4); @@ -96,14 +129,6 @@ public class CellLayout extends ViewGroup { setAlwaysDrawnWithCacheEnabled(false); - if (mOccupied == null) { - if (mPortrait) { - mOccupied = new boolean[mShortAxisCells][mLongAxisCells]; - } else { - mOccupied = new boolean[mLongAxisCells][mShortAxisCells]; - } - } - mWallpaperManager = WallpaperManager.getInstance(getContext()); } @@ -113,6 +138,18 @@ public class CellLayout extends ViewGroup { } @Override + protected void onDraw(Canvas canvas) { + if (!mDragRect.isEmpty()) { + mDragRectDrawable.setBounds( + (int)mDragRect.left, + (int)mDragRect.top, + (int)mDragRect.right, + (int)mDragRect.bottom); + mDragRectDrawable.draw(canvas); + } + } + + @Override public void cancelLongPress() { super.cancelLongPress(); @@ -132,14 +169,24 @@ public class CellLayout extends ViewGroup { return mPortrait ? mLongAxisCells : mShortAxisCells; } - @Override - public void addView(View child, int index, ViewGroup.LayoutParams params) { + // Takes canonical layout parameters + public boolean addViewToCellLayout(View child, int index, int childId, LayoutParams params) { + final LayoutParams lp = params; + // Generate an id for each view, this assumes we have at most 256x256 cells // per workspace screen - final LayoutParams cellParams = (LayoutParams) params; - cellParams.regenerateId = true; + if (lp.cellX >= 0 && lp.cellX <= getCountX() - 1 && lp.cellY >= 0 && lp.cellY <= getCountY() - 1) { + // If the horizontal or vertical span is set to -1, it is taken to + // mean that it spans the extent of the CellLayout + if (lp.cellHSpan < 0) lp.cellHSpan = getCountX(); + if (lp.cellVSpan < 0) lp.cellVSpan = getCountY(); - super.addView(child, index, params); + child.setId(childId); + + addView(child, index, lp); + return true; + } + return false; } @Override @@ -158,67 +205,73 @@ public class CellLayout extends ViewGroup { mCellInfo.screen = ((ViewGroup) getParent()).indexOfChild(this); } + public void setTagToCellInfoForPoint(int touchX, int touchY) { + final CellInfo cellInfo = mCellInfo; + final Rect frame = mRect; + final int x = touchX + mScrollX; + final int y = touchY + 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; + } + } + } + + mLastDownOnOccupiedCell = found; + + if (!found) { + final int cellXY[] = mTmpCellXY; + 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, null, true); + + 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); + } + + @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; - } - } - } - - mLastDownOnOccupiedCell = found; - - 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, null); - - 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); + setTagToCellInfoForPoint((int) ev.getX(), (int) ev.getY()); } else if (action == MotionEvent.ACTION_UP) { cellInfo.cell = null; cellInfo.cellX = -1; @@ -242,7 +295,7 @@ public class CellLayout extends ViewGroup { final int yCount = portrait ? mLongAxisCells : mShortAxisCells; final boolean[][] occupied = mOccupied; - findOccupiedCells(xCount, yCount, occupied, null); + findOccupiedCells(xCount, yCount, occupied, null, true); findIntersectingVacantCells(info, info.cellX, info.cellY, xCount, yCount, occupied); @@ -251,8 +304,8 @@ public class CellLayout extends ViewGroup { return info; } - private static void findIntersectingVacantCells(CellInfo cellInfo, int x, int y, - int xCount, int yCount, boolean[][] occupied) { + 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; @@ -324,6 +377,9 @@ public class CellLayout extends ViewGroup { cellInfo.vacantCells.add(cell); } + /** + * Check if the column 'x' is empty from rows 'top' to 'bottom', inclusive. + */ private static boolean isColumnEmpty(int x, int top, int bottom, boolean[][] occupied) { for (int y = top; y <= bottom; y++) { if (occupied[x][y]) { @@ -333,6 +389,9 @@ public class CellLayout extends ViewGroup { return true; } + /** + * Check if the row 'y' is empty from columns 'left' to 'right', inclusive. + */ private static boolean isRowEmpty(int y, int left, int right, boolean[][] occupied) { for (int x = left; x <= right; x++) { if (occupied[x][y]) { @@ -356,7 +415,7 @@ public class CellLayout extends ViewGroup { } } } else { - findOccupiedCells(xCount, yCount, occupied, ignoreView); + findOccupiedCells(xCount, yCount, occupied, ignoreView, true); } CellInfo cellInfo = new CellInfo(); @@ -387,21 +446,21 @@ public class CellLayout extends ViewGroup { // 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 + * 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; + + final int hStartPadding = getLeftPadding(); + final int vStartPadding = getTopPadding(); result[0] = (x - hStartPadding) / (mCellWidth + mWidthGap); result[1] = (y - vStartPadding) / (mCellHeight + mHeightGap); @@ -414,7 +473,7 @@ public class CellLayout extends ViewGroup { 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 @@ -427,18 +486,15 @@ public class CellLayout extends ViewGroup { /** * Given a cell coordinate, return the point that represents the upper left corner of that cell - * - * @param cellX X coordinate of the 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; - + final int hStartPadding = getLeftPadding(); + final int vStartPadding = getTopPadding(); result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap); result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap); @@ -453,97 +509,117 @@ public class CellLayout extends ViewGroup { } int getLeftPadding() { - return mPortrait ? mShortAxisStartPadding : mLongAxisStartPadding; + return mLeftPadding; } int getTopPadding() { - return mPortrait ? mLongAxisStartPadding : mShortAxisStartPadding; + return mTopPadding; } int getRightPadding() { - return mPortrait ? mShortAxisEndPadding : mLongAxisEndPadding; + return mRightPadding; } int getBottomPadding() { - return mPortrait ? mLongAxisEndPadding : mShortAxisEndPadding; + return mBottomPadding; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // TODO: currently ignoring padding - + int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); - int widthSpecSize = MeasureSpec.getSize(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; + boolean portrait = heightSpecSize > widthSpecSize; + if (portrait != mPortrait || mOccupied == null) { + if (portrait) { + mOccupied = new boolean[mShortAxisCells][mLongAxisCells]; + } else { + mOccupied = new boolean[mLongAxisCells][mShortAxisCells]; + } + } + mPortrait = portrait; int numShortGaps = shortAxisCells - 1; int numLongGaps = longAxisCells - 1; if (mPortrait) { - int vSpaceLeft = heightSpecSize - longAxisStartPadding - longAxisEndPadding - - (cellHeight * longAxisCells); + int vSpaceLeft = heightSpecSize - mLongAxisStartPadding + - mLongAxisEndPadding - (cellHeight * longAxisCells); mHeightGap = vSpaceLeft / numLongGaps; - int hSpaceLeft = widthSpecSize - shortAxisStartPadding - shortAxisEndPadding - - (cellWidth * shortAxisCells); + int hSpaceLeft = widthSpecSize - mShortAxisStartPadding + - mShortAxisEndPadding - (cellWidth * shortAxisCells); if (numShortGaps > 0) { mWidthGap = hSpaceLeft / numShortGaps; } else { mWidthGap = 0; } + + if (LauncherApplication.isInPlaceRotationEnabled()) { + mWidthGap = mHeightGap = Math.min(mHeightGap, mWidthGap); + mLeftPadding = mRightPadding = (widthSpecSize - cellWidth + * shortAxisCells - (shortAxisCells - 1) * mWidthGap) / 2; + mTopPadding = mBottomPadding = (heightSpecSize - cellHeight + * longAxisCells - (longAxisCells - 1) * mHeightGap) / 2; + } else { + mLeftPadding = mShortAxisStartPadding; + mRightPadding = mShortAxisEndPadding; + mTopPadding = mLongAxisStartPadding; + mBottomPadding = mLongAxisEndPadding; + } } else { - int hSpaceLeft = widthSpecSize - longAxisStartPadding - longAxisEndPadding - - (cellWidth * longAxisCells); + int hSpaceLeft = widthSpecSize - mLongAxisStartPadding + - mLongAxisEndPadding - (cellWidth * longAxisCells); mWidthGap = hSpaceLeft / numLongGaps; - int vSpaceLeft = heightSpecSize - shortAxisStartPadding - shortAxisEndPadding - - (cellHeight * shortAxisCells); + int vSpaceLeft = heightSpecSize - mShortAxisStartPadding + - mShortAxisEndPadding - (cellHeight * shortAxisCells); if (numShortGaps > 0) { mHeightGap = vSpaceLeft / numShortGaps; } else { mHeightGap = 0; } + + if (LauncherApplication.isScreenXLarge()) { + mWidthGap = mHeightGap = Math.min(mHeightGap, mWidthGap); + mLeftPadding = mRightPadding = (widthSpecSize - cellWidth + * longAxisCells - (longAxisCells - 1) * mWidthGap) / 2 ; + mTopPadding = mBottomPadding = (heightSpecSize - cellHeight + * shortAxisCells - (shortAxisCells - 1) * mHeightGap) / 2; + } else { + mLeftPadding = mLongAxisStartPadding; + mRightPadding = mLongAxisEndPadding; + mTopPadding = mShortAxisStartPadding; + mBottomPadding = mShortAxisEndPadding; + } } - int count = getChildCount(); for (int i = 0; i < count; i++) { View child = getChildAt(i); LayoutParams lp = (LayoutParams) child.getLayoutParams(); + lp.setup(cellWidth, cellHeight, mWidthGap, mHeightGap, + mLeftPadding, mTopPadding); - if (mPortrait) { - lp.setup(cellWidth, cellHeight, mWidthGap, mHeightGap, shortAxisStartPadding, - longAxisStartPadding); - } else { - lp.setup(cellWidth, cellHeight, mWidthGap, mHeightGap, longAxisStartPadding, - shortAxisStartPadding); - } - - if (lp.regenerateId) { - child.setId(((getId() & 0xFF) << 16) | (lp.cellX & 0xFF) << 8 | (lp.cellY & 0xFF)); - lp.regenerateId = false; - } + int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.width, + MeasureSpec.EXACTLY); + int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(lp.height, + MeasureSpec.EXACTLY); - int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY); - int childheightMeasureSpec = - MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY); child.measure(childWidthMeasureSpec, childheightMeasureSpec); } @@ -567,7 +643,7 @@ public class CellLayout extends ViewGroup { if (lp.dropped) { lp.dropped = false; - final int[] cellXY = mCellXY; + final int[] cellXY = mTmpCellXY; getLocationOnScreen(cellXY); mWallpaperManager.sendWallpaperCommand(getWindowToken(), "android.home.drop", cellXY[0] + childLeft + lp.width / 2, @@ -593,10 +669,110 @@ public class CellLayout extends ViewGroup { super.setChildrenDrawnWithCacheEnabled(enabled); } + private boolean isVacant(int originX, int originY, int spanX, int spanY) { + for (int i = 0; i < spanY; i++) { + if (!isRowEmpty(originY + i, originX, originX + spanX - 1, mOccupied)) { + return false; + } + } + return true; + } + + public View getChildAt(int x, int y) { + final int count = getChildCount(); + for (int i = 0; i < count; i++) { + View child = getChildAt(i); + LayoutParams lp = (LayoutParams) child.getLayoutParams(); + + if ((lp.cellX <= x) && (x < lp.cellX + lp.cellHSpan) && + (lp.cellY <= y) && (y < lp.cellY + lp.cellHSpan)) { + return child; + } + } + return null; + } + + /** + * Estimate the size that a child with the given dimensions will take in the layout. + */ + void estimateChildSize(int minWidth, int minHeight, int[] result) { + // Assuming it's placed at 0, 0, find where the bottom right cell will land + rectToCell(minWidth, minHeight, result); + + // Then figure out the rect it will occupy + cellToRect(0, 0, result[0], result[1], mRectF); + result[0] = (int)mRectF.width(); + result[1] = (int)mRectF.height(); + } + + /** + * Estimate where the top left cell of the dragged item will land if it is dropped. + * + * @param originX The X value of the top left corner of the item + * @param originY The Y value of the top left corner of the item + * @param spanX The number of horizontal cells that the item spans + * @param spanY The number of vertical cells that the item spans + * @param result The estimated drop cell X and Y. + */ + void estimateDropCell(int originX, int originY, int spanX, int spanY, int[] result) { + final int countX = getCountX(); + final int countY = getCountY(); + + pointToCellRounded(originX, originY, result); + + // If the item isn't fully on this screen, snap to the edges + int rightOverhang = result[0] + spanX - countX; + if (rightOverhang > 0) { + result[0] -= rightOverhang; // Snap to right + } + result[0] = Math.max(0, result[0]); // Snap to left + int bottomOverhang = result[1] + spanY - countY; + if (bottomOverhang > 0) { + result[1] -= bottomOverhang; // Snap to bottom + } + result[1] = Math.max(0, result[1]); // Snap to top + } + + void visualizeDropLocation(View view, int originX, int originY, int spanX, int spanY) { + final int[] originCell = mDragCell; + final int[] cellXY = mTmpCellXY; + estimateDropCell(originX, originY, spanX, spanY, cellXY); + + // Only recalculate the bounding rect when necessary + if (!Arrays.equals(cellXY, originCell)) { + originCell[0] = cellXY[0]; + originCell[1] = cellXY[1]; + + // Find the top left corner of the rect the object will occupy + final int[] topLeft = mTmpCellXY; + cellToPoint(originCell[0], originCell[1], topLeft); + final int left = topLeft[0]; + final int top = topLeft[1]; + + // Now find the bottom right + final int[] bottomRight = mTmpCellXY; + cellToPoint(originCell[0] + spanX - 1, originCell[1] + spanY - 1, bottomRight); + bottomRight[0] += mCellWidth; + bottomRight[1] += mCellHeight; + + final int countX = mPortrait ? mShortAxisCells : mLongAxisCells; + final int countY = mPortrait ? mLongAxisCells : mShortAxisCells; + // TODO: It's not necessary to do this every time, but it's not especially expensive + findOccupiedCells(countX, countY, mOccupied, view, false); + + boolean vacant = isVacant(originCell[0], originCell[1], spanX, spanY); + mDragRectDrawable = vacant ? mVacantDrawable : mOccupiedDrawable; + + // mDragRect will be rendered in onDraw() + mDragRect.set(left, top, bottomRight[0], bottomRight[1]); + invalidate(); + } + } + /** * Find a vacant area that will fit the given bounds nearest the requested * cell location. Uses Euclidean distance to score multiple vacant areas. - * + * * @param pixelX The X location at which you want to search for a vacant area. * @param pixelY The Y location at which you want to search for a vacant area. * @param spanX Horizontal span of the object. @@ -608,12 +784,11 @@ public class CellLayout extends ViewGroup { */ int[] findNearestVacantArea(int pixelX, int pixelY, int spanX, int spanY, CellInfo vacantCells, int[] recycle) { - + // Keep track of best-scoring drop area final int[] bestXY = recycle != null ? recycle : new int[2]; - final int[] cellXY = mCellXY; double bestDistance = Double.MAX_VALUE; - + // Bail early if vacant cells aren't valid if (!vacantCells.valid) { return null; @@ -623,17 +798,18 @@ public class CellLayout extends ViewGroup { final int size = vacantCells.vacantCells.size(); for (int i = 0; i < size; i++) { final CellInfo.VacantCell cell = vacantCells.vacantCells.get(i); - + // Reject if vacant cell isn't our exact size if (cell.spanX != spanX || cell.spanY != spanY) { continue; } - - // Score is center distance from requested pixel + + // Score is distance from requested pixel to the top left of each cell + final int[] cellXY = mTmpCellXY; cellToPoint(cell.cellX, cell.cellY, cellXY); - - double distance = Math.sqrt(Math.pow(cellXY[0] - pixelX, 2) + - Math.pow(cellXY[1] - pixelY, 2)); + + double distance = Math.sqrt(Math.pow(cellXY[0] - pixelX, 2) + + Math.pow(cellXY[1] - pixelY, 2)); if (distance <= bestDistance) { bestDistance = distance; bestXY[0] = cell.cellX; @@ -641,44 +817,52 @@ public class CellLayout extends ViewGroup { } } - // Return null if no suitable location found + // Return null if no suitable location found if (bestDistance < Double.MAX_VALUE) { return bestXY; } else { return null; } } - + /** - * Drop a child at the specified position + * Called when a drag and drop operation has finished (successfully or not). + */ + void onDragComplete() { + // Invalidate the drag data + mDragCell[0] = -1; + mDragCell[1] = -1; + + mDragRect.setEmpty(); + invalidate(); + } + + /** + * Mark a child as having been dropped. * * @param child The child that is being dropped - * @param targetXY Destination area to move to */ - void onDropChild(View child, int[] targetXY) { + void onDropChild(View child) { if (child != null) { LayoutParams lp = (LayoutParams) child.getLayoutParams(); - lp.cellX = targetXY[0]; - lp.cellY = targetXY[1]; lp.isDragging = false; lp.dropped = true; mDragRect.setEmpty(); child.requestLayout(); - invalidate(); } + onDragComplete(); } void onDropAborted(View child) { if (child != null) { ((LayoutParams) child.getLayoutParams()).isDragging = false; - invalidate(); } - mDragRect.setEmpty(); + onDragComplete(); } /** * Start dragging the specified child - * + * * @param child The child that is being dragged */ void onDragChild(View child) { @@ -686,58 +870,44 @@ public class CellLayout extends ViewGroup { 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 cellHSpan Width in cells * @param cellVSpan Height in cells - * @param dragRect Rectnagle into which to put the results + * @param resultRect Rect into which to put the results */ - public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, RectF dragRect) { + public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, RectF resultRect) { 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; - + + final int hStartPadding = getLeftPadding(); + final int vStartPadding = getTopPadding(); + 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); + + resultRect.set(x, y, x + width, y + height); } - + /** - * Computes the required horizontal and vertical cell spans to always + * 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 result An array of length 2 in which to store the result (may be null). */ - public int[] rectToCell(int width, int height) { + public int[] rectToCell(int width, int height, int[] result) { // Always assume we're working with the smallest span to make sure we // reserve enough space in both orientations. final Resources resources = getResources(); @@ -749,7 +919,12 @@ public class CellLayout extends ViewGroup { int spanX = (width + smallerSize) / smallerSize; int spanY = (height + smallerSize) / smallerSize; - return new int[] { spanX, spanY }; + if (result == null) { + return new int[] { spanX, spanY }; + } + result[0] = spanX; + result[1] = spanY; + return result; } /** @@ -758,7 +933,7 @@ public class CellLayout extends ViewGroup { * @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) { @@ -767,7 +942,7 @@ public class CellLayout extends ViewGroup { final int yCount = portrait ? mLongAxisCells : mShortAxisCells; final boolean[][] occupied = mOccupied; - findOccupiedCells(xCount, yCount, occupied, null); + findOccupiedCells(xCount, yCount, occupied, null, true); return findVacantCell(vacant, spanX, spanY, xCount, yCount, occupied); } @@ -796,13 +971,16 @@ out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) { return false; } - boolean[] getOccupiedCells() { + /** + * Update the array of occupied cells (mOccupied), and return a flattened copy of the array. + */ + boolean[] getOccupiedCellsFlattened() { final boolean portrait = mPortrait; final int xCount = portrait ? mShortAxisCells : mLongAxisCells; final int yCount = portrait ? mLongAxisCells : mShortAxisCells; final boolean[][] occupied = mOccupied; - findOccupiedCells(xCount, yCount, occupied, null); + findOccupiedCells(xCount, yCount, occupied, null, true); final boolean[] flat = new boolean[xCount * yCount]; for (int y = 0; y < yCount; y++) { @@ -814,7 +992,14 @@ out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) { return flat; } - private void findOccupiedCells(int xCount, int yCount, boolean[][] occupied, View ignoreView) { + /** + * Update the array of occupied cells. + * @param ignoreView If non-null, the space occupied by this View is treated as vacant + * @param ignoreFolders If true, a cell occupied by a Folder is treated as vacant + */ + private void findOccupiedCells( + int xCount, int yCount, boolean[][] occupied, View ignoreView, boolean ignoreFolders) { + for (int x = 0; x < xCount; x++) { for (int y = 0; y < yCount; y++) { occupied[x][y] = false; @@ -824,7 +1009,7 @@ out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) { int count = getChildCount(); for (int i = 0; i < count; i++) { View child = getChildAt(i); - if (child instanceof Folder || child.equals(ignoreView)) { + if ((ignoreFolders && child instanceof Folder) || child.equals(ignoreView)) { continue; } LayoutParams lp = (LayoutParams) child.getLayoutParams(); @@ -852,6 +1037,17 @@ out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) { return new CellLayout.LayoutParams(p); } + public static class CellLayoutAnimationController extends LayoutAnimationController { + public CellLayoutAnimationController(Animation animation, float delay) { + super(animation, delay); + } + + @Override + protected long getDelayForView(View view) { + return (int) (Math.random() * 150); + } + } + public static class LayoutParams extends ViewGroup.MarginLayoutParams { /** * Horizontal location of the item in the grid. @@ -876,7 +1072,7 @@ out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) { */ @ViewDebug.ExportedProperty public int cellVSpan; - + /** * Is this item currently being dragged */ @@ -889,8 +1085,6 @@ out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) { @ViewDebug.ExportedProperty int y; - boolean regenerateId; - boolean dropped; public LayoutParams(Context c, AttributeSet attrs) { @@ -904,7 +1098,15 @@ out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) { cellHSpan = 1; cellVSpan = 1; } - + + public LayoutParams(LayoutParams source) { + super(source); + this.cellX = source.cellX; + this.cellY = source.cellY; + this.cellHSpan = source.cellHSpan; + this.cellVSpan = source.cellVSpan; + } + public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) { super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); this.cellX = cellX; @@ -915,12 +1117,12 @@ out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) { 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) - @@ -929,14 +1131,18 @@ out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) { x = hStartPadding + myCellX * (cellWidth + widthGap) + leftMargin; y = vStartPadding + myCellY * (cellHeight + heightGap) + topMargin; } + + public String toString() { + return "(" + this.cellX + ", " + this.cellY + ")"; + } } 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. + * 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; @@ -947,7 +1153,7 @@ out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) { // 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 + // very often private static final int POOL_LIMIT = 100; private static final Object sLock = new Object(); @@ -982,8 +1188,8 @@ out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) { @Override public String toString() { - return "VacantCell[x=" + cellX + ", y=" + cellY + ", spanX=" + spanX + - ", spanY=" + spanY + "]"; + return "VacantCell[x=" + cellX + ", y=" + cellY + ", spanX=" + + spanX + ", spanY=" + spanY + "]"; } } @@ -1006,7 +1212,9 @@ out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) { final ArrayList<VacantCell> list = vacantCells; final int count = list.size(); - for (int i = 0; i < count; i++) list.get(i).release(); + for (int i = 0; i < count; i++) { + list.get(i).release(); + } list.clear(); } @@ -1052,7 +1260,9 @@ out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) { boolean found = false; - if (this.spanX >= spanX && this.spanY >= spanY) { + // return the span represented by the CellInfo only there is no view there + // (this.cell == null) and there is enough space + if (this.cell == null && this.spanX >= spanX && this.spanY >= spanY) { cellXY[0] = cellX; cellXY[1] = cellY; found = true; @@ -1080,15 +1290,17 @@ out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) { } } - if (clear) clearVacantCells(); + if (clear) { + clearVacantCells(); + } return found; } @Override public String toString() { - return "Cell[view=" + (cell == null ? "null" : cell.getClass()) + ", x=" + cellX + - ", y=" + cellY + "]"; + return "Cell[view=" + (cell == null ? "null" : cell.getClass()) + + ", x=" + cellX + ", y=" + cellY + "]"; } } @@ -1096,5 +1308,3 @@ out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) { return mLastDownOnOccupiedCell; } } - - |