summaryrefslogtreecommitdiffstats
path: root/src/com/android/launcher2/CellLayout.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/launcher2/CellLayout.java')
-rw-r--r--src/com/android/launcher2/CellLayout.java648
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;
}
}
-
-