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
This commit is contained in:
Winson Chung
2010-06-11 17:34:16 -07:00
committed by Adam Cohen
parent 6404116682
commit aafa03cbb9
28 changed files with 992 additions and 419 deletions

View File

@@ -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 @@ public class CellLayout extends ViewGroup {
private final Rect mRect = new Rect();
private final CellInfo mCellInfo = new CellInfo();
int[] mCellXY = new int[2];
boolean[][] mOccupied;
@@ -62,8 +70,8 @@ public class CellLayout extends ViewGroup {
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 @@ public class CellLayout extends ViewGroup {
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 @@ 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());
}
@@ -132,14 +132,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 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 @@ public class CellLayout extends ViewGroup {
}
}
}
mLastDownOnOccupiedCell = found;
if (!found) {
@@ -217,6 +227,7 @@ public class CellLayout extends ViewGroup {
setTag(cellInfo);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
@@ -256,8 +267,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;
@@ -392,21 +403,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);
@@ -419,7 +430,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
@@ -432,18 +443,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);
@@ -458,92 +466,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);
int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.width,
MeasureSpec.EXACTLY);
int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(lp.height,
MeasureSpec.EXACTLY);
if (mPortrait) {
lp.setup(cellWidth, cellHeight, mWidthGap, mHeightGap, shortAxisStartPadding,
longAxisStartPadding);
} else {
lp.setup(cellWidth, cellHeight, mWidthGap, mHeightGap, longAxisStartPadding,
shortAxisStartPadding);
}
int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY);
int childheightMeasureSpec =
MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY);
child.measure(childWidthMeasureSpec, childheightMeasureSpec);
}
@@ -596,7 +629,7 @@ public class CellLayout extends ViewGroup {
/**
* 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 @@ 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 +656,17 @@ 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
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 @@ 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
* 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 @@ public class CellLayout extends ViewGroup {
/**
* Start dragging the specified child
*
*
* @param child The child that is being dragged
*/
void onDragChild(View child) {
@@ -686,13 +716,13 @@ 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
* @param cellY The child's new y cell location
*/
void onDragOverChild(View child, int cellX, int cellY) {
int[] cellXY = mCellXY;
@@ -701,39 +731,38 @@ public class CellLayout extends ViewGroup {
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 @@ 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) {
@@ -852,6 +881,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 +916,7 @@ out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) {
*/
@ViewDebug.ExportedProperty
public int cellVSpan;
/**
* Is this item currently being dragged
*/
@@ -902,7 +942,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;
@@ -913,12 +961,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) -
@@ -927,14 +975,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;
@@ -945,7 +997,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();
@@ -980,8 +1032,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 + "]";
}
}
@@ -1004,7 +1056,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();
}
@@ -1078,15 +1132,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 + "]";
}
}
@@ -1094,5 +1150,3 @@ out: for (int i = x; i < x + spanX - 1 && x < xCount; i++) {
return mLastDownOnOccupiedCell;
}
}