From fa3c58f647e603760c0bd929166c0edee25675a9 Mon Sep 17 00:00:00 2001 From: Adam Cohen Date: Fri, 6 Dec 2013 16:10:55 -0800 Subject: Adding reorder hinting, hopefully will help with (issue 10654628) -> Adds instant feedback that reorder _will_ happen, so hopefully will improve perceived disambiguation Change-Id: Icd99e7beeb87e79627a6b9fc78a9132781410379 --- src/com/android/launcher3/CellLayout.java | 156 +++++++++++++++++++----------- src/com/android/launcher3/Workspace.java | 17 ++-- 2 files changed, 109 insertions(+), 64 deletions(-) diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java index df16d4194..a05dd90c0 100644 --- a/src/com/android/launcher3/CellLayout.java +++ b/src/com/android/launcher3/CellLayout.java @@ -133,8 +133,8 @@ public class CellLayout extends ViewGroup { private HashMap mReorderAnimators = new HashMap(); - private HashMap - mShakeAnimators = new HashMap(); + private HashMap + mShakeAnimators = new HashMap(); private boolean mItemPlacementDirty = false; @@ -149,19 +149,20 @@ public class CellLayout extends ViewGroup { private boolean mIsHotseat = false; private float mHotseatScale = 1f; - public static final int MODE_DRAG_OVER = 0; - public static final int MODE_ON_DROP = 1; - public static final int MODE_ON_DROP_EXTERNAL = 2; - public static final int MODE_ACCEPT_DROP = 3; + public static final int MODE_SHOW_REORDER_HINT = 0; + public static final int MODE_DRAG_OVER = 1; + public static final int MODE_ON_DROP = 2; + public static final int MODE_ON_DROP_EXTERNAL = 3; + public static final int MODE_ACCEPT_DROP = 4; private static final boolean DESTRUCTIVE_REORDER = false; private static final boolean DEBUG_VISUALIZE_OCCUPIED = false; static final int LANDSCAPE = 0; static final int PORTRAIT = 1; - private static final float REORDER_HINT_MAGNITUDE = 0.12f; + private static final float REORDER_PREVIEW_MAGNITUDE = 0.12f; private static final int REORDER_ANIMATION_DURATION = 150; - private float mReorderHintAnimationMagnitude; + private float mReorderPreviewAnimationMagnitude; private ArrayList mIntersectingViews = new ArrayList(); private Rect mOccupiedRect = new Rect(); @@ -225,7 +226,7 @@ public class CellLayout extends ViewGroup { mForegroundPadding = res.getDimensionPixelSize(R.dimen.workspace_overscroll_drawable_padding); - mReorderHintAnimationMagnitude = (REORDER_HINT_MAGNITUDE * + mReorderPreviewAnimationMagnitude = (REORDER_PREVIEW_MAGNITUDE * grid.iconSizePx); mNormalBackground.setFilterBitmap(true); @@ -2088,6 +2089,8 @@ public class CellLayout extends ViewGroup { } } + solution.intersectingViews = new ArrayList(mIntersectingViews); + // First we try to find a solution which respects the push mechanic. That is, // we try to find a solution such that no displaced item travels through another item // without also displacing that item. @@ -2136,8 +2139,9 @@ public class CellLayout extends ViewGroup { } } - ItemConfiguration simpleSwap(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, - int spanY, int[] direction, View dragView, boolean decX, ItemConfiguration solution) { + ItemConfiguration findReorderSolution(int pixelX, int pixelY, int minSpanX, int minSpanY, + int spanX, int spanY, int[] direction, View dragView, boolean decX, + ItemConfiguration solution) { // Copy the current state into the solution. This solution will be manipulated as necessary. copyCurrentStateToSolution(solution, false); // Copy the current occupied array into the temporary occupied array. This array will be @@ -2159,11 +2163,11 @@ public class CellLayout extends ViewGroup { // We try shrinking the widget down to size in an alternating pattern, shrink 1 in // x, then 1 in y etc. if (spanX > minSpanX && (minSpanY == spanY || decX)) { - return simpleSwap(pixelX, pixelY, minSpanX, minSpanY, spanX - 1, spanY, direction, - dragView, false, solution); + return findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX - 1, spanY, + direction, dragView, false, solution); } else if (spanY > minSpanY) { - return simpleSwap(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY - 1, direction, - dragView, true, solution); + return findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY - 1, + direction, dragView, true, solution); } solution.isSolution = false; } else { @@ -2243,25 +2247,30 @@ public class CellLayout extends ViewGroup { } } - // This method starts or changes the reorder hint animations - private void beginOrAdjustHintAnimations(ItemConfiguration solution, View dragView, int delay) { + + // This method starts or changes the reorder preview animations + private void beginOrAdjustReorderPreviewAnimations(ItemConfiguration solution, + View dragView, int delay, int mode) { int childCount = mShortcutsAndWidgets.getChildCount(); for (int i = 0; i < childCount; i++) { View child = mShortcutsAndWidgets.getChildAt(i); if (child == dragView) continue; CellAndSpan c = solution.map.get(child); + boolean skip = mode == ReorderPreviewAnimation.MODE_HINT && solution.intersectingViews + != null && !solution.intersectingViews.contains(child); + LayoutParams lp = (LayoutParams) child.getLayoutParams(); - if (c != null) { - ReorderHintAnimation rha = new ReorderHintAnimation(child, lp.cellX, lp.cellY, - c.x, c.y, c.spanX, c.spanY); + if (c != null && !skip) { + ReorderPreviewAnimation rha = new ReorderPreviewAnimation(child, mode, lp.cellX, + lp.cellY, c.x, c.y, c.spanX, c.spanY); rha.animate(); } } } - // Class which represents the reorder hint animations. These animations show that an item is + // Class which represents the reorder preview animations. These animations show that an item is // in a temporary state, and hint at where the item will return to. - class ReorderHintAnimation { + class ReorderPreviewAnimation { View child; float finalDeltaX; float finalDeltaY; @@ -2269,11 +2278,18 @@ public class CellLayout extends ViewGroup { float initDeltaY; float finalScale; float initScale; - private static final int DURATION = 300; + int mode; + boolean repeating = false; + private static final int PREVIEW_DURATION = 300; + private static final int HINT_DURATION = Workspace.REORDER_TIMEOUT; + + public static final int MODE_HINT = 0; + public static final int MODE_PREVIEW = 1; + Animator a; - public ReorderHintAnimation(View child, int cellX0, int cellY0, int cellX1, int cellY1, - int spanX, int spanY) { + public ReorderPreviewAnimation(View child, int mode, int cellX0, int cellY0, int cellX1, + int cellY1, int spanX, int spanY) { regionToCenterPoint(cellX0, cellY0, spanX, spanY, mTmpPoint); final int x0 = mTmpPoint[0]; final int y0 = mTmpPoint[1]; @@ -2284,20 +2300,22 @@ public class CellLayout extends ViewGroup { final int dY = y1 - y0; finalDeltaX = 0; finalDeltaY = 0; + int dir = mode == MODE_HINT ? -1 : 1; if (dX == dY && dX == 0) { } else { if (dY == 0) { - finalDeltaX = - Math.signum(dX) * mReorderHintAnimationMagnitude; + finalDeltaX = - dir * Math.signum(dX) * mReorderPreviewAnimationMagnitude; } else if (dX == 0) { - finalDeltaY = - Math.signum(dY) * mReorderHintAnimationMagnitude; + finalDeltaY = - dir * Math.signum(dY) * mReorderPreviewAnimationMagnitude; } else { double angle = Math.atan( (float) (dY) / dX); - finalDeltaX = (int) (- Math.signum(dX) * - Math.abs(Math.cos(angle) * mReorderHintAnimationMagnitude)); - finalDeltaY = (int) (- Math.signum(dY) * - Math.abs(Math.sin(angle) * mReorderHintAnimationMagnitude)); + finalDeltaX = (int) (- dir * Math.signum(dX) * + Math.abs(Math.cos(angle) * mReorderPreviewAnimationMagnitude)); + finalDeltaY = (int) (- dir * Math.signum(dY) * + Math.abs(Math.sin(angle) * mReorderPreviewAnimationMagnitude)); } } + this.mode = mode; initDeltaX = child.getTranslationX(); initDeltaY = child.getTranslationY(); finalScale = getChildrenScale() - 4.0f / child.getWidth(); @@ -2307,7 +2325,7 @@ public class CellLayout extends ViewGroup { void animate() { if (mShakeAnimators.containsKey(child)) { - ReorderHintAnimation oldAnimation = mShakeAnimators.get(child); + ReorderPreviewAnimation oldAnimation = mShakeAnimators.get(child); oldAnimation.cancel(); mShakeAnimators.remove(child); if (finalDeltaX == 0 && finalDeltaY == 0) { @@ -2322,14 +2340,15 @@ public class CellLayout extends ViewGroup { a = va; va.setRepeatMode(ValueAnimator.REVERSE); va.setRepeatCount(ValueAnimator.INFINITE); - va.setDuration(DURATION); + va.setDuration(mode == MODE_HINT ? HINT_DURATION : PREVIEW_DURATION); va.setStartDelay((int) (Math.random() * 60)); va.addUpdateListener(new AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { float r = ((Float) animation.getAnimatedValue()).floatValue(); - float x = r * finalDeltaX + (1 - r) * initDeltaX; - float y = r * finalDeltaY + (1 - r) * initDeltaY; + float r1 = (mode == MODE_HINT && repeating) ? 1.0f : r; + float x = r1 * finalDeltaX + (1 - r1) * initDeltaX; + float y = r1 * finalDeltaY + (1 - r1) * initDeltaY; child.setTranslationX(x); child.setTranslationY(y); float s = r * finalScale + (1 - r) * initScale; @@ -2343,6 +2362,7 @@ public class CellLayout extends ViewGroup { initDeltaX = 0; initDeltaY = 0; initScale = getChildrenScale(); + repeating = true; } }); mShakeAnimators.put(child, this); @@ -2374,8 +2394,8 @@ public class CellLayout extends ViewGroup { } } - private void completeAndClearReorderHintAnimations() { - for (ReorderHintAnimation a: mShakeAnimators.values()) { + private void completeAndClearReorderPreviewAnimations() { + for (ReorderPreviewAnimation a: mShakeAnimators.values()) { a.completeAnimationImmediately(); } mShakeAnimators.clear(); @@ -2518,20 +2538,21 @@ public class CellLayout extends ViewGroup { } void revertTempState() { - if (!isItemPlacementDirty() || DESTRUCTIVE_REORDER) return; - final int count = mShortcutsAndWidgets.getChildCount(); - for (int i = 0; i < count; i++) { - View child = mShortcutsAndWidgets.getChildAt(i); - LayoutParams lp = (LayoutParams) child.getLayoutParams(); - if (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY) { - lp.tmpCellX = lp.cellX; - lp.tmpCellY = lp.cellY; - animateChildToPosition(child, lp.cellX, lp.cellY, REORDER_ANIMATION_DURATION, - 0, false, false); + completeAndClearReorderPreviewAnimations(); + if (isItemPlacementDirty() && !DESTRUCTIVE_REORDER) { + final int count = mShortcutsAndWidgets.getChildCount(); + for (int i = 0; i < count; i++) { + View child = mShortcutsAndWidgets.getChildAt(i); + LayoutParams lp = (LayoutParams) child.getLayoutParams(); + if (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY) { + lp.tmpCellX = lp.cellX; + lp.tmpCellY = lp.cellY; + animateChildToPosition(child, lp.cellX, lp.cellY, REORDER_ANIMATION_DURATION, + 0, false, false); + } } + setItemPlacementDirty(false); } - completeAndClearReorderHintAnimations(); - setItemPlacementDirty(false); } boolean createAreaForResize(int cellX, int cellY, int spanX, int spanY, @@ -2540,7 +2561,7 @@ public class CellLayout extends ViewGroup { regionToCenterPoint(cellX, cellY, spanX, spanY, pixelXY); // First we determine if things have moved enough to cause a different layout - ItemConfiguration swapSolution = simpleSwap(pixelXY[0], pixelXY[1], spanX, spanY, + ItemConfiguration swapSolution = findReorderSolution(pixelXY[0], pixelXY[1], spanX, spanY, spanX, spanY, direction, dragView, true, new ItemConfiguration()); setUseTempCoords(true); @@ -2554,18 +2575,18 @@ public class CellLayout extends ViewGroup { if (commit) { commitTempPlacement(); - completeAndClearReorderHintAnimations(); + completeAndClearReorderPreviewAnimations(); setItemPlacementDirty(false); } else { - beginOrAdjustHintAnimations(swapSolution, dragView, - REORDER_ANIMATION_DURATION); + beginOrAdjustReorderPreviewAnimations(swapSolution, dragView, + REORDER_ANIMATION_DURATION, ReorderPreviewAnimation.MODE_PREVIEW); } mShortcutsAndWidgets.requestLayout(); } return swapSolution.isSolution; } - int[] createArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY, + int[] performReorder(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY, View dragView, int[] result, int resultSpan[], int mode) { // First we determine if things have moved enough to cause a different layout result = findNearestArea(pixelX, pixelY, spanX, spanY, result); @@ -2592,7 +2613,8 @@ public class CellLayout extends ViewGroup { mPreviousReorderDirection[1] = mDirectionVector[1]; } - ItemConfiguration swapSolution = simpleSwap(pixelX, pixelY, minSpanX, minSpanY, + // Find a solution involving pushing / displacing any items in the way + ItemConfiguration swapSolution = findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, mDirectionVector, dragView, true, new ItemConfiguration()); // We attempt the approach which doesn't shuffle views at all @@ -2600,12 +2622,29 @@ public class CellLayout extends ViewGroup { minSpanY, spanX, spanY, dragView, new ItemConfiguration()); ItemConfiguration finalSolution = null; + + // If the reorder solution requires resizing (shrinking) the item being dropped, we instead + // favor a solution in which the item is not resized, but if (swapSolution.isSolution && swapSolution.area() >= noShuffleSolution.area()) { finalSolution = swapSolution; } else if (noShuffleSolution.isSolution) { finalSolution = noShuffleSolution; } + if (mode == MODE_SHOW_REORDER_HINT) { + beginOrAdjustReorderPreviewAnimations(finalSolution, dragView, 0, + ReorderPreviewAnimation.MODE_HINT); + if (finalSolution != null) { + result[0] = finalSolution.dragViewX; + result[1] = finalSolution.dragViewY; + resultSpan[0] = finalSolution.dragViewSpanX; + resultSpan[1] = finalSolution.dragViewSpanY; + } else { + result[0] = result[1] = resultSpan[0] = resultSpan[1] = -1; + } + return result; + } + boolean foundSolution = true; if (!DESTRUCTIVE_REORDER) { setUseTempCoords(true); @@ -2630,11 +2669,11 @@ public class CellLayout extends ViewGroup { if (!DESTRUCTIVE_REORDER && (mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL)) { commitTempPlacement(); - completeAndClearReorderHintAnimations(); + completeAndClearReorderPreviewAnimations(); setItemPlacementDirty(false); } else { - beginOrAdjustHintAnimations(finalSolution, dragView, - REORDER_ANIMATION_DURATION); + beginOrAdjustReorderPreviewAnimations(finalSolution, dragView, + REORDER_ANIMATION_DURATION, ReorderPreviewAnimation.MODE_PREVIEW); } } } else { @@ -2661,6 +2700,7 @@ public class CellLayout extends ViewGroup { HashMap map = new HashMap(); private HashMap savedMap = new HashMap(); ArrayList sortedViews = new ArrayList(); + ArrayList intersectingViews; boolean isSolution = false; int dragViewX, dragViewY, dragViewSpanX, dragViewSpanY; diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index bf4f561e0..72399e4ec 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -216,7 +216,7 @@ public class Workspace extends SmoothPagedView // Variables relating to the creation of user folders by hovering shortcuts over shortcuts private static final int FOLDER_CREATION_TIMEOUT = 0; - private static final int REORDER_TIMEOUT = 250; + public static final int REORDER_TIMEOUT = 350; private final Alarm mFolderCreationAlarm = new Alarm(); private final Alarm mReorderAlarm = new Alarm(); private FolderRingAnimator mDragFolderRingAnimator = null; @@ -2709,7 +2709,7 @@ public class Workspace extends SmoothPagedView } int[] resultSpan = new int[2]; - mTargetCell = dropTargetLayout.createArea((int) mDragViewVisualCenter[0], + mTargetCell = dropTargetLayout.performReorder((int) mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY, null, mTargetCell, resultSpan, CellLayout.MODE_ACCEPT_DROP); boolean foundCell = mTargetCell[0] >= 0 && mTargetCell[1] >= 0; @@ -2935,7 +2935,7 @@ public class Workspace extends SmoothPagedView } int[] resultSpan = new int[2]; - mTargetCell = dropTargetLayout.createArea((int) mDragViewVisualCenter[0], + mTargetCell = dropTargetLayout.performReorder((int) mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY, cell, mTargetCell, resultSpan, CellLayout.MODE_ON_DROP); @@ -3517,6 +3517,11 @@ public class Workspace extends SmoothPagedView && !mReorderAlarm.alarmPending() && (mLastReorderX != reorderX || mLastReorderY != reorderY)) { + int[] resultSpan = new int[2]; + mDragTargetLayout.performReorder((int) mDragViewVisualCenter[0], + (int) mDragViewVisualCenter[1], minSpanX, minSpanY, item.spanX, item.spanY, + child, mTargetCell, resultSpan, CellLayout.MODE_SHOW_REORDER_HINT); + // Otherwise, if we aren't adding to or creating a folder and there's no pending // reorder, then we schedule a reorder ReorderAlarmListener listener = new ReorderAlarmListener(mDragViewVisualCenter, @@ -3621,7 +3626,7 @@ public class Workspace extends SmoothPagedView mLastReorderX = mTargetCell[0]; mLastReorderY = mTargetCell[1]; - mTargetCell = mDragTargetLayout.createArea((int) mDragViewVisualCenter[0], + mTargetCell = mDragTargetLayout.performReorder((int) mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1], minSpanX, minSpanY, spanX, spanY, child, mTargetCell, resultSpan, CellLayout.MODE_DRAG_OVER); @@ -3731,7 +3736,7 @@ public class Workspace extends SmoothPagedView minSpanY = item.minSpanY; } int[] resultSpan = new int[2]; - mTargetCell = cellLayout.createArea((int) mDragViewVisualCenter[0], + mTargetCell = cellLayout.performReorder((int) mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1], minSpanX, minSpanY, info.spanX, info.spanY, null, mTargetCell, resultSpan, CellLayout.MODE_ON_DROP_EXTERNAL); @@ -3823,7 +3828,7 @@ public class Workspace extends SmoothPagedView if (touchXY != null) { // when dragging and dropping, just find the closest free spot - mTargetCell = cellLayout.createArea((int) mDragViewVisualCenter[0], + mTargetCell = cellLayout.performReorder((int) mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1], 1, 1, 1, 1, null, mTargetCell, null, CellLayout.MODE_ON_DROP_EXTERNAL); } else { -- cgit v1.2.3