Merge "Scrolling folder during drag-drop" into ub-launcher3-burnaby
This commit is contained in:
@@ -49,8 +49,8 @@ public class DragController {
|
||||
/** Indicates the drag is a copy. */
|
||||
public static int DRAG_ACTION_COPY = 1;
|
||||
|
||||
private static final int SCROLL_DELAY = 500;
|
||||
private static final int RESCROLL_DELAY = PagedView.PAGE_SNAP_ANIMATION_DURATION + 150;
|
||||
public static final int SCROLL_DELAY = 500;
|
||||
public static final int RESCROLL_DELAY = PagedView.PAGE_SNAP_ANIMATION_DURATION + 150;
|
||||
|
||||
private static final boolean PROFILE_DRAWING_DURING_DRAG = false;
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@ import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.android.launcher3.FolderInfo.FolderListener;
|
||||
import com.android.launcher3.Workspace.ItemOperator;
|
||||
|
||||
@@ -74,6 +75,21 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
|
||||
static final int STATE_ANIMATING = 1;
|
||||
static final int STATE_OPEN = 2;
|
||||
|
||||
/**
|
||||
* Fraction of the width to scroll when showing the next page hint.
|
||||
*/
|
||||
private static final float SCROLL_HINT_FRACTION = 0.07f;
|
||||
|
||||
/**
|
||||
* Time for which the scroll hint is shown before automatically changing page.
|
||||
*/
|
||||
public static final int SCROLL_HINT_DURATION = DragController.SCROLL_DELAY;
|
||||
|
||||
/**
|
||||
* Fraction of icon width which behave as scroll region.
|
||||
*/
|
||||
private static final float ICON_OVERSCROLL_WIDTH_FACTOR = 0.45f;
|
||||
|
||||
private static final int REORDER_DELAY = 250;
|
||||
private static final int ON_EXIT_CLOSE_DELAY = 400;
|
||||
private static final Rect sTempRect = new Rect();
|
||||
@@ -129,6 +145,17 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
|
||||
private boolean mDeferDropAfterUninstall;
|
||||
private boolean mUninstallSuccessful;
|
||||
|
||||
// Folder scrolling
|
||||
private int mScrollAreaOffset;
|
||||
private Alarm mOnScrollHintAlarm;
|
||||
private Alarm mScrollPauseAlarm;
|
||||
|
||||
// TODO: Use {@link #mContent} once {@link #ALLOW_FOLDER_SCROLL} is removed.
|
||||
private FolderPagedView mPagedView;
|
||||
|
||||
private int mScrollHintDir = DragController.SCROLL_NONE;
|
||||
private int mCurrentScrollDir = DragController.SCROLL_NONE;
|
||||
|
||||
/**
|
||||
* Used to inflate the Workspace from XML.
|
||||
*
|
||||
@@ -157,6 +184,11 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
|
||||
// name is complete, we have something to focus on, thus hiding the cursor and giving
|
||||
// reliable behavior when clicking the text field (since it will always gain focus on click).
|
||||
setFocusableInTouchMode(true);
|
||||
|
||||
if (ALLOW_FOLDER_SCROLL) {
|
||||
mOnScrollHintAlarm = new Alarm();
|
||||
mScrollPauseAlarm = new Alarm();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -183,6 +215,10 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
|
||||
int measureSpec = MeasureSpec.UNSPECIFIED;
|
||||
mFooter.measure(measureSpec, measureSpec);
|
||||
mFooterHeight = mFooter.getMeasuredHeight();
|
||||
|
||||
if (ALLOW_FOLDER_SCROLL) {
|
||||
mPagedView = (FolderPagedView) mContent;
|
||||
}
|
||||
}
|
||||
|
||||
private ActionMode.Callback mActionModeCallback = new ActionMode.Callback() {
|
||||
@@ -386,6 +422,11 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
|
||||
public void animateOpen() {
|
||||
if (!(getParent() instanceof DragLayer)) return;
|
||||
|
||||
if (ALLOW_FOLDER_SCROLL) {
|
||||
// Always open on the first page.
|
||||
mPagedView.snapToPageImmediately(0);
|
||||
}
|
||||
|
||||
Animator openFolderAnim = null;
|
||||
final Runnable onCompleteRunnable;
|
||||
if (!Utilities.isLmpOrAbove()) {
|
||||
@@ -544,6 +585,11 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
|
||||
public void onDragEnter(DragObject d) {
|
||||
mPrevTargetRank = -1;
|
||||
mOnExitAlarm.cancelAlarm();
|
||||
if (ALLOW_FOLDER_SCROLL) {
|
||||
// Get the area offset such that the folder only closes if half the drag icon width
|
||||
// is outside the folder area
|
||||
mScrollAreaOffset = d.dragView.getDragRegionWidth() / 2 - d.xOffset;
|
||||
}
|
||||
}
|
||||
|
||||
OnAlarmListener mReorderAlarmListener = new OnAlarmListener() {
|
||||
@@ -558,18 +604,80 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
|
||||
return (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDragOver(DragObject d) {
|
||||
final float[] r = d.getVisualCenter(null);
|
||||
r[0] -= getPaddingLeft();
|
||||
r[1] -= getPaddingTop();
|
||||
onDragOver(d, REORDER_DELAY);
|
||||
}
|
||||
|
||||
private int getTargetRank(DragObject d, float[] recycle) {
|
||||
recycle = d.getVisualCenter(recycle);
|
||||
return mContent.findNearestArea(
|
||||
(int) recycle[0] - getPaddingLeft(), (int) recycle[1] - getPaddingTop());
|
||||
}
|
||||
|
||||
private void onDragOver(DragObject d, int reorderDelay) {
|
||||
if (ALLOW_FOLDER_SCROLL && mScrollPauseAlarm.alarmPending()) {
|
||||
return;
|
||||
}
|
||||
final float[] r = new float[2];
|
||||
mTargetRank = getTargetRank(d, r);
|
||||
|
||||
mTargetRank = mContent.findNearestArea((int) r[0], (int) r[1]);
|
||||
if (mTargetRank != mPrevTargetRank) {
|
||||
mReorderAlarm.cancelAlarm();
|
||||
mReorderAlarm.setOnAlarmListener(mReorderAlarmListener);
|
||||
mReorderAlarm.setAlarm(REORDER_DELAY);
|
||||
mPrevTargetRank = mTargetRank;
|
||||
}
|
||||
|
||||
if (!ALLOW_FOLDER_SCROLL) {
|
||||
return;
|
||||
}
|
||||
|
||||
float x = r[0];
|
||||
int currentPage = mPagedView.getNextPage();
|
||||
int cellWidth = mPagedView.getCurrentCellLayout().getCellWidth();
|
||||
if (currentPage > 0 && x < cellWidth * ICON_OVERSCROLL_WIDTH_FACTOR) {
|
||||
// Show scroll hint on the left
|
||||
if (mScrollHintDir != DragController.SCROLL_LEFT) {
|
||||
mPagedView.showScrollHint(-SCROLL_HINT_FRACTION);
|
||||
mScrollHintDir = DragController.SCROLL_LEFT;
|
||||
}
|
||||
|
||||
// Set alarm for when the hint is complete
|
||||
if (!mOnScrollHintAlarm.alarmPending() || mCurrentScrollDir != DragController.SCROLL_LEFT) {
|
||||
mCurrentScrollDir = DragController.SCROLL_LEFT;
|
||||
mOnScrollHintAlarm.cancelAlarm();
|
||||
mOnScrollHintAlarm.setOnAlarmListener(new OnScrollHintListener(d));
|
||||
mOnScrollHintAlarm.setAlarm(SCROLL_HINT_DURATION);
|
||||
|
||||
mReorderAlarm.cancelAlarm();
|
||||
mTargetRank = mEmptyCellRank;
|
||||
}
|
||||
} else if (currentPage < (mPagedView.getPageCount() - 1) &&
|
||||
(x > (getWidth() - cellWidth * ICON_OVERSCROLL_WIDTH_FACTOR))) {
|
||||
// Show scroll hint on the right
|
||||
if (mScrollHintDir != DragController.SCROLL_RIGHT) {
|
||||
mPagedView.showScrollHint(SCROLL_HINT_FRACTION);
|
||||
mScrollHintDir = DragController.SCROLL_RIGHT;
|
||||
}
|
||||
|
||||
// Set alarm for when the hint is complete
|
||||
if (!mOnScrollHintAlarm.alarmPending() || mCurrentScrollDir != DragController.SCROLL_RIGHT) {
|
||||
mCurrentScrollDir = DragController.SCROLL_RIGHT;
|
||||
mOnScrollHintAlarm.cancelAlarm();
|
||||
mOnScrollHintAlarm.setOnAlarmListener(new OnScrollHintListener(d));
|
||||
mOnScrollHintAlarm.setAlarm(SCROLL_HINT_DURATION);
|
||||
|
||||
mReorderAlarm.cancelAlarm();
|
||||
mTargetRank = mEmptyCellRank;
|
||||
}
|
||||
} else {
|
||||
mOnScrollHintAlarm.cancelAlarm();
|
||||
if (mScrollHintDir != DragController.SCROLL_NONE) {
|
||||
mPagedView.clearScrollHint();
|
||||
mScrollHintDir = DragController.SCROLL_NONE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OnAlarmListener mOnExitAlarmListener = new OnAlarmListener() {
|
||||
@@ -595,6 +703,15 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
|
||||
mOnExitAlarm.setAlarm(ON_EXIT_CLOSE_DELAY);
|
||||
}
|
||||
mReorderAlarm.cancelAlarm();
|
||||
|
||||
if (ALLOW_FOLDER_SCROLL) {
|
||||
mOnScrollHintAlarm.cancelAlarm();
|
||||
mScrollPauseAlarm.cancelAlarm();
|
||||
if (mScrollHintDir != DragController.SCROLL_NONE) {
|
||||
mPagedView.clearScrollHint();
|
||||
mScrollHintDir = DragController.SCROLL_NONE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onDropCompleted(final View target, final DragObject d,
|
||||
@@ -960,6 +1077,22 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
|
||||
};
|
||||
}
|
||||
|
||||
if (ALLOW_FOLDER_SCROLL) {
|
||||
// If the icon was dropped while the page was being scrolled, we need to compute
|
||||
// the target location again such that the icon is placed of the final page.
|
||||
if (!mPagedView.rankOnCurrentPage(mEmptyCellRank)) {
|
||||
// Reorder again.
|
||||
mTargetRank = getTargetRank(d, null);
|
||||
|
||||
// Rearrange items immediately.
|
||||
mReorderAlarmListener.onAlarm(mReorderAlarm);
|
||||
|
||||
mOnScrollHintAlarm.cancelAlarm();
|
||||
mScrollPauseAlarm.cancelAlarm();
|
||||
}
|
||||
mPagedView.completePendingPageChanges();
|
||||
}
|
||||
|
||||
View currentDragView;
|
||||
ShortcutInfo si = mCurrentDragInfo;
|
||||
if (mIsExternalDrag) {
|
||||
@@ -1090,6 +1223,57 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
|
||||
@Override
|
||||
public void getHitRectRelativeToDragLayer(Rect outRect) {
|
||||
getHitRect(outRect);
|
||||
outRect.left -= mScrollAreaOffset;
|
||||
outRect.right += mScrollAreaOffset;
|
||||
}
|
||||
|
||||
private class OnScrollHintListener implements OnAlarmListener {
|
||||
|
||||
private final DragObject mDragObject;
|
||||
|
||||
OnScrollHintListener(DragObject object) {
|
||||
mDragObject = object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll hint has been shown long enough. Now scroll to appropriate page.
|
||||
*/
|
||||
@Override
|
||||
public void onAlarm(Alarm alarm) {
|
||||
if (mCurrentScrollDir == DragController.SCROLL_LEFT) {
|
||||
mPagedView.scrollLeft();
|
||||
mScrollHintDir = DragController.SCROLL_NONE;
|
||||
} else if (mCurrentScrollDir == DragController.SCROLL_RIGHT) {
|
||||
mPagedView.scrollRight();
|
||||
mScrollHintDir = DragController.SCROLL_NONE;
|
||||
} else {
|
||||
// This should not happen
|
||||
return;
|
||||
}
|
||||
mCurrentScrollDir = DragController.SCROLL_NONE;
|
||||
|
||||
// Pause drag event until the scrolling is finished
|
||||
mScrollPauseAlarm.setOnAlarmListener(new OnScrollFinishedListener(mDragObject));
|
||||
mScrollPauseAlarm.setAlarm(DragController.RESCROLL_DELAY);
|
||||
}
|
||||
}
|
||||
|
||||
private class OnScrollFinishedListener implements OnAlarmListener {
|
||||
|
||||
private final DragObject mDragObject;
|
||||
|
||||
OnScrollFinishedListener(DragObject object) {
|
||||
mDragObject = object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Page scroll is complete.
|
||||
*/
|
||||
@Override
|
||||
public void onAlarm(Alarm alarm) {
|
||||
// Reorder immediately on page change.
|
||||
onDragOver(mDragObject, 1);
|
||||
}
|
||||
}
|
||||
|
||||
public static interface FolderContent {
|
||||
|
||||
@@ -22,6 +22,7 @@ import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.animation.DecelerateInterpolator;
|
||||
|
||||
import com.android.launcher3.FocusHelper.PagedFolderKeyEventListener;
|
||||
import com.android.launcher3.PageIndicator.PageMarkerResources;
|
||||
@@ -30,12 +31,15 @@ import com.android.launcher3.Workspace.ItemOperator;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
public class FolderPagedView extends PagedView implements Folder.FolderContent {
|
||||
|
||||
private static final String TAG = "FolderPagedView";
|
||||
|
||||
private static final int REORDER_ANIMATION_DURATION = 230;
|
||||
private static final int START_VIEW_REORDER_DELAY = 30;
|
||||
private static final float VIEW_REORDER_DELAY_FACTOR = 0.9f;
|
||||
private static final int[] sTempPosArray = new int[2];
|
||||
|
||||
// TODO: Remove this restriction
|
||||
@@ -137,18 +141,9 @@ public class FolderPagedView extends PagedView implements Folder.FolderContent {
|
||||
public int allocateNewLastItemRank() {
|
||||
int rank = getItemCount();
|
||||
int total = rank + 1;
|
||||
if (rank < mMaxItemsPerPage) {
|
||||
// Rearrange the items as the grid size might change.
|
||||
mFolder.rearrangeChildren(total);
|
||||
} else {
|
||||
setupContentDimensions(total);
|
||||
}
|
||||
// Rearrange the items as the grid size might change.
|
||||
mFolder.rearrangeChildren(total);
|
||||
|
||||
// Add a new page if last page is full
|
||||
if (getPageAt(getChildCount() - 1).getShortcutsAndWidgets().getChildCount()
|
||||
>= mMaxItemsPerPage) {
|
||||
createAndAddNewPage();
|
||||
}
|
||||
setCurrentPage(getChildCount() - 1);
|
||||
return rank;
|
||||
}
|
||||
@@ -265,7 +260,8 @@ public class FolderPagedView extends PagedView implements Folder.FolderContent {
|
||||
int newX, newY, rank;
|
||||
|
||||
rank = 0;
|
||||
for (View v : list) {
|
||||
for (int i = 0; i < itemCount; i++) {
|
||||
View v = list.size() > i ? list.get(i) : null;
|
||||
if (currentPage == null || position >= mMaxItemsPerPage) {
|
||||
// Next page
|
||||
if (pageItr.hasNext()) {
|
||||
@@ -276,25 +272,28 @@ public class FolderPagedView extends PagedView implements Folder.FolderContent {
|
||||
position = 0;
|
||||
}
|
||||
|
||||
CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams();
|
||||
newX = position % mGridCountX;
|
||||
newY = position / mGridCountX;
|
||||
ItemInfo info = (ItemInfo) v.getTag();
|
||||
if (info.cellX != newX || info.cellY != newY || info.rank != rank) {
|
||||
info.cellX = newX;
|
||||
info.cellY = newY;
|
||||
info.rank = rank;
|
||||
if (saveChanges) {
|
||||
LauncherModel.addOrMoveItemInDatabase(getContext(), info,
|
||||
mFolder.mInfo.id, 0, info.cellX, info.cellY);
|
||||
if (v != null) {
|
||||
CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams();
|
||||
newX = position % mGridCountX;
|
||||
newY = position / mGridCountX;
|
||||
ItemInfo info = (ItemInfo) v.getTag();
|
||||
if (info.cellX != newX || info.cellY != newY || info.rank != rank) {
|
||||
info.cellX = newX;
|
||||
info.cellY = newY;
|
||||
info.rank = rank;
|
||||
if (saveChanges) {
|
||||
LauncherModel.addOrMoveItemInDatabase(getContext(), info,
|
||||
mFolder.mInfo.id, 0, info.cellX, info.cellY);
|
||||
}
|
||||
}
|
||||
lp.cellX = info.cellX;
|
||||
lp.cellY = info.cellY;
|
||||
currentPage.addViewToCellLayout(
|
||||
v, -1, mFolder.mLauncher.getViewIdForItem(info), lp, true);
|
||||
}
|
||||
lp.cellX = info.cellX;
|
||||
lp.cellY = info.cellY;
|
||||
|
||||
rank ++;
|
||||
position++;
|
||||
currentPage.addViewToCellLayout(
|
||||
v, -1, mFolder.mLauncher.getViewIdForItem(info), lp, true);
|
||||
}
|
||||
|
||||
// Remove extra views.
|
||||
@@ -328,6 +327,10 @@ public class FolderPagedView extends PagedView implements Folder.FolderContent {
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
int lastPage = getChildCount() - 1;
|
||||
if (lastPage < 0) {
|
||||
// If there are no pages, there must be only one icon in the folder.
|
||||
return 1;
|
||||
}
|
||||
return getPageAt(lastPage).getShortcutsAndWidgets().getChildCount()
|
||||
+ lastPage * mMaxItemsPerPage;
|
||||
}
|
||||
@@ -406,10 +409,49 @@ public class FolderPagedView extends PagedView implements Folder.FolderContent {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scrolls the current view by a fraction
|
||||
*/
|
||||
public void showScrollHint(float fraction) {
|
||||
int hint = (int) (fraction * getWidth());
|
||||
int scroll = getScrollForPage(getNextPage()) + hint;
|
||||
int delta = scroll - mUnboundedScrollX;
|
||||
if (delta != 0) {
|
||||
mScroller.setInterpolator(new DecelerateInterpolator());
|
||||
mScroller.startScroll(mUnboundedScrollX, 0, delta, 0, Folder.SCROLL_HINT_DURATION);
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public void clearScrollHint() {
|
||||
if (mUnboundedScrollX != getScrollForPage(getNextPage())) {
|
||||
snapToPage(getNextPage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finish animation all the views which are animating across pages
|
||||
*/
|
||||
public void completePendingPageChanges() {
|
||||
if (!mPageChangingViews.isEmpty()) {
|
||||
HashMap<View, Runnable> pendingViews = new HashMap<>(mPageChangingViews);
|
||||
for (Map.Entry<View, Runnable> e : pendingViews.entrySet()) {
|
||||
e.getKey().animate().cancel();
|
||||
e.getValue().run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean rankOnCurrentPage(int rank) {
|
||||
int p = rank / mMaxItemsPerPage;
|
||||
return p == getNextPage();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void realTimeReorder(int empty, int target) {
|
||||
completePendingPageChanges();
|
||||
int delay = 0;
|
||||
float delayAmount = 30;
|
||||
float delayAmount = START_VIEW_REORDER_DELAY;
|
||||
|
||||
// Animation only happens on the current page.
|
||||
int pageToAnimate = getNextPage();
|
||||
@@ -523,7 +565,7 @@ public class FolderPagedView extends PagedView implements Folder.FolderContent {
|
||||
if (page.animateChildToPosition(v, i % mGridCountX, i / mGridCountX,
|
||||
REORDER_ANIMATION_DURATION, delay, true, true)) {
|
||||
delay += delayAmount;
|
||||
delayAmount *= 0.9;
|
||||
delayAmount *= VIEW_REORDER_DELAY_FACTOR;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user