Allow for NxM layout and in-place rotation of items on homescreen.
Currently, rotation is disabled as designs are still in flux, but the NxM grid is enabled (8x4).
Change-Id: I0026f88c674719e3d67de6d6d481d2d4cd606362
diff --git a/src/com/android/launcher2/CellLayout.java b/src/com/android/launcher2/CellLayout.java
index 73481c2..d69c56f 100644
--- a/src/com/android/launcher2/CellLayout.java
+++ b/src/com/android/launcher2/CellLayout.java
@@ -16,36 +16,44 @@
package com.android.launcher2;
+import java.util.ArrayList;
+
+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.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 java.util.ArrayList;
+import android.view.animation.Animation;
+import android.view.animation.LayoutAnimationController;
import com.android.launcher.R;
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;
@@ -54,7 +62,7 @@
private final Rect mRect = new Rect();
private final CellInfo mCellInfo = new CellInfo();
-
+
int[] mCellXY = new int[2];
boolean[][] mOccupied;
@@ -62,8 +70,8 @@
private boolean mDirtyTag;
private boolean mLastDownOnOccupiedCell = false;
-
- private final WallpaperManager mWallpaperManager;
+
+ private final WallpaperManager mWallpaperManager;
public CellLayout(Context context) {
this(context, null);
@@ -79,16 +87,16 @@
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 +104,6 @@
setAlwaysDrawnWithCacheEnabled(false);
- if (mOccupied == null) {
- if (mPortrait) {
- mOccupied = new boolean[mShortAxisCells][mLongAxisCells];
- } else {
- mOccupied = new boolean[mLongAxisCells][mShortAxisCells];
- }
- }
-
mWallpaperManager = WallpaperManager.getInstance(getContext());
}
@@ -132,14 +132,24 @@
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 lp = (LayoutParams) params;
- child.setId(((getId() & 0xFF) << 16) | (lp.cellX & 0xFF) << 8 | (lp.cellY & 0xFF));
+ 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
@@ -185,7 +195,7 @@
}
}
}
-
+
mLastDownOnOccupiedCell = found;
if (!found) {
@@ -217,6 +227,7 @@
setTag(cellInfo);
}
+
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
@@ -256,8 +267,8 @@
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;
@@ -392,21 +403,21 @@
// 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);
@@ -419,7 +430,7 @@
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
@@ -432,18 +443,15 @@
/**
* 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);
@@ -458,92 +466,117 @@
}
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);
- }
-
- 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);
}
@@ -596,7 +629,7 @@
/**
* 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 +641,12 @@
*/
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 +656,17 @@
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
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,25 +674,22 @@
}
}
- // 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
+ * 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();
@@ -678,7 +708,7 @@
/**
* Start dragging the specified child
- *
+ *
* @param child The child that is being dragged
*/
void onDragChild(View child) {
@@ -686,13 +716,13 @@
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
+ * @param cellY The child's new y cell location
*/
void onDragOverChild(View child, int cellX, int cellY) {
int[] cellXY = mCellXY;
@@ -701,39 +731,38 @@
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
*/
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;
-
+
+ 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);
}
-
+
/**
- * 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
*/
@@ -758,7 +787,7 @@
* @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) {
@@ -852,6 +881,17 @@
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 +916,7 @@
*/
@ViewDebug.ExportedProperty
public int cellVSpan;
-
+
/**
* Is this item currently being dragged
*/
@@ -902,7 +942,15 @@
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;
@@ -913,12 +961,12 @@
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) -
@@ -927,14 +975,18 @@
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;
@@ -945,7 +997,7 @@
// 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();
@@ -980,8 +1032,8 @@
@Override
public String toString() {
- return "VacantCell[x=" + cellX + ", y=" + cellY + ", spanX=" + spanX +
- ", spanY=" + spanY + "]";
+ return "VacantCell[x=" + cellX + ", y=" + cellY + ", spanX="
+ + spanX + ", spanY=" + spanY + "]";
}
}
@@ -1004,7 +1056,9 @@
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();
}
@@ -1078,15 +1132,17 @@
}
}
- 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 + "]";
}
}
@@ -1094,5 +1150,3 @@
return mLastDownOnOccupiedCell;
}
}
-
-