From bfbfd26c627a18f8e1e3e6d0e53e78feab360203 Mon Sep 17 00:00:00 2001 From: Adam Cohen Date: Mon, 13 Jun 2011 16:55:12 -0700 Subject: Initial implementation of folder reordering Change-Id: I9f700e71f46e3b91afa96742d9e3890d519c391d --- src/com/android/launcher2/CellLayout.java | 63 ++++++ src/com/android/launcher2/CellLayoutChildren.java | 4 + src/com/android/launcher2/DragController.java | 3 + src/com/android/launcher2/DropTarget.java | 6 + src/com/android/launcher2/Folder.java | 230 +++++++++++++++------- src/com/android/launcher2/Workspace.java | 37 ---- 6 files changed, 240 insertions(+), 103 deletions(-) (limited to 'src/com') diff --git a/src/com/android/launcher2/CellLayout.java b/src/com/android/launcher2/CellLayout.java index e5aa7a96a..a9ba88d94 100644 --- a/src/com/android/launcher2/CellLayout.java +++ b/src/com/android/launcher2/CellLayout.java @@ -20,6 +20,7 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; +import android.animation.PropertyValuesHolder; import android.animation.TimeInterpolator; import android.animation.ValueAnimator; import android.animation.ValueAnimator.AnimatorUpdateListener; @@ -48,6 +49,7 @@ import android.view.animation.LayoutAnimationController; import com.android.launcher.R; import java.util.Arrays; +import java.util.HashMap; public class CellLayout extends ViewGroup { static final String TAG = "CellLayout"; @@ -118,6 +120,9 @@ public class CellLayout extends ViewGroup { private InterruptibleInOutAnimator mCrosshairsAnimator = null; private float mCrosshairsVisibility = 0.0f; + private HashMap mReorderAnimators = new + HashMap(); + // When a drag operation is in progress, holds the nearest cell to the touch point private final int[] mDragCell = new int[2]; @@ -966,6 +971,64 @@ public class CellLayout extends ViewGroup { return mChildren.getChildAt(x, y); } + public boolean animateChildToPosition(final View child, int cellX, int cellY, int duration) { + CellLayoutChildren clc = getChildrenLayout(); + if (clc.indexOfChild(child) != -1 && !mOccupied[cellX][cellY]) { + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + final ItemInfo info = (ItemInfo) child.getTag(); + + // We cancel any existing animations + if (mReorderAnimators.containsKey(lp)) { + mReorderAnimators.get(lp).cancel(); + mReorderAnimators.remove(lp); + } + + int oldX = lp.x; + int oldY = lp.y; + mOccupied[lp.cellX][lp.cellY] = false; + mOccupied[cellX][cellY] = true; + + lp.isLockedToGrid = true; + lp.cellX = info.cellX = cellX; + lp.cellY = info.cellY = cellY; + clc.setupLp(lp); + lp.isLockedToGrid = false; + int newX = lp.x; + int newY = lp.y; + + PropertyValuesHolder x = PropertyValuesHolder.ofInt("x", oldX, newX); + PropertyValuesHolder y = PropertyValuesHolder.ofInt("y", oldY, newY); + ObjectAnimator oa = ObjectAnimator.ofPropertyValuesHolder(lp, x, y); + oa.setDuration(duration); + mReorderAnimators.put(lp, oa); + oa.addUpdateListener(new AnimatorUpdateListener() { + public void onAnimationUpdate(ValueAnimator animation) { + child.requestLayout(); + } + }); + oa.addListener(new AnimatorListenerAdapter() { + boolean cancelled = false; + public void onAnimationEnd(Animator animation) { + // If the animation was cancelled, it means that another animation + // has interrupted this one, and we don't want to lock the item into + // place just yet. + if (!cancelled) { + lp.isLockedToGrid = true; + } + if (mReorderAnimators.containsKey(lp)) { + mReorderAnimators.remove(lp); + } + } + public void onAnimationCancel(Animator animation) { + cancelled = true; + } + }); + oa.start(); + return true; + } + return false; + } + /** * Estimate where the top left cell of the dragged item will land if it is dropped. * diff --git a/src/com/android/launcher2/CellLayoutChildren.java b/src/com/android/launcher2/CellLayoutChildren.java index 59db9c9ed..6e78885de 100644 --- a/src/com/android/launcher2/CellLayoutChildren.java +++ b/src/com/android/launcher2/CellLayoutChildren.java @@ -79,6 +79,10 @@ public class CellLayoutChildren extends ViewGroup { setMeasuredDimension(widthSpecSize, heightSpecSize); } + public void setupLp(CellLayout.LayoutParams lp) { + lp.setup(mCellWidth, mCellHeight, mWidthGap, mHeightGap); + } + public void measureChild(View child) { final int cellWidth = mCellWidth; final int cellHeight = mCellHeight; diff --git a/src/com/android/launcher2/DragController.java b/src/com/android/launcher2/DragController.java index 8ad5c7ce5..5b1b20a68 100644 --- a/src/com/android/launcher2/DragController.java +++ b/src/com/android/launcher2/DragController.java @@ -284,6 +284,7 @@ public class DragController { mDragging = true; + mDragObject.dragComplete = false; mDragObject.xOffset = mMotionDownX - (screenX + dragRegionLeft); mDragObject.yOffset = mMotionDownY - (screenY + dragRegionTop); mDragObject.dragSource = source; @@ -373,6 +374,7 @@ public class DragController { public void cancelDrag() { if (mDragging) { // Should we also be calling onDragExit() here? + mDragObject.dragComplete = true; mDragObject.dragSource.onDropCompleted(null, mDragObject, false); } endDrag(); @@ -565,6 +567,7 @@ public class DragController { mDragObject.y = coordinates[1]; boolean accepted = false; if (dropTarget != null) { + mDragObject.dragComplete = true; dropTarget.onDragExit(mDragObject); if (dropTarget.acceptDrop(mDragObject)) { dropTarget.onDrop(mDragObject); diff --git a/src/com/android/launcher2/DropTarget.java b/src/com/android/launcher2/DropTarget.java index 9c0faf31f..6e18479ac 100644 --- a/src/com/android/launcher2/DropTarget.java +++ b/src/com/android/launcher2/DropTarget.java @@ -34,6 +34,12 @@ public interface DropTarget { /** Y offset from the upper-left corner of the cell to where we touched. */ public int yOffset = -1; + /** This indicates whether a drag is in final stages, either drop or cancel. It + * differentiates onDragExit, since this is called when the drag is ending, above + * the current drag target, or when the drag moves off the current drag object. + */ + public boolean dragComplete = false; + /** The view that moves around while you drag. */ public DragView dragView = null; diff --git a/src/com/android/launcher2/Folder.java b/src/com/android/launcher2/Folder.java index d6be3072c..d81183c6b 100644 --- a/src/com/android/launcher2/Folder.java +++ b/src/com/android/launcher2/Folder.java @@ -23,6 +23,7 @@ import android.animation.PropertyValuesHolder; import android.animation.ValueAnimator; import android.animation.ValueAnimator.AnimatorUpdateListener; import android.content.Context; +import android.graphics.Color; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.util.AttributeSet; @@ -54,11 +55,6 @@ public class Folder extends LinearLayout implements DragSource, OnItemLongClickL protected Launcher mLauncher; protected FolderInfo mInfo; - - /** - * Which item is being dragged - */ - protected ShortcutInfo mDragItem; private static final String TAG = "Launcher.Folder"; @@ -75,6 +71,8 @@ public class Folder extends LinearLayout implements DragSource, OnItemLongClickL private int[] mDragItemPosition = new int[2]; private static final int FULL_GROW = 0; private static final int PARTIAL_GROW = 1; + private static final int REORDER_ANIMATION_DURATION = 230; + private static final int ON_EXIT_CLOSE_DELAY = 800; private int mMode = PARTIAL_GROW; private boolean mRearrangeOnClose = false; private FolderIcon mFolderIcon; @@ -84,7 +82,14 @@ public class Folder extends LinearLayout implements DragSource, OnItemLongClickL private ArrayList mItemsInReadingOrder = new ArrayList(); private Drawable mIconDrawable; boolean mItemsInvalidated = false; - ShortcutInfo mCurrentDragInfo; + private ShortcutInfo mCurrentDragInfo; + private View mCurrentDragView; + boolean mSuppressOnAdd = false; + private int[] mTargetCell = new int[2]; + private int[] mPreviousTargetCell = new int[2]; + private int[] mEmptyCell = new int[2]; + private Alarm mReorderAlarm = new Alarm(); + private Alarm mOnExitAlarm = new Alarm(); /** * Used to inflate the Workspace from XML. @@ -136,8 +141,6 @@ public class Folder extends LinearLayout implements DragSource, OnItemLongClickL public boolean onLongClick(View v) { Object tag = v.getTag(); if (tag instanceof ShortcutInfo) { - mLauncher.closeFolder(this); - ShortcutInfo item = (ShortcutInfo) tag; if (!v.isInTouchMode()) { return false; @@ -150,10 +153,11 @@ public class Folder extends LinearLayout implements DragSource, OnItemLongClickL mIconDrawable = ((TextView) v).getCompoundDrawables()[1]; mCurrentDragInfo = item; - mItemsInvalidated = true; - mInfo.itemsChanged(); - - mDragItem = item; + mEmptyCell[0] = item.cellX; + mEmptyCell[1] = item.cellY; + mCurrentDragView = v; + mContent.removeView(mCurrentDragView); + mInfo.remove(item); } else { mLauncher.closeFolder(this); mLauncher.showRenameDialog(mInfo); @@ -182,8 +186,6 @@ public class Folder extends LinearLayout implements DragSource, OnItemLongClickL mDragController.startDrag(view, this, app, DragController.DRAG_ACTION_COPY); mLauncher.closeFolder(this); - mDragItem = app; - return true; } @@ -382,25 +384,6 @@ public class Folder extends LinearLayout implements DragSource, OnItemLongClickL !isFull()); } - public void onDrop(DragObject d) { - ShortcutInfo item; - if (d.dragInfo instanceof ApplicationInfo) { - // Came from all apps -- make a copy - item = ((ApplicationInfo) d.dragInfo).makeShortcut(); - item.spanX = 1; - item.spanY = 1; - } else { - item = (ShortcutInfo) d.dragInfo; - } - - // Dragged from self onto self - if (item == mCurrentDragInfo) { - mInfo.remove(item); - } - - mInfo.add(item); - } - protected boolean findAndSetEmptyCells(ShortcutInfo item) { int[] emptyCell = new int[2]; if (mContent.findCellForSpan(emptyCell, item.spanX, item.spanY)) { @@ -430,46 +413,135 @@ public class Folder extends LinearLayout implements DragSource, OnItemLongClickL } public void onDragEnter(DragObject d) { + mPreviousTargetCell[0] = -1; + mPreviousTargetCell[1] = -1; mContent.onDragEnter(); + mOnExitAlarm.cancelAlarm(); } - public void onDragOver(DragObject d) { - float[] r = mapPointFromScreenToContent(d.x, d.y, null); - mContent.visualizeDropLocation(null, null, (int) r[0], (int) r[1], 1, 1); + OnAlarmListener mReorderAlarmListener = new OnAlarmListener() { + public void onAlarm(Alarm alarm) { + realTimeReorder(mEmptyCell, mTargetCell); + } + }; + + boolean readingOrderGreaterThan(int[] v1, int[] v2) { + if (v1[1] > v2[1] || (v1[1] == v2[1] && v1[0] > v2[0])) { + return true; + } else { + return false; + } } - public void onDragExit(DragObject d) { - mContent.onDragExit(); + private void realTimeReorder(int[] empty, int[] target) { + boolean wrap; + int startX; + int endX; + int startY; + if (readingOrderGreaterThan(target, empty)) { + wrap = empty[0] >= mContent.getCountX() - 1; + startY = wrap ? empty[1] + 1 : empty[1]; + for (int y = startY; y <= target[1]; y++) { + startX = y == empty[1] ? empty[0] + 1 : 0; + endX = y < target[1] ? mContent.getCountX() - 1 : target[0]; + for (int x = startX; x <= endX; x++) { + View v = mContent.getChildAt(x,y); + if (mContent.animateChildToPosition(v, empty[0], empty[1], + REORDER_ANIMATION_DURATION)) { + empty[0] = x; + empty[1] = y; + } + } + } + } else { + wrap = empty[0] == 0; + startY = wrap ? empty[1] - 1 : empty[1]; + for (int y = startY; y >= target[1]; y--) { + startX = y == empty[1] ? empty[0] - 1 : mContent.getCountX() - 1; + endX = y > target[1] ? 0 : target[0]; + for (int x = startX; x >= endX; x--) { + View v = mContent.getChildAt(x,y); + if (mContent.animateChildToPosition(v, empty[0], empty[1], + REORDER_ANIMATION_DURATION)) { + empty[0] = x; + empty[1] = y; + } + } + } + } } - public float[] mapPointFromScreenToContent(int x, int y, float[] r) { - if (r == null) { - r = new float[2]; + public void onDragOver(DragObject d) { + float[] r = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset, d.dragView, null); + mTargetCell = mContent.findNearestArea((int) r[0], (int) r[1], 1, 1, mTargetCell); + + if (mTargetCell[0] != mPreviousTargetCell[0] || mTargetCell[1] != mPreviousTargetCell[1]) { + mReorderAlarm.cancelAlarm(); + mReorderAlarm.setOnAlarmListener(mReorderAlarmListener); + mReorderAlarm.setAlarm(150); + mPreviousTargetCell[0] = mTargetCell[0]; + mPreviousTargetCell[1] = mTargetCell[1]; } + } + + // This is used to compute the visual center of the dragView. The idea is that + // the visual center represents the user's interpretation of where the item is, and hence + // is the appropriate point to use when determining drop location. + private float[] getDragViewVisualCenter(int x, int y, int xOffset, int yOffset, + DragView dragView, float[] recycle) { + float res[]; + if (recycle == null) { + res = new float[2]; + } else { + res = recycle; + } + + // These represent the visual top and left of drag view if a dragRect was provided. + // If a dragRect was not provided, then they correspond to the actual view left and + // top, as the dragRect is in that case taken to be the entire dragView. + // R.dimen.dragViewOffsetY. + int left = x - xOffset; + int top = y - yOffset; + + // In order to find the visual center, we shift by half the dragRect + res[0] = left + dragView.getDragRegion().width() / 2; + res[1] = top + dragView.getDragRegion().height() / 2; + + return res; + } - int[] screenLocation = new int[2]; - mContent.getLocationOnScreen(screenLocation); + OnAlarmListener mOnExitAlarmListener = new OnAlarmListener() { + public void onAlarm(Alarm alarm) { + mLauncher.closeFolder(Folder.this); + mCurrentDragInfo = null; + mCurrentDragView = null; + mSuppressOnAdd = false; + mRearrangeOnClose = true; + } + }; - r[0] = x - screenLocation[0]; - r[1] = y - screenLocation[1]; - return r; + public void onDragExit(DragObject d) { + // We only close the folder if this is a true drag exit, ie. not because a drop + // has occurred above the folder. + if (!d.dragComplete) { + mOnExitAlarm.setOnAlarmListener(mOnExitAlarmListener); + mOnExitAlarm.setAlarm(ON_EXIT_CLOSE_DELAY); + } + mReorderAlarm.cancelAlarm(); + mContent.onDragExit(); } public void onDropCompleted(View target, DragObject d, boolean success) { + mCurrentDragInfo = null; + mCurrentDragView = null; + mSuppressOnAdd = false; if (!success) { if (d.dragView != null) { if (target instanceof CellLayout) { // TODO: we should animate the item back to the folder in this case } } - mCurrentDragInfo = null; - mItemsInvalidated = true; - mInfo.itemsChanged(); - } else { - if (target != this) { - mInfo.remove(mCurrentDragInfo); - mCurrentDragInfo = null; - } + // TODO: if the drag fails, we need to re-add the item } } @@ -594,18 +666,6 @@ public class Folder extends LinearLayout implements DragSource, OnItemLongClickL mItemsInvalidated = true; } - public void onAdd(ShortcutInfo item) { - mItemsInvalidated = true; - if (!findAndSetEmptyCells(item)) { - // The current layout is full, can we expand it? - setupContentForNumItems(getItemCount() + 1); - findAndSetEmptyCells(item); - } - createAndAddShortcut(item); - LauncherModel.addOrMoveItemInDatabase( - mLauncher, item, mInfo.id, 0, item.cellX, item.cellY); - } - public int getItemCount() { return mContent.getChildrenLayout().getChildCount(); } @@ -621,8 +681,46 @@ public class Folder extends LinearLayout implements DragSource, OnItemLongClickL } } + public void onDrop(DragObject d) { + ShortcutInfo item; + if (d.dragInfo instanceof ApplicationInfo) { + // Came from all apps -- make a copy + item = ((ApplicationInfo) d.dragInfo).makeShortcut(); + item.spanX = 1; + item.spanY = 1; + } else { + item = (ShortcutInfo) d.dragInfo; + } + // Dragged from self onto self + if (item == mCurrentDragInfo) { + ShortcutInfo si = (ShortcutInfo) mCurrentDragView.getTag(); + CellLayout.LayoutParams lp = (CellLayout.LayoutParams) mCurrentDragView.getLayoutParams(); + si.cellX = lp.cellX = mEmptyCell[0]; + si.cellX = lp.cellY = mEmptyCell[1]; + mContent.addViewToCellLayout(mCurrentDragView, -1, (int)item.id, lp, true); + mSuppressOnAdd = true; + mItemsInvalidated = true; + setupContentDimension(getItemCount()); + } + mInfo.add(item); + } + + public void onAdd(ShortcutInfo item) { + mItemsInvalidated = true; + if (mSuppressOnAdd) return; + if (!findAndSetEmptyCells(item)) { + // The current layout is full, can we expand it? + setupContentForNumItems(getItemCount() + 1); + findAndSetEmptyCells(item); + } + createAndAddShortcut(item); + LauncherModel.addOrMoveItemInDatabase( + mLauncher, item, mInfo.id, 0, item.cellX, item.cellY); + } + public void onRemove(ShortcutInfo item) { mItemsInvalidated = true; + if (item == mCurrentDragInfo) return; View v = mContent.getChildAt(mDragItemPosition[0], mDragItemPosition[1]); mContent.removeView(v); if (mState == STATE_ANIMATING) { diff --git a/src/com/android/launcher2/Workspace.java b/src/com/android/launcher2/Workspace.java index ff2dede6e..1e012540b 100644 --- a/src/com/android/launcher2/Workspace.java +++ b/src/com/android/launcher2/Workspace.java @@ -2555,42 +2555,6 @@ public class Workspace extends SmoothPagedView } public DropTarget getDropTargetDelegate(DragObject d) { - - if (mIsSmall || mIsInUnshrinkAnimation) { - // If we're shrunken, don't let anyone drag on folders/etc that are on the mini-screens - return null; - } - // We may need to delegate the drag to a child view. If a 1x1 item - // would land in a cell occupied by a DragTarget (e.g. a Folder), - // then drag events should be handled by that child. - - ItemInfo item = (ItemInfo) d.dragInfo; - CellLayout currentLayout = getCurrentDropLayout(); - - int dragPointX, dragPointY; - if (item.spanX == 1 && item.spanY == 1) { - // For a 1x1, calculate the drop cell exactly as in onDragOver - dragPointX = d.x - d.xOffset; - dragPointY = d.y - d.yOffset; - } else { - // Otherwise, use the exact drag coordinates - dragPointX = d.x; - dragPointY = d.y; - } - dragPointX += mScrollX - currentLayout.getLeft(); - dragPointY += mScrollY - currentLayout.getTop(); - - // If we are dragging over a cell that contains a DropTarget that will - // accept the drop, delegate to that DropTarget. - final int[] cellXY = mTempCell; - currentLayout.estimateDropCell(dragPointX, dragPointY, item.spanX, item.spanY, cellXY); - View child = currentLayout.getChildAt(cellXY[0], cellXY[1]); - if (child instanceof DropTarget) { - DropTarget target = (DropTarget)child; - if (target.acceptDrop(d)) { - return target; - } - } return null; } @@ -3559,5 +3523,4 @@ public class Workspace extends SmoothPagedView @Override public void syncPageItems(int page) { } - } -- cgit v1.2.3