From 55bd9725d5c0373b89f7b9bbd9547550ea3bbc63 Mon Sep 17 00:00:00 2001 From: Jorim Jaggi Date: Thu, 16 Jan 2014 15:30:42 -0800 Subject: Implement spring-loading of folders when dragging over. Bug: 8912132 Change-Id: Id81889a133e56461df2e20599c4b40020818ba18 --- .../android/launcher3/AppsCustomizePagedView.java | 2 +- src/com/android/launcher3/DragLayer.java | 35 +++--- src/com/android/launcher3/Folder.java | 139 +++++++++++++++------ src/com/android/launcher3/FolderIcon.java | 31 +++++ src/com/android/launcher3/Launcher.java | 1 + 5 files changed, 147 insertions(+), 61 deletions(-) diff --git a/src/com/android/launcher3/AppsCustomizePagedView.java b/src/com/android/launcher3/AppsCustomizePagedView.java index 49b12b1b1..251ae2108 100644 --- a/src/com/android/launcher3/AppsCustomizePagedView.java +++ b/src/com/android/launcher3/AppsCustomizePagedView.java @@ -817,7 +817,7 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen */ private void endDragging(View target, boolean isFlingToDelete, boolean success) { if (isFlingToDelete || !success || (target != mLauncher.getWorkspace() && - !(target instanceof DeleteDropTarget))) { + !(target instanceof DeleteDropTarget) && !(target instanceof Folder))) { // Exit spring loaded mode if we have not successfully dropped or have not handled the // drop in Workspace mLauncher.getWorkspace().removeExtraEmptyScreen(true, new Runnable() { diff --git a/src/com/android/launcher3/DragLayer.java b/src/com/android/launcher3/DragLayer.java index ab0469d53..862ceca28 100644 --- a/src/com/android/launcher3/DragLayer.java +++ b/src/com/android/launcher3/DragLayer.java @@ -65,8 +65,6 @@ public class DragLayer extends FrameLayout implements ViewGroup.OnHierarchyChang private boolean mHoverPointClosesFolder = false; private Rect mHitRect = new Rect(); - private int mWorkspaceIndex = -1; - private int mQsbIndex = -1; public static final int ANIMATION_END_DISAPPEAR = 0; public static final int ANIMATION_END_FADE_OUT = 1; public static final int ANIMATION_END_REMAIN_VISIBLE = 2; @@ -75,6 +73,8 @@ public class DragLayer extends FrameLayout implements ViewGroup.OnHierarchyChang private final Rect mInsets = new Rect(); + private int mDragViewIndex; + /** * Used to create a new DragLayer from XML. * @@ -771,31 +771,26 @@ public class DragLayer extends FrameLayout implements ViewGroup.OnHierarchyChang } private void updateChildIndices() { - if (mLauncher != null) { - mWorkspaceIndex = indexOfChild(mLauncher.getWorkspace()); - mQsbIndex = indexOfChild(mLauncher.getSearchBar()); + mDragViewIndex = -1; + int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + if (getChildAt(i) instanceof DragView) { + mDragViewIndex = i; + } } } @Override protected int getChildDrawingOrder(int childCount, int i) { - // TODO: We have turned off this custom drawing order because it now effects touch - // dispatch order. We need to sort that issue out and then decide how to go about this. - if (true || LauncherAppState.isScreenLandscape(getContext()) || - mWorkspaceIndex == -1 || mQsbIndex == -1 || - mLauncher.getWorkspace().isDrawingBackgroundGradient()) { + if (mDragViewIndex == -1) { return i; - } - - // This ensures that the workspace is drawn above the hotseat and qsb, - // except when the workspace is drawing a background gradient, in which - // case we want the workspace to stay behind these elements. - if (i == mQsbIndex) { - return mWorkspaceIndex; - } else if (i == mWorkspaceIndex) { - return mQsbIndex; - } else { + } else if (i == mDragViewIndex) { + return getChildCount()-1; + } else if (i < mDragViewIndex) { return i; + } else { + // i > mDragViewIndex + return i-1; } } diff --git a/src/com/android/launcher3/Folder.java b/src/com/android/launcher3/Folder.java index a9134e105..b4c399266 100644 --- a/src/com/android/launcher3/Folder.java +++ b/src/com/android/launcher3/Folder.java @@ -24,7 +24,6 @@ import android.content.Context; import android.content.res.Resources; import android.graphics.PointF; import android.graphics.Rect; -import android.graphics.drawable.Drawable; import android.os.SystemClock; import android.support.v4.widget.AutoScrollHelper; import android.text.InputType; @@ -70,6 +69,8 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList static final int STATE_ANIMATING = 1; static final int STATE_OPEN = 2; + private static final int CLOSE_FOLDER_DELAY_MS = 150; + private int mExpandDuration; protected CellLayout mContent; private ScrollView mScrollView; @@ -85,10 +86,10 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList private int mMaxCountY; private int mMaxNumItems; private ArrayList mItemsInReadingOrder = new ArrayList(); - private Drawable mIconDrawable; boolean mItemsInvalidated = false; private ShortcutInfo mCurrentDragInfo; private View mCurrentDragView; + private boolean mIsExternalDrag; boolean mSuppressOnAdd = false; private int[] mTargetCell = new int[2]; private int[] mPreviousTargetCell = new int[2]; @@ -242,7 +243,6 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList mLauncher.getWorkspace().onDragStartedWithItem(v); mLauncher.getWorkspace().beginDragShared(v, this); - mIconDrawable = ((TextView) v).getCompoundDrawables()[1]; mCurrentDragInfo = item; mEmptyCell[0] = item.cellX; @@ -303,10 +303,6 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList return mFolderName; } - public Drawable getDragDrawable() { - return mIconDrawable; - } - /** * We need to handle touch events to prevent them from falling through to the workspace below. */ @@ -381,7 +377,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList int count = 0; for (int i = 0; i < children.size(); i++) { ShortcutInfo child = (ShortcutInfo) children.get(i); - if (!createAndAddShortcut(child)) { + if (createAndAddShortcut(child) == null) { overflow.add(child); } else { count++; @@ -466,11 +462,15 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList public void onAnimationEnd(Animator animation) { mState = STATE_OPEN; setLayerType(LAYER_TYPE_NONE, null); - Cling cling = mLauncher.getLauncherClings().showFoldersCling(); - if (cling != null) { - cling.bringScrimToFront(); - bringToFront(); - cling.bringToFront(); + + // Only show cling if we are not in the middle of a drag - this would be quite jarring. + if (!mDragController.isDragging()) { + Cling cling = mLauncher.getLauncherClings().showFoldersCling(); + if (cling != null) { + cling.bringScrimToFront(); + bringToFront(); + cling.bringToFront(); + } } setFocusOnFirstChild(); } @@ -478,6 +478,23 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList oa.setDuration(mExpandDuration); setLayerType(LAYER_TYPE_HARDWARE, null); oa.start(); + + // Make sure the folder picks up the last drag move even if the finger doesn't move. + if (mDragController.isDragging()) { + mDragController.forceTouchMove(); + } + } + + public void beginExternalDrag(ShortcutInfo item) { + setupContentForNumItems(getItemCount() + 1); + findAndSetEmptyCells(item); + + mCurrentDragInfo = item; + mEmptyCell[0] = item.cellX; + mEmptyCell[1] = item.cellY; + mIsExternalDrag = true; + + mDragInProgress = true; } private void sendCustomAccessibilityEvent(int type, String text) { @@ -544,7 +561,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList } } - protected boolean createAndAddShortcut(ShortcutInfo item) { + protected View createAndAddShortcut(ShortcutInfo item) { final BubbleTextView textView = (BubbleTextView) mInflater.inflate(R.layout.application, this, false); textView.setCompoundDrawables(null, @@ -565,7 +582,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList // This shouldn't happen, log it. Log.e(TAG, "Folder order not properly persisted during bind"); if (!findAndSetEmptyCells(item)) { - return false; + return null; } } @@ -574,7 +591,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList boolean insert = false; textView.setOnKeyListener(new FolderKeyEventListener()); mContent.addViewToCellLayout(textView, insert ? 0 : -1, (int)item.id, lp, true); - return true; + return textView; } public void onDragEnter(DragObject d) { @@ -723,6 +740,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList mCurrentDragView = null; mSuppressOnAdd = false; mRearrangeOnClose = true; + mIsExternalDrag = false; } public void onDragExit(DragObject d) { @@ -756,7 +774,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList success && (!beingCalledAfterUninstall || mUninstallSuccessful); if (successfulDrop) { - if (mDeleteFolderOnDropCompleted && !mItemAddedBackToSelfViaIcon) { + if (mDeleteFolderOnDropCompleted && !mItemAddedBackToSelfViaIcon && target != this) { replaceFolderWithFinalItem(); } } else { @@ -1152,34 +1170,75 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList } public void onDrop(DragObject d) { - ShortcutInfo item; - if (d.dragInfo instanceof AppInfo) { - // Came from all apps -- make a copy - item = ((AppInfo) d.dragInfo).makeShortcut(); - item.spanX = 1; - item.spanY = 1; - } else { - item = (ShortcutInfo) d.dragInfo; + Runnable cleanUpRunnable = null; + + // If we are coming from All Apps space, we need to remove the extra empty screen (which is + // normally done in Workspace#onDropExternal, as well zoom back in and close the folder. + if (d.dragSource != mLauncher.getWorkspace() && !(d.dragSource instanceof Folder)) { + cleanUpRunnable = new Runnable() { + @Override + public void run() { + mLauncher.getWorkspace().removeExtraEmptyScreen(false, new Runnable() { + @Override + public void run() { + mLauncher.closeFolder(); + mLauncher.exitSpringLoadedDragModeDelayed(true, + Launcher.EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT_FOLDER_CLOSE, + null); + } + }, CLOSE_FOLDER_DELAY_MS, false); + } + }; } - // Dragged from self onto self, currently this is the only path possible, however - // we keep this as a distinct code path. - if (item == mCurrentDragInfo) { - ShortcutInfo si = (ShortcutInfo) mCurrentDragView.getTag(); - CellLayout.LayoutParams lp = (CellLayout.LayoutParams) mCurrentDragView.getLayoutParams(); + + View currentDragView; + ShortcutInfo si = mCurrentDragInfo; + if (mIsExternalDrag) { + si.cellX = mEmptyCell[0]; + si.cellY = mEmptyCell[1]; + currentDragView = createAndAddShortcut(si); + } else { + currentDragView = mCurrentDragView; + CellLayout.LayoutParams lp = (CellLayout.LayoutParams) currentDragView.getLayoutParams(); si.cellX = lp.cellX = mEmptyCell[0]; si.cellX = lp.cellY = mEmptyCell[1]; - mContent.addViewToCellLayout(mCurrentDragView, -1, (int)item.id, lp, true); - if (d.dragView.hasDrawn()) { - mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, mCurrentDragView); - } else { - d.deferDragViewCleanupPostAnimation = false; - mCurrentDragView.setVisibility(VISIBLE); + mContent.addViewToCellLayout(currentDragView, -1, (int) si.id, lp, true); + } + + if (d.dragView.hasDrawn()) { + + // Temporarily reset the scale such that the animation target gets calculated correctly. + float scaleX = getScaleX(); + float scaleY = getScaleY(); + setScaleX(1.0f); + setScaleY(1.0f); + mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, currentDragView, + cleanUpRunnable, null); + setScaleX(scaleX); + setScaleY(scaleY); + } else { + d.deferDragViewCleanupPostAnimation = false; + currentDragView.setVisibility(VISIBLE); + } + mItemsInvalidated = true; + setupContentDimensions(getItemCount()); + + // Actually move the item in the database if it was an external drag. + if (mIsExternalDrag) { + LauncherModel.addOrMoveItemInDatabase( + mLauncher, si, mInfo.id, 0, si.cellX, si.cellY); + + // We only need to update the locations if it doesn't get handled in #onDropCompleted. + if (d.dragSource != this) { + updateItemLocationsInDatabaseBatch(); } - mItemsInvalidated = true; - setupContentDimensions(getItemCount()); - mSuppressOnAdd = true; + mIsExternalDrag = false; } - mInfo.add(item); + + // Temporarily suppress the listener, as we did all the work already here. + mSuppressOnAdd = true; + mInfo.add(si); + mSuppressOnAdd = false; } // This is used so the item doesn't immediately appear in the folder when added. In one case diff --git a/src/com/android/launcher3/FolderIcon.java b/src/com/android/launcher3/FolderIcon.java index 644db47ac..78026f162 100644 --- a/src/com/android/launcher3/FolderIcon.java +++ b/src/com/android/launcher3/FolderIcon.java @@ -75,10 +75,16 @@ public class FolderIcon extends FrameLayout implements FolderListener { // Flag as to whether or not to draw an outer ring. Currently none is designed. public static final boolean HAS_OUTER_RING = true; + // Flag whether the folder should open itself when an item is dragged over is enabled. + public static final boolean SPRING_LOADING_ENABLED = true; + // The degree to which the item in the back of the stack is scaled [0...1] // (0 means it's not scaled at all, 1 means it's scaled to nothing) private static final float PERSPECTIVE_SCALE_FACTOR = 0.35f; + // Delay when drag enters until the folder opens, in miliseconds. + private static final int ON_OPEN_DELAY = 800; + public static Drawable sSharedFolderLeaveBehind = null; private ImageView mPreviewBackground; @@ -103,6 +109,9 @@ public class FolderIcon extends FrameLayout implements FolderListener { private PreviewItemDrawingParams mAnimParams = new PreviewItemDrawingParams(0, 0, 0, 0); private ArrayList mHiddenItems = new ArrayList(); + private Alarm mOpenAlarm = new Alarm(); + private ItemInfo mDragInfo; + public FolderIcon(Context context, AttributeSet attrs) { super(context, attrs); init(); @@ -331,11 +340,32 @@ public class FolderIcon extends FrameLayout implements FolderListener { mFolderRingAnimator.setCellLayout(layout); mFolderRingAnimator.animateToAcceptState(); layout.showFolderAccept(mFolderRingAnimator); + mOpenAlarm.setOnAlarmListener(mOnOpenListener); + if (SPRING_LOADING_ENABLED) { + mOpenAlarm.setAlarm(ON_OPEN_DELAY); + } + mDragInfo = (ItemInfo) dragInfo; } public void onDragOver(Object dragInfo) { } + OnAlarmListener mOnOpenListener = new OnAlarmListener() { + public void onAlarm(Alarm alarm) { + ShortcutInfo item; + if (mDragInfo instanceof AppInfo) { + // Came from all apps -- make a copy. + item = ((AppInfo) mDragInfo).makeShortcut(); + item.spanX = 1; + item.spanY = 1; + } else { + item = (ShortcutInfo) mDragInfo; + } + mFolder.beginExternalDrag(item); + mLauncher.openFolder(FolderIcon.this); + } + }; + public void performCreateAnimation(final ShortcutInfo destInfo, final View destView, final ShortcutInfo srcInfo, final DragView srcView, Rect dstRect, float scaleRelativeToDragLayer, Runnable postAnimationRunnable) { @@ -371,6 +401,7 @@ public class FolderIcon extends FrameLayout implements FolderListener { public void onDragExit() { mFolderRingAnimator.animateToNaturalState(); + mOpenAlarm.cancelAlarm(); } private void onDrop(final ShortcutInfo item, DragView animateView, Rect finalRect, diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index c57d32e31..c87f9e2f0 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -210,6 +210,7 @@ public class Launcher extends Activity static final int APPWIDGET_HOST_ID = 1024; public static final int EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT = 300; + public static final int EXIT_SPRINGLOADED_MODE_SHORT_TIMEOUT_FOLDER_CLOSE = 400; private static final int ON_ACTIVITY_RESULT_ANIMATION_DELAY = 500; private static final Object sLock = new Object(); -- cgit v1.2.3