summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHyunyoung Song <hyunyoungs@google.com>2016-06-06 21:20:45 +0000
committerAndroid (Google) Code Review <android-gerrit@google.com>2016-06-06 21:20:45 +0000
commit63741779e564ea9c6814800199b994f7464906db (patch)
tree2fa66db52a63ab1c67b2ea05766f7a2daaa38ce2
parent02d3d43d97a06b8066495de8eeb63a46ee98b7f2 (diff)
parent645764e3e5fa34d9adcddfc722d726b76f048306 (diff)
downloadandroid_packages_apps_Trebuchet-63741779e564ea9c6814800199b994f7464906db.tar.gz
android_packages_apps_Trebuchet-63741779e564ea9c6814800199b994f7464906db.tar.bz2
android_packages_apps_Trebuchet-63741779e564ea9c6814800199b994f7464906db.zip
Merge "Pull up all apps interaction First phase implementation: dragging and animation interaction is implemented namely in two classes. ScrollGestureDetector and AllAppsTransitionController." into ub-launcher3-calgary
-rw-r--r--src/com/android/launcher3/BaseContainerView.java9
-rw-r--r--src/com/android/launcher3/InsettableFrameLayout.java6
-rw-r--r--src/com/android/launcher3/Launcher.java31
-rw-r--r--src/com/android/launcher3/LauncherStateTransitionAnimation.java204
-rw-r--r--src/com/android/launcher3/PinchToOverviewListener.java10
-rw-r--r--src/com/android/launcher3/allapps/AllAppsContainerView.java76
-rw-r--r--src/com/android/launcher3/allapps/AllAppsSearchBarController.java3
-rw-r--r--src/com/android/launcher3/allapps/AllAppsTransitionController.java347
-rw-r--r--src/com/android/launcher3/allapps/VerticalPullDetector.java196
-rw-r--r--src/com/android/launcher3/dragndrop/DragController.java3
-rw-r--r--src/com/android/launcher3/dragndrop/DragLayer.java38
-rw-r--r--src/com/android/launcher3/util/TouchController.java8
12 files changed, 852 insertions, 79 deletions
diff --git a/src/com/android/launcher3/BaseContainerView.java b/src/com/android/launcher3/BaseContainerView.java
index d0cacd374..a9ef43dbf 100644
--- a/src/com/android/launcher3/BaseContainerView.java
+++ b/src/com/android/launcher3/BaseContainerView.java
@@ -24,6 +24,9 @@ import android.util.AttributeSet;
import android.view.View;
import android.widget.FrameLayout;
+import com.android.launcher3.allapps.AllAppsContainerView;
+import com.android.launcher3.config.FeatureFlags;
+
/**
* A base container view, which supports resizing.
*/
@@ -48,7 +51,11 @@ public abstract class BaseContainerView extends FrameLayout {
super(context, attrs, defStyleAttr);
int width = ((Launcher) context).getDeviceProfile().availableWidthPx;
- mHorizontalPadding = DeviceProfile.getContainerPadding(context, width);
+ if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && (this instanceof AllAppsContainerView)) {
+ mHorizontalPadding = 0;
+ } else {
+ mHorizontalPadding = DeviceProfile.getContainerPadding(context, width);
+ }
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.BaseContainerView, defStyleAttr, 0);
diff --git a/src/com/android/launcher3/InsettableFrameLayout.java b/src/com/android/launcher3/InsettableFrameLayout.java
index f4bfa4549..61edc0f7f 100644
--- a/src/com/android/launcher3/InsettableFrameLayout.java
+++ b/src/com/android/launcher3/InsettableFrameLayout.java
@@ -9,6 +9,9 @@ import android.view.ViewDebug;
import android.view.ViewGroup;
import android.widget.FrameLayout;
+import com.android.launcher3.allapps.AllAppsContainerView;
+import com.android.launcher3.config.FeatureFlags;
+
public class InsettableFrameLayout extends FrameLayout implements
ViewGroup.OnHierarchyChangeListener, Insettable {
@@ -31,6 +34,9 @@ public class InsettableFrameLayout extends FrameLayout implements
lp.rightMargin += (newInsets.right - oldInsets.right);
lp.bottomMargin += (newInsets.bottom - oldInsets.bottom);
}
+ if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP && child instanceof AllAppsContainerView) {
+ lp.setMargins(0, 0, 0, lp.bottomMargin);
+ }
child.setLayoutParams(lp);
}
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index ca31ea54d..13690b43a 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -92,6 +92,7 @@ import android.widget.Toast;
import com.android.launcher3.DropTarget.DragObject;
import com.android.launcher3.allapps.AllAppsContainerView;
+import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.allapps.DefaultAppSearchController;
import com.android.launcher3.compat.AppWidgetManagerCompat;
import com.android.launcher3.compat.LauncherActivityInfoCompat;
@@ -256,6 +257,7 @@ public class Launcher extends Activity
// Main container view for the all apps screen.
@Thunk AllAppsContainerView mAppsView;
+ AllAppsTransitionController mAllAppsController;
// Main container view and the model for the widget tray screen.
@Thunk WidgetsContainerView mWidgetsView;
@@ -413,7 +415,8 @@ public class Launcher extends Activity
mIconCache = app.getIconCache();
mDragController = new DragController(this);
- mStateTransitionAnimation = new LauncherStateTransitionAnimation(this);
+ mAllAppsController = new AllAppsTransitionController(this);
+ mStateTransitionAnimation = new LauncherStateTransitionAnimation(this, mAllAppsController);
mAppWidgetManager = AppWidgetManagerCompat.getInstance(this);
@@ -1339,8 +1342,6 @@ public class Launcher extends Activity
* Finds all the views we need and configure them properly.
*/
private void setupViews() {
- final DragController dragController = mDragController;
-
mLauncherView = findViewById(R.id.launcher);
mFocusHandler = (FocusIndicatorView) findViewById(R.id.focus_indicator);
mDragLayer = (DragLayer) findViewById(R.id.drag_layer);
@@ -1353,7 +1354,8 @@ public class Launcher extends Activity
mWorkspaceBackgroundDrawable = getResources().getDrawable(R.drawable.workspace_bg);
// Setup the drag layer
- mDragLayer.setup(this, dragController);
+
+ mDragLayer.setup(this, mDragController, mAllAppsController);
// Setup the hotseat
mHotseat = (Hotseat) findViewById(R.id.hotseat);
@@ -1367,9 +1369,9 @@ public class Launcher extends Activity
// Setup the workspace
mWorkspace.setHapticFeedbackEnabled(false);
mWorkspace.setOnLongClickListener(this);
- mWorkspace.setup(dragController);
+ mWorkspace.setup(mDragController);
mWorkspace.bindAndInitFirstWorkspaceScreen(null /* recycled qsb */);
- dragController.addDragListener(mWorkspace);
+ mDragController.addDragListener(mWorkspace);
// Get the search/delete/uninstall bar
mSearchDropTargetBar = (SearchDropTargetBar)
@@ -1388,15 +1390,15 @@ public class Launcher extends Activity
}
// Setup the drag controller (drop targets have to be added in reverse order in priority)
- dragController.setDragScoller(mWorkspace);
- dragController.setScrollView(mDragLayer);
- dragController.setMoveTarget(mWorkspace);
- dragController.addDropTarget(mWorkspace);
+ mDragController.setDragScoller(mWorkspace);
+ mDragController.setScrollView(mDragLayer);
+ mDragController.setMoveTarget(mWorkspace);
+ mDragController.addDropTarget(mWorkspace);
if (mSearchDropTargetBar != null) {
- mSearchDropTargetBar.setup(this, dragController);
+ mSearchDropTargetBar.setup(this, mDragController);
}
if (mAppInfoDropTargetBar != null) {
- mAppInfoDropTargetBar.setup(this, dragController);
+ mAppInfoDropTargetBar.setup(this, mDragController);
}
if (TestingUtils.MEMORY_DUMP_ENABLED) {
@@ -1940,6 +1942,7 @@ public class Launcher extends Activity
if (mWorkspace.getChildCount() > 0) {
outState.putInt(RUNTIME_STATE_CURRENT_SCREEN,
mWorkspace.getCurrentPageOffsetFromCustomContent());
+
}
super.onSaveInstanceState(outState);
@@ -3163,6 +3166,8 @@ public class Launcher extends Activity
mWorkspace.startReordering(v);
} else {
showOverviewMode(true);
+ mHotseat.requestDisallowInterceptTouchEvent(true);
+
}
} else {
final boolean isAllAppsButton = inHotseat && isAllAppsButtonRank(
@@ -3318,7 +3323,7 @@ public class Launcher extends Activity
/**
* Shows the apps view.
*/
- void showAppsView(boolean animated, boolean resetListToTop, boolean updatePredictedApps,
+ public void showAppsView(boolean animated, boolean resetListToTop, boolean updatePredictedApps,
boolean focusSearchBar) {
if (resetListToTop) {
mAppsView.scrollToTop();
diff --git a/src/com/android/launcher3/LauncherStateTransitionAnimation.java b/src/com/android/launcher3/LauncherStateTransitionAnimation.java
index 17a5424a4..41e30b129 100644
--- a/src/com/android/launcher3/LauncherStateTransitionAnimation.java
+++ b/src/com/android/launcher3/LauncherStateTransitionAnimation.java
@@ -33,6 +33,8 @@ import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import com.android.launcher3.allapps.AllAppsContainerView;
+import com.android.launcher3.allapps.AllAppsTransitionController;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.Thunk;
import com.android.launcher3.util.UiThreadCircularReveal;
import com.android.launcher3.widget.WidgetsContainerView;
@@ -82,6 +84,17 @@ import java.util.HashMap;
*/
public class LauncherStateTransitionAnimation {
+ /**
+ * animation used for all apps and widget tray when
+ *{@link FeatureFlags#LAUNCHER3_ALL_APPS_PULL_UP} is {@code false}
+ */
+ public static final int CIRCULAR_REVEAL = 0;
+ /**
+ * animation used for all apps and not widget tray when
+ *{@link FeatureFlags#LAUNCHER3_ALL_APPS_PULL_UP} is {@code true}
+ */
+ public static final int PULLUP = 1;
+
private static final float FINAL_REVEAL_ALPHA_FOR_WIDGETS = 0.3f;
/**
@@ -113,9 +126,11 @@ public class LauncherStateTransitionAnimation {
@Thunk Launcher mLauncher;
@Thunk AnimatorSet mCurrentAnimation;
+ AllAppsTransitionController mAllAppsController;
- public LauncherStateTransitionAnimation(Launcher l) {
+ public LauncherStateTransitionAnimation(Launcher l, AllAppsTransitionController allAppsController) {
mLauncher = l;
+ mAllAppsController = allAppsController;
}
/**
@@ -154,9 +169,13 @@ public class LauncherStateTransitionAnimation {
}
}
};
+ int animType = CIRCULAR_REVEAL;
+ if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
+ animType = PULLUP;
+ }
// Only animate the search bar if animating from spring loaded mode back to all apps
mCurrentAnimation = startAnimationToOverlay(fromWorkspaceState,
- Workspace.State.NORMAL_HIDDEN, buttonView, toView, animated, cb);
+ Workspace.State.NORMAL_HIDDEN, buttonView, toView, animated, animType, cb);
}
/**
@@ -167,7 +186,7 @@ public class LauncherStateTransitionAnimation {
final WidgetsContainerView toView = mLauncher.getWidgetsView();
final View buttonView = mLauncher.getWidgetsButton();
mCurrentAnimation = startAnimationToOverlay(fromWorkspaceState,
- Workspace.State.OVERVIEW_HIDDEN, buttonView, toView, animated,
+ Workspace.State.OVERVIEW_HIDDEN, buttonView, toView, animated, CIRCULAR_REVEAL,
new PrivateTransitionCallbacks(FINAL_REVEAL_ALPHA_FOR_WIDGETS){
@Override
void onTransitionComplete() {
@@ -189,8 +208,12 @@ public class LauncherStateTransitionAnimation {
}
if (fromState == Launcher.State.APPS || fromState == Launcher.State.APPS_SPRING_LOADED) {
+ int animType = CIRCULAR_REVEAL;
+ if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
+ animType = PULLUP;
+ }
startAnimationToWorkspaceFromAllApps(fromWorkspaceState, toWorkspaceState,
- animated, onCompleteRunnable);
+ animated, animType, onCompleteRunnable);
} else if (fromState == Launcher.State.WIDGETS ||
fromState == Launcher.State.WIDGETS_SPRING_LOADED) {
startAnimationToWorkspaceFromWidgets(fromWorkspaceState, toWorkspaceState,
@@ -208,7 +231,7 @@ public class LauncherStateTransitionAnimation {
private AnimatorSet startAnimationToOverlay(
final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState,
final View buttonView, final BaseContainerView toView,
- final boolean animated, final PrivateTransitionCallbacks pCb) {
+ final boolean animated, int animType, final PrivateTransitionCallbacks pCb) {
final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
final Resources res = mLauncher.getResources();
final boolean material = Utilities.ATLEAST_LOLLIPOP;
@@ -225,12 +248,35 @@ public class LauncherStateTransitionAnimation {
// Cancel the current animation
cancelAnimation();
- playCommonTransitionAnimations(toWorkspaceState, fromView, toView,
- animated, initialized, animation, revealDuration, layerViews);
-
+ if (!FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
+ playCommonTransitionAnimations(toWorkspaceState, fromView, toView,
+ animated, initialized, animation, revealDuration, layerViews);
+ }
final View contentView = toView.getContentView();
- if (animated && initialized) {
+ if (!animated || !initialized) {
+ toView.setTranslationX(0.0f);
+ toView.setTranslationY(0.0f);
+ toView.setScaleX(1.0f);
+ toView.setScaleY(1.0f);
+ toView.setAlpha(1.0f);
+ toView.setVisibility(View.VISIBLE);
+ toView.bringToFront();
+
+ // Show the content view
+ contentView.setVisibility(View.VISIBLE);
+
+ dispatchOnLauncherTransitionPrepare(fromView, animated, false);
+ dispatchOnLauncherTransitionStart(fromView, animated, false);
+ dispatchOnLauncherTransitionEnd(fromView, animated, false);
+ dispatchOnLauncherTransitionPrepare(toView, animated, false);
+ dispatchOnLauncherTransitionStart(toView, animated, false);
+ dispatchOnLauncherTransitionEnd(toView, animated, false);
+ pCb.onTransitionComplete();
+
+ return null;
+ }
+ if (animType == CIRCULAR_REVEAL) {
// Setup the reveal view animation
final View revealView = toView.getRevealView();
@@ -366,27 +412,40 @@ public class LauncherStateTransitionAnimation {
toView.post(startAnimRunnable);
return animation;
- } else {
- toView.setTranslationX(0.0f);
- toView.setTranslationY(0.0f);
- toView.setScaleX(1.0f);
- toView.setScaleY(1.0f);
- toView.setVisibility(View.VISIBLE);
- toView.bringToFront();
+ } else if (animType == PULLUP) {
+ animation.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ dispatchOnLauncherTransitionEnd(fromView, animated, false);
+ dispatchOnLauncherTransitionEnd(toView, animated, false);
+ cleanupAnimation();
+ pCb.onTransitionComplete();
+ }
- // Show the content view
- contentView.setVisibility(View.VISIBLE);
+ });
+ mAllAppsController.animateToAllApps(animation);
dispatchOnLauncherTransitionPrepare(fromView, animated, false);
- dispatchOnLauncherTransitionStart(fromView, animated, false);
- dispatchOnLauncherTransitionEnd(fromView, animated, false);
dispatchOnLauncherTransitionPrepare(toView, animated, false);
- dispatchOnLauncherTransitionStart(toView, animated, false);
- dispatchOnLauncherTransitionEnd(toView, animated, false);
- pCb.onTransitionComplete();
- return null;
+ final AnimatorSet stateAnimation = animation;
+ final Runnable startAnimRunnable = new Runnable() {
+ public void run() {
+ // Check that mCurrentAnimation hasn't changed while
+ // we waited for a layout/draw pass
+ if (mCurrentAnimation != stateAnimation)
+ return;
+ dispatchOnLauncherTransitionStart(fromView, animated, false);
+ dispatchOnLauncherTransitionStart(toView, animated, false);
+
+ toView.requestFocus();
+ stateAnimation.start();
+ }
+ };
+ toView.post(startAnimRunnable);
+ return animation;
}
+ return null;
}
/**
@@ -439,7 +498,7 @@ public class LauncherStateTransitionAnimation {
* Starts an animation to the workspace from the apps view.
*/
private void startAnimationToWorkspaceFromAllApps(final Workspace.State fromWorkspaceState,
- final Workspace.State toWorkspaceState, final boolean animated,
+ final Workspace.State toWorkspaceState, final boolean animated, int type,
final Runnable onCompleteRunnable) {
AllAppsContainerView appsView = mLauncher.getAppsView();
// No alpha anim from all apps
@@ -476,7 +535,7 @@ public class LauncherStateTransitionAnimation {
// Only animate the search bar if animating to spring loaded mode from all apps
mCurrentAnimation = startAnimationToWorkspaceFromOverlay(fromWorkspaceState, toWorkspaceState,
mLauncher.getAllAppsButton(), appsView,
- animated, onCompleteRunnable, cb);
+ animated, type, onCompleteRunnable, cb);
}
/**
@@ -506,7 +565,7 @@ public class LauncherStateTransitionAnimation {
mCurrentAnimation = startAnimationToWorkspaceFromOverlay(
fromWorkspaceState, toWorkspaceState,
mLauncher.getWidgetsButton(), widgetsView,
- animated, onCompleteRunnable, cb);
+ animated, CIRCULAR_REVEAL, onCompleteRunnable, cb);
}
/**
@@ -598,7 +657,7 @@ public class LauncherStateTransitionAnimation {
private AnimatorSet startAnimationToWorkspaceFromOverlay(
final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState,
final View buttonView, final BaseContainerView fromView,
- final boolean animated, final Runnable onCompleteRunnable,
+ final boolean animated, int animType, final Runnable onCompleteRunnable,
final PrivateTransitionCallbacks pCb) {
final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
final Resources res = mLauncher.getResources();
@@ -619,10 +678,29 @@ public class LauncherStateTransitionAnimation {
boolean multiplePagesVisible = toWorkspaceState.hasMultipleVisiblePages;
- playCommonTransitionAnimations(toWorkspaceState, fromView, toView,
- animated, initialized, animation, revealDuration, layerViews);
+ if (!FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
+ playCommonTransitionAnimations(toWorkspaceState, fromView, toView,
+ animated, initialized, animation, revealDuration, layerViews);
+ }
+ if (!animated || !initialized) {
+ mAllAppsController.finishPullDown();
+ fromView.setVisibility(View.GONE);
+ dispatchOnLauncherTransitionPrepare(fromView, animated, multiplePagesVisible);
+ dispatchOnLauncherTransitionStart(fromView, animated, true);
+ dispatchOnLauncherTransitionEnd(fromView, animated, true);
+ dispatchOnLauncherTransitionPrepare(toView, animated, multiplePagesVisible);
+ dispatchOnLauncherTransitionStart(toView, animated, true);
+ dispatchOnLauncherTransitionEnd(toView, animated, true);
+ pCb.onTransitionComplete();
- if (animated && initialized) {
+ // Run any queued runnables
+ if (onCompleteRunnable != null) {
+ onCompleteRunnable.run();
+ }
+
+ return null;
+ }
+ if (animType == CIRCULAR_REVEAL) {
final View revealView = fromView.getRevealView();
final View contentView = fromView.getContentView();
@@ -791,23 +869,59 @@ public class LauncherStateTransitionAnimation {
fromView.post(startAnimRunnable);
return animation;
- } else /* if (!(animated && initialized)) */ {
- fromView.setVisibility(View.GONE);
- dispatchOnLauncherTransitionPrepare(fromView, animated, multiplePagesVisible);
- dispatchOnLauncherTransitionStart(fromView, animated, true);
- dispatchOnLauncherTransitionEnd(fromView, animated, true);
- dispatchOnLauncherTransitionPrepare(toView, animated, multiplePagesVisible);
- dispatchOnLauncherTransitionStart(toView, animated, true);
- dispatchOnLauncherTransitionEnd(toView, animated, true);
- pCb.onTransitionComplete();
+ } else if (animType == PULLUP) {
+ animation.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ dispatchOnLauncherTransitionEnd(fromView, animated, false);
+ dispatchOnLauncherTransitionEnd(toView, animated, false);
+ cleanupAnimation();
+ pCb.onTransitionComplete();
+ }
- // Run any queued runnables
- if (onCompleteRunnable != null) {
- onCompleteRunnable.run();
- }
+ });
+ mAllAppsController.animateToWorkspace(animation);
- return null;
+ // Dispatch the prepare transition signal
+ dispatchOnLauncherTransitionPrepare(fromView, animated, false);
+ dispatchOnLauncherTransitionPrepare(toView, animated, false);
+
+ animation.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ dispatchOnLauncherTransitionEnd(fromView, animated, true);
+ dispatchOnLauncherTransitionEnd(toView, animated, true);
+
+ // Run any queued runnables
+ if (onCompleteRunnable != null) {
+ onCompleteRunnable.run();
+ }
+
+ // This can hold unnecessary references to views.
+ cleanupAnimation();
+ pCb.onTransitionComplete();
+ }
+ });
+
+ final AnimatorSet stateAnimation = animation;
+ final Runnable startAnimRunnable = new Runnable() {
+ public void run() {
+ // Check that mCurrentAnimation hasn't changed while
+ // we waited for a layout/draw pass
+ if (mCurrentAnimation != stateAnimation)
+ return;
+ dispatchOnLauncherTransitionStart(fromView, animated, false);
+ dispatchOnLauncherTransitionStart(toView, animated, false);
+
+ // Focus the new view
+ toView.requestFocus();
+ stateAnimation.start();
+ }
+ };
+ fromView.post(startAnimRunnable);
+ return animation;
}
+ return null;
}
/**
diff --git a/src/com/android/launcher3/PinchToOverviewListener.java b/src/com/android/launcher3/PinchToOverviewListener.java
index 0c8568e5e..6ee96fc79 100644
--- a/src/com/android/launcher3/PinchToOverviewListener.java
+++ b/src/com/android/launcher3/PinchToOverviewListener.java
@@ -21,6 +21,8 @@ import android.content.Context;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
+import com.android.launcher3.util.TouchController;
+
/**
* Detects pinches and animates the Workspace to/from overview mode.
*
@@ -30,7 +32,8 @@ import android.view.ScaleGestureDetector;
* @see PinchThresholdManager
* @see PinchAnimationManager
*/
-public class PinchToOverviewListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
+public class PinchToOverviewListener extends ScaleGestureDetector.SimpleOnScaleGestureListener
+ implements TouchController {
private static final float OVERVIEW_PROGRESS = 0f;
private static final float WORKSPACE_PROGRESS = 1f;
/**
@@ -63,15 +66,16 @@ public class PinchToOverviewListener extends ScaleGestureDetector.SimpleOnScaleG
return mPinchStarted;
}
- public void onTouchEvent(MotionEvent ev) {
+ public boolean onTouchEvent(MotionEvent ev) {
if (mPinchStarted) {
if (ev.getPointerCount() > 2) {
// Using more than two fingers causes weird behavior, so just cancel the pinch.
cancelPinch(mPreviousProgress, -1);
} else {
- mPinchDetector.onTouchEvent(ev);
+ return mPinchDetector.onTouchEvent(ev);
}
}
+ return false;
}
@Override
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index c9bd02c22..95ab286d9 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -20,16 +20,20 @@ import android.content.Context;
import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.Rect;
+import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.Selection;
import android.text.SpannableStringBuilder;
import android.text.method.TextKeyListener;
import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
+import android.widget.LinearLayout;
import com.android.launcher3.AppInfo;
import com.android.launcher3.BaseContainerView;
@@ -40,6 +44,7 @@ import com.android.launcher3.DeviceProfile;
import com.android.launcher3.DragSource;
import com.android.launcher3.DropTarget;
import com.android.launcher3.ExtendedEditText;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.folder.Folder;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
@@ -178,9 +183,13 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
mApps.setAdapter(mAdapter);
mLayoutManager = mAdapter.getLayoutManager();
mItemDecoration = mAdapter.getItemDecoration();
- mRecyclerViewTopBottomPadding =
- res.getDimensionPixelSize(R.dimen.all_apps_list_top_bottom_padding);
-
+ if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
+ mRecyclerViewTopBottomPadding = 0;
+ setPadding(0, 0, 0, 0);
+ } else {
+ mRecyclerViewTopBottomPadding =
+ res.getDimensionPixelSize(R.dimen.all_apps_list_top_bottom_padding);
+ }
mSearchQueryBuilder = new SpannableStringBuilder();
Selection.setSelection(mSearchQueryBuilder, 0);
}
@@ -220,6 +229,13 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
mApps.removeApps(apps);
}
+ public void setSearchBarVisible(boolean visible) {
+ if (visible) {
+ mSearchBarController.setVisibility(View.VISIBLE);
+ } else {
+ mSearchBarController.setVisibility(View.INVISIBLE);
+ }
+ }
/**
* Sets the search bar that shows above the a-z list.
*/
@@ -239,6 +255,10 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
mAppsRecyclerView.scrollToTop();
}
+ public boolean isScrollAtTop() {
+ return ((LinearLayoutManager) mAppsRecyclerView.getLayoutManager())
+ .findFirstVisibleItemPosition() == 1;
+ }
/**
* Focuses the search field and begins an app search.
*/
@@ -321,13 +341,33 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
MeasureSpec.getSize(widthMeasureSpec) - mHorizontalPadding,
MeasureSpec.getSize(heightMeasureSpec));
+ DeviceProfile grid = mLauncher.getDeviceProfile();
+ int availableWidth = (!mContentBounds.isEmpty() ? mContentBounds.width() :
+ MeasureSpec.getSize(widthMeasureSpec))
+ - 2 * mAppsRecyclerView.getMaxScrollbarWidth();
+ if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
+ if (mNumAppsPerRow != grid.inv.numColumns ||
+ mNumPredictedAppsPerRow != grid.inv.numColumns) {
+ mNumAppsPerRow = grid.inv.numColumns;
+ mNumPredictedAppsPerRow = grid.inv.numColumns;
+
+ mAppsRecyclerView.setNumAppsPerRow(grid, mNumAppsPerRow);
+ mAdapter.setNumAppsPerRow(mNumAppsPerRow);
+ mApps.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow, new FullMergeAlgorithm());
+ if (mNumAppsPerRow > 0) {
+ int iconSize = availableWidth / mNumAppsPerRow;
+ int iconSpacing = (iconSize - grid.allAppsIconSizePx) / 2;
+ }
+ }
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ return;
+ }
+
+ // --- remove START when {@code FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP} is enabled. ---
+
// Update the number of items in the grid before we measure the view
// TODO: mSectionNamesMargin is currently 0, but also account for it,
// if it's enabled in the future.
- int availableWidth = (!mContentBounds.isEmpty() ? mContentBounds.width() :
- MeasureSpec.getSize(widthMeasureSpec))
- - 2 * mAppsRecyclerView.getMaxScrollbarWidth();
- DeviceProfile grid = mLauncher.getDeviceProfile();
grid.updateAppsViewNumCols(getResources(), availableWidth);
if (mNumAppsPerRow != grid.allAppsNumCols ||
mNumPredictedAppsPerRow != grid.allAppsNumPredictiveCols) {
@@ -346,6 +386,8 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
mAdapter.setNumAppsPerRow(mNumAppsPerRow);
mApps.setNumAppsPerRow(mNumAppsPerRow, mNumPredictedAppsPerRow, mergeAlgorithm);
+ // TODO: should we not do all this complicated computation but just match the
+ // numAppsPerRow with the workspace?
if (mNumAppsPerRow > 0) {
int iconSize = availableWidth / mNumAppsPerRow;
int iconSpacing = (iconSize - grid.allAppsIconSizePx) / 2;
@@ -353,6 +395,7 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
}
}
+ // --- remove END when {@code FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP} is enabled. ---
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@@ -385,6 +428,24 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
MarginLayoutParams lp = (MarginLayoutParams) mSearchContainer.getLayoutParams();
lp.leftMargin = bgPadding.left;
lp.rightMargin = bgPadding.right;
+
+ if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
+ MarginLayoutParams mlp = (MarginLayoutParams) mAppsRecyclerView.getLayoutParams();
+
+ int navBarHeight = 84; /* replace with mInset.height() in dragLayer */
+ DeviceProfile grid = mLauncher.getDeviceProfile();
+ int height = navBarHeight + grid.hotseatCellHeightPx;
+
+ mlp.topMargin = height;
+ mAppsRecyclerView.setLayoutParams(mlp);
+
+ LinearLayout.LayoutParams llp =
+ (LinearLayout.LayoutParams) mSearchInput.getLayoutParams();
+ llp.topMargin = navBarHeight;
+ mSearchInput.setLayoutParams(llp);
+
+ lp.height = height;
+ }
mSearchContainer.setLayoutParams(lp);
}
@@ -437,6 +498,7 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
// Return early if this is not initiated from a touch
if (!v.isInTouchMode()) return false;
// When we have exited all apps or are in transition, disregard long clicks
+
if (!mLauncher.isAppsViewVisible() ||
mLauncher.getWorkspace().isSwitchingState()) return false;
// Return if global dragging is not enabled
diff --git a/src/com/android/launcher3/allapps/AllAppsSearchBarController.java b/src/com/android/launcher3/allapps/AllAppsSearchBarController.java
index a4bea8dbd..ac3593238 100644
--- a/src/com/android/launcher3/allapps/AllAppsSearchBarController.java
+++ b/src/com/android/launcher3/allapps/AllAppsSearchBarController.java
@@ -49,6 +49,9 @@ public abstract class AllAppsSearchBarController
protected DefaultAppSearchAlgorithm mSearchAlgorithm;
protected InputMethodManager mInputMethodManager;
+ public void setVisibility(int visibility) {
+ mInput.setVisibility(visibility);
+ }
/**
* Sets the references to the apps model and the search result callback.
*/
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
new file mode 100644
index 000000000..1428c2f32
--- /dev/null
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -0,0 +1,347 @@
+package com.android.launcher3.allapps;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.graphics.drawable.Drawable;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+
+import com.android.launcher3.Hotseat;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAnimUtils;
+import com.android.launcher3.util.TouchController;
+
+/**
+ * Handles AllApps view transition.
+ * 1) Slides all apps view using direct manipulation
+ * 2) When finger is released, animate to either top or bottom accordingly.
+ *
+ * Algorithm:
+ * If release velocity > THRES1, snap according to the direction of movement.
+ * If release velocity < THRES1, snap according to either top or bottom depending on whether it's
+ * closer to top or closer to the page indicator.
+ */
+public class AllAppsTransitionController implements TouchController, VerticalPullDetector.Listener {
+
+ private static final String TAG = "AllAppsTrans";
+ private static final boolean DBG = false;
+
+ private static final float ANIMATION_DURATION = 500;
+
+ private AllAppsContainerView mAppsView;
+ private Hotseat mHotseat;
+ private Drawable mHotseatBackground;
+ private float mHotseatAlpha;
+ private View mWorkspaceCurPage;
+
+ private final Launcher mLauncher;
+ private final VerticalPullDetector mDetector;
+
+ // Animation in this class is controlled by a single variable {@link mProgressTransY}.
+ // Visually, it represents top y coordinate of the all apps container. Using the
+ // {@link mTranslation} as the denominator, this fraction value ranges in [0, 1].
+ private float mProgressTransY; // numerator
+ private float mTranslation = -1; // denominator
+
+ private float mAnimationDuration;
+ private float mCurY;
+
+ private AnimatorSet mCurrentAnimation;
+
+ public AllAppsTransitionController(Launcher launcher) {
+ mLauncher = launcher;
+ mDetector = new VerticalPullDetector(launcher);
+ mDetector.setListener(this);
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ init();
+ if (mLauncher.getWorkspace().isInOverviewMode() ||
+ mLauncher.isWidgetsViewVisible()) {
+ return false;
+ }
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ mDetector.setAllAppsState(mLauncher.isAllAppsVisible(), mAppsView.isScrollAtTop());
+ }
+ mDetector.onTouchEvent(ev);
+ return mDetector.mScrolling;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ return mDetector.onTouchEvent(ev);
+ }
+
+ private void init() {
+ if (mAppsView != null) {
+ return;
+ }
+ mAppsView = mLauncher.getAppsView();
+ mHotseat = mLauncher.getHotseat();
+ mWorkspaceCurPage = mLauncher.getWorkspace().getChildAt(
+ mLauncher.getWorkspace().getCurrentPage());
+
+ if (mHotseatBackground == null) {
+ mHotseatBackground = mHotseat.getBackground();
+ mHotseatAlpha = mHotseatBackground.getAlpha() / 255f;
+ }
+ }
+
+ @Override
+ public void onScrollStart(boolean start) {
+ cancelAnimation();
+ mCurrentAnimation = LauncherAnimUtils.createAnimatorSet();
+ mCurY = mAppsView.getTranslationY();
+ preparePull(start);
+
+ }
+
+ /**
+ * @param start {@code true} if start of new drag.
+ */
+ public void preparePull(boolean start) {
+ if (start) {
+ if (!mLauncher.isAllAppsVisible()) {
+ mHotseat.setBackground(null);
+ mAppsView.setVisibility(View.VISIBLE);
+ mAppsView.getContentView().setVisibility(View.VISIBLE);
+ mAppsView.setAlpha(mHotseatAlpha);
+ mAppsView.setSearchBarVisible(false);
+
+ if (mTranslation < 0) {
+ mTranslation = mHotseat.getTop();
+ setProgress(mTranslation);
+ }
+ } else {
+ mLauncher.getWorkspace().setVisibility(View.VISIBLE);
+ mLauncher.getWorkspace().setAlpha(1f);
+ mLauncher.getWorkspace().onLauncherTransitionPrepare(mLauncher, false, false);
+ mWorkspaceCurPage.setVisibility(View.VISIBLE);
+ mAppsView.setSearchBarVisible(false);
+ setLightStatusBar(false);
+ }
+ }
+ mHotseat.setVisibility(View.VISIBLE);
+ mHotseat.bringToFront();
+ }
+
+ private void setLightStatusBar(boolean enable) {
+ int systemUiFlags = mLauncher.getWindow().getDecorView().getSystemUiVisibility();
+ if (enable) {
+ mLauncher.getWindow().getDecorView().setSystemUiVisibility(systemUiFlags
+ | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
+
+ } else {
+ mLauncher.getWindow().getDecorView().setSystemUiVisibility(systemUiFlags
+ & ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
+
+ }
+ }
+
+ private final Interpolator mAlphaInterpolator = new DecelerateInterpolator(.5f);
+
+ @Override
+ public boolean onScroll(float displacement, float velocity) {
+ if (mAppsView == null) {
+ return false; // early termination.
+ }
+ if (0 <= mCurY + displacement && mCurY + displacement < mTranslation) {
+ setProgress(mCurY + displacement);
+ }
+ return true;
+ }
+
+ /**
+ * @param progress y value of the border between hotseat and all apps
+ */
+ public void setProgress(float progress) {
+ mProgressTransY = progress;
+ float alpha = calcAlphaAllApps(progress);
+ float workspaceHotseatAlpha = Math.max(mHotseatAlpha, 1 - alpha);
+ setTransAndAlpha(mAppsView, progress, Math.max(mHotseatAlpha, alpha));
+ setTransAndAlpha(mWorkspaceCurPage, -mTranslation + progress, workspaceHotseatAlpha);
+ setTransAndAlpha(mHotseat, -mTranslation + progress, workspaceHotseatAlpha);
+ }
+
+ public float getProgress() {
+ return mProgressTransY;
+ }
+
+ private float calcAlphaAllApps(float progress) {
+ return mAlphaInterpolator.getInterpolation((mTranslation - progress)/mTranslation);
+ }
+
+ private void setTransAndAlpha(View v, float transY, float alpha) {
+ if (v != null) {
+ v.setTranslationY(transY);
+ v.setAlpha(alpha);
+ }
+ }
+
+ @Override
+ public void onScrollEnd(float velocity, boolean fling) {
+ if (mAppsView == null) {
+ return; // early termination.
+ }
+
+ if (fling) {
+ if (velocity < 0) {
+ calculateDuration(velocity, mAppsView.getTranslationY());
+ showAppsView(); // Flinging in UP direction
+ } else {
+ calculateDuration(velocity, Math.abs(mTranslation - mAppsView.getTranslationY()));
+ showWorkspace(); // Flinging in DOWN direction
+ }
+ // snap to top or bottom using the release velocity
+ } else {
+ if (mAppsView.getTranslationY() > mTranslation / 2) {
+ calculateDuration(velocity, Math.abs(mTranslation - mAppsView.getTranslationY()));
+ showWorkspace(); // Released in the bottom half
+ } else {
+ calculateDuration(velocity, Math.abs(mAppsView.getTranslationY()));
+ showAppsView(); // Released in the top half
+ }
+ }
+ }
+
+ private void calculateDuration(float velocity, float disp) {
+ // TODO: make these values constants after tuning.
+ float velocityDivisor = Math.max(1, 0.75f * velocity);
+ float travelDistance = Math.max(0.2f, disp / mTranslation);
+ mAnimationDuration = Math.max(100, ANIMATION_DURATION / velocityDivisor * travelDistance);
+ if (true) { // MERONG
+ Log.d(TAG, String.format("calculateDuration=%f, v=%f, d=%f", mAnimationDuration, velocity, disp));
+ }
+ }
+
+ /**
+ * Depending on the current state of the launcher, either just
+ * 1) animate
+ * 2) animate and do all the state updates.
+ */
+ private void showAppsView() {
+ if (mLauncher.isAllAppsVisible()) {
+ animateToAllApps(mCurrentAnimation);
+ mCurrentAnimation.start();
+ } else {
+ mLauncher.showAppsView(true /* animated */, true /* resetListToTop */,
+ true /* updatePredictedApps */, false /* focusSearchBar */);
+ }
+ }
+
+ /**
+ * Depending on the current state of the launcher, either just
+ * 1) animate
+ * 2) animate and do all the state updates.
+ */
+ private void showWorkspace() {
+ if (mLauncher.isAllAppsVisible()) {
+ mLauncher.showWorkspace(true /* animated */);
+ } else {
+ animateToWorkspace(mCurrentAnimation);
+ mCurrentAnimation.start();
+ }
+ }
+
+ public void animateToAllApps(AnimatorSet animationOut) {
+ if ((mAppsView = mLauncher.getAppsView()) == null || animationOut == null){
+ return;
+ }
+ if (!mDetector.mScrolling) {
+ preparePull(true);
+ }
+ mCurY = mAppsView.getTranslationY();
+ final float fromAllAppsTop = mAppsView.getTranslationY();
+ final float toAllAppsTop = 0;
+
+ ObjectAnimator driftAndAlpha = ObjectAnimator.ofFloat(this, "progress",
+ fromAllAppsTop, toAllAppsTop);
+ driftAndAlpha.setDuration((long) mAnimationDuration);
+ animationOut.play(driftAndAlpha);
+
+ animationOut.addListener(new AnimatorListenerAdapter() {
+ boolean canceled = false;
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ canceled = true;
+ }
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (canceled) {
+ return;
+ } else {
+ finishPullUp();
+ cleanUpAnimation();
+ mDetector.finishedScrolling();
+ }
+ }});
+ mCurrentAnimation = animationOut;
+ }
+
+ private void finishPullUp() {
+ mAppsView.setSearchBarVisible(true);
+ mHotseat.setVisibility(View.INVISIBLE);
+ setProgress(0f);
+ setLightStatusBar(true);
+ }
+
+ public void animateToWorkspace(AnimatorSet animationOut) {
+ if ((mAppsView = mLauncher.getAppsView()) == null || animationOut == null){
+ return;
+ }
+ if(!mDetector.mScrolling) {
+ preparePull(true);
+ }
+ final float fromAllAppsTop = mAppsView.getTranslationY();
+ final float toAllAppsTop = mTranslation;
+
+ ObjectAnimator driftAndAlpha = ObjectAnimator.ofFloat(this, "progress",
+ fromAllAppsTop, toAllAppsTop);
+ driftAndAlpha.setDuration((long) mAnimationDuration);
+ animationOut.play(driftAndAlpha);
+
+ animationOut.addListener(new AnimatorListenerAdapter() {
+ boolean canceled = false;
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ canceled = true;
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (canceled) {
+ return;
+ } else {
+ finishPullDown();
+ cleanUpAnimation();
+ mDetector.finishedScrolling();
+ }
+ }});
+ mCurrentAnimation = animationOut;
+ }
+
+ public void finishPullDown() {
+ mAppsView.setVisibility(View.INVISIBLE);
+ mHotseat.setBackground(mHotseatBackground);
+ mHotseat.setVisibility(View.VISIBLE);
+ setProgress(mTranslation);
+ setLightStatusBar(false);
+ }
+
+ private void cancelAnimation() {
+ if (mCurrentAnimation != null) {
+ mCurrentAnimation.cancel();
+ mCurrentAnimation = null;
+ }
+ }
+
+ private void cleanUpAnimation() {
+ mCurrentAnimation = null;
+ }
+}
diff --git a/src/com/android/launcher3/allapps/VerticalPullDetector.java b/src/com/android/launcher3/allapps/VerticalPullDetector.java
new file mode 100644
index 000000000..4cc921c33
--- /dev/null
+++ b/src/com/android/launcher3/allapps/VerticalPullDetector.java
@@ -0,0 +1,196 @@
+package com.android.launcher3.allapps;
+
+import android.content.Context;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+
+/**
+ * One dimensional scroll gesture detector for all apps container pull up interaction.
+ */
+public class VerticalPullDetector {
+
+ private static final String TAG = "ScrollGesture";
+ private static final boolean DBG = false;
+
+ private float mTouchSlop;
+
+ private boolean mAllAppsVisible;
+ private boolean mAllAppsScrollAtTop;
+
+ /**
+ * The minimum release velocity in pixels per millisecond that triggers fling..
+ */
+ private static final float RELEASE_VELOCITY_PX_MS = 1.7f;
+
+ /**
+ * The time constant used to calculate dampening in the low-pass filter of scroll velocity.
+ * Cutoff frequency is set at 10 Hz.
+ */
+ public static final float SCROLL_VELOCITY_DAMPENING_RC = 1000f / (2f * (float) Math.PI * 10);
+
+ /* Scroll state, this is set to true during dragging and animation. */
+ boolean mScrolling;
+
+
+ float mDownY;
+ float mDownMillis;
+
+ float mLastY;
+ float mLastMillis;
+
+ float mVelocity;
+ float mLastDisplacement;
+ float mDisplacement;
+
+ /* scroll started during previous animation */
+ boolean mSubtractSlop = true;
+
+ /* Client of this gesture detector can register a callback. */
+ Listener mListener;
+
+ public void setListener(Listener l) {
+ mListener = l;
+ }
+
+ interface Listener{
+ /**
+ * @param start when should
+ */
+ void onScrollStart(boolean start);
+ boolean onScroll(float displacement, float velocity);
+ void onScrollEnd(float velocity, boolean fling);
+ }
+
+ public VerticalPullDetector(Context context) {
+ mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+ }
+
+ public void setAllAppsState(boolean allAppsVisible, boolean scrollAtTop) {
+ mAllAppsVisible = allAppsVisible;
+ mAllAppsScrollAtTop = scrollAtTop;
+ }
+
+ private boolean shouldScrollStart() {
+ if (mAllAppsVisible && mDisplacement > mTouchSlop && mAllAppsScrollAtTop) {
+ return true;
+ }
+ if (!mAllAppsVisible && mDisplacement < -mTouchSlop) {
+ return true;
+ }
+ return false;
+ }
+
+ public boolean onTouchEvent(MotionEvent ev) {
+ switch (ev.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ mDownMillis = ev.getDownTime();
+ mDownY = ev.getY();
+ mLastDisplacement = 0;
+ mVelocity = 0;
+
+ if (mScrolling) {
+ reportScrollStart(true /* recatch */);
+ }
+ break;
+ case MotionEvent.ACTION_MOVE:
+ mDisplacement = computeDisplacement(ev);
+ mVelocity = computeVelocity(ev, mVelocity);
+
+ if (!mScrolling && shouldScrollStart()) {
+ mScrolling = true;
+ reportScrollStart(false /* recatch */);
+ }
+ if (mScrolling && mListener != null) {
+ reportScroll();
+ }
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ // These are synthetic events and there is no need to update internal values.
+ if (mScrolling && mListener != null) {
+ reportScrollEnd();
+ }
+ break;
+ default:
+ //TODO: add multi finger tracking by tracking active pointer.
+ break;
+ }
+ // Do house keeping.
+ mLastDisplacement = mDisplacement;
+
+ mLastY = ev.getY();
+ mLastMillis = ev.getEventTime();
+
+ return true;
+ }
+
+ public void finishedScrolling() {
+ mScrolling = false;
+ }
+
+ private boolean reportScrollStart(boolean recatch) {
+ mListener.onScrollStart(!recatch);
+ if (DBG) {
+ Log.d(TAG, "onScrollStart recatch:" + recatch);
+ }
+ return true;
+ }
+
+ private boolean reportScroll() {
+ float delta = mDisplacement - mLastDisplacement;
+ if (delta != 0) {
+ if (DBG) {
+ Log.d(TAG, String.format("onScroll disp=%.1f, velocity=%.1f",
+ mDisplacement, mVelocity));
+ }
+ return mListener.onScroll(mDisplacement - (mSubtractSlop? mTouchSlop : 0), mVelocity);
+ }
+ return true;
+ }
+
+ private void reportScrollEnd() {
+ if (DBG) {
+ Log.d(TAG, String.format("onScrolEnd disp=%.1f, velocity=%.1f",
+ mDisplacement, mVelocity));
+ }
+ mListener.onScrollEnd(mVelocity, Math.abs(mVelocity) > RELEASE_VELOCITY_PX_MS);
+ }
+ /**
+ * Computes the damped velocity using the two motion events and the previous velocity.
+ */
+ private float computeVelocity(MotionEvent to, float previousVelocity) {
+ float delta = computeDelta(to);
+
+ float deltaTimeMillis = to.getEventTime() - mLastMillis;
+ float velocity = (deltaTimeMillis > 0) ? (delta / deltaTimeMillis) : 0;
+ if (Math.abs(previousVelocity) < 0.001f) {
+ return velocity;
+ }
+
+ float alpha = computeDampeningFactor(deltaTimeMillis);
+ return interpolate(previousVelocity, velocity, alpha);
+ }
+
+ private float computeDisplacement(MotionEvent to) {
+ return to.getY() - mDownY;
+ }
+
+ private float computeDelta(MotionEvent to) {
+ return to.getY() - mLastY;
+ }
+
+ /**
+ * Returns a time-dependent dampening factor using delta time.
+ */
+ private static float computeDampeningFactor(float deltaTime) {
+ return deltaTime / (SCROLL_VELOCITY_DAMPENING_RC + deltaTime);
+ }
+
+ /**
+ * Returns the linear interpolation between two values
+ */
+ private static float interpolate(float from, float to, float alpha) {
+ return (1.0f - alpha) * from + alpha * to;
+ }
+}
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index f4470f320..af5ff587a 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -46,6 +46,7 @@ import com.android.launcher3.Utilities;
import com.android.launcher3.Workspace;
import com.android.launcher3.accessibility.DragViewStateAnnouncer;
import com.android.launcher3.util.Thunk;
+import com.android.launcher3.util.TouchController;
import java.util.ArrayList;
import java.util.HashSet;
@@ -53,7 +54,7 @@ import java.util.HashSet;
/**
* Class for initiating a drag within a view or across multiple views.
*/
-public class DragController implements DragDriver.EventListener {
+public class DragController implements DragDriver.EventListener, TouchController {
private static final String TAG = "Launcher.DragController";
/** Indicates the drag is a move. */
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index 33ce68313..e4c84360c 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -43,6 +43,7 @@ import android.widget.FrameLayout;
import android.widget.TextView;
import com.android.launcher3.AppWidgetResizeFrame;
+import com.android.launcher3.BaseContainerView;
import com.android.launcher3.CellLayout;
import com.android.launcher3.InsettableFrameLayout;
import com.android.launcher3.ItemInfo;
@@ -56,10 +57,13 @@ import com.android.launcher3.ShortcutAndWidgetContainer;
import com.android.launcher3.Utilities;
import com.android.launcher3.Workspace;
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
+import com.android.launcher3.allapps.AllAppsContainerView;
+import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.folder.Folder;
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.util.Thunk;
+import com.android.launcher3.util.TouchController;
import java.util.ArrayList;
@@ -117,6 +121,11 @@ public class DragLayer extends InsettableFrameLayout {
// Related to pinch-to-go-to-overview gesture.
private PinchToOverviewListener mPinchListener = null;
+
+ // Handles all apps pull up interaction
+ private AllAppsTransitionController mAllAppsController;
+
+ private TouchController mActiveController;
/**
* Used to create a new DragLayer from XML.
*
@@ -138,9 +147,11 @@ public class DragLayer extends InsettableFrameLayout {
mIsRtl = Utilities.isRtl(res);
}
- public void setup(Launcher launcher, DragController controller) {
+ public void setup(Launcher launcher, DragController dragController,
+ AllAppsTransitionController allAppsTransitionController) {
mLauncher = launcher;
- mDragController = controller;
+ mDragController = dragController;
+ mAllAppsController = allAppsTransitionController;
boolean isAccessibilityEnabled = ((AccessibilityManager) mLauncher.getSystemService(
Context.ACCESSIBILITY_SERVICE)).isEnabled();
@@ -246,6 +257,7 @@ public class DragLayer extends InsettableFrameLayout {
public boolean onInterceptTouchEvent(MotionEvent ev) {
int action = ev.getAction();
+
if (action == MotionEvent.ACTION_DOWN) {
if (handleTouchDown(ev, true)) {
return true;
@@ -258,11 +270,21 @@ public class DragLayer extends InsettableFrameLayout {
}
clearAllResizeFrames();
+ if (mDragController.onInterceptTouchEvent(ev)) {
+ mActiveController = mDragController;
+ return true;
+ }
+ if (mAllAppsController.onInterceptTouchEvent(ev)) {
+ mActiveController = mAllAppsController;
+ return true;
+ }
+
if (mPinchListener != null && mPinchListener.onInterceptTouchEvent(ev)) {
// Stop listening for scrolling etc. (onTouchEvent() handles the rest of the pinch.)
+ mActiveController = mPinchListener;
return true;
}
- return mDragController.onInterceptTouchEvent(ev);
+ return false;
}
@Override
@@ -375,11 +397,6 @@ public class DragLayer extends InsettableFrameLayout {
int x = (int) ev.getX();
int y = (int) ev.getY();
- // This is only reached if a pinch was started from onInterceptTouchEvent();
- // this continues sending events for it.
- if (mPinchListener != null) {
- mPinchListener.onTouchEvent(ev);
- }
if (action == MotionEvent.ACTION_DOWN) {
if (handleTouchDown(ev, false)) {
@@ -406,7 +423,10 @@ public class DragLayer extends InsettableFrameLayout {
}
}
if (handled) return true;
- return mDragController.onTouchEvent(ev);
+ if (mActiveController != null) {
+ return mActiveController.onTouchEvent(ev);
+ }
+ return false;
}
@Override
diff --git a/src/com/android/launcher3/util/TouchController.java b/src/com/android/launcher3/util/TouchController.java
new file mode 100644
index 000000000..d1409c8b9
--- /dev/null
+++ b/src/com/android/launcher3/util/TouchController.java
@@ -0,0 +1,8 @@
+package com.android.launcher3.util;
+
+import android.view.MotionEvent;
+
+public interface TouchController {
+ boolean onTouchEvent(MotionEvent ev);
+ boolean onInterceptTouchEvent(MotionEvent ev);
+}