Merge "Using state animation to control all-apps transition" into ub-launcher3-master
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index 661ba11..63c232d 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -39,7 +39,6 @@
protected static final int FLAG_HIDE_HOTSEAT = 1 << 2;
protected static final int FLAG_DISABLE_ACCESSIBILITY = 1 << 3;
protected static final int FLAG_DO_NOT_RESTORE = 1 << 4;
- protected static final int FLAG_HAS_SPRING = 1 << 5;
private static final LauncherState[] sAllStates = new LauncherState[4];
@@ -90,7 +89,6 @@
* @see com.android.launcher3.allapps.AllAppsTransitionController
*/
public final float verticalProgress;
- public final boolean hasVerticalSpring;
public LauncherState(int id, int containerType, int transitionDuration, float verticalProgress,
int flags) {
@@ -106,7 +104,6 @@
this.doNotRestore = (flags & FLAG_DO_NOT_RESTORE) != 0;
this.verticalProgress = verticalProgress;
- this.hasVerticalSpring = (flags & FLAG_HAS_SPRING) != 0;
this.ordinal = id;
sAllStates[id] = this;
diff --git a/src/com/android/launcher3/LauncherStateManager.java b/src/com/android/launcher3/LauncherStateManager.java
index 863cae7..fd94067 100644
--- a/src/com/android/launcher3/LauncherStateManager.java
+++ b/src/com/android/launcher3/LauncherStateManager.java
@@ -28,6 +28,7 @@
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.anim.AnimationLayerSet;
import com.android.launcher3.anim.AnimationSuccessListener;
+import com.android.launcher3.anim.AnimatorPlaybackController;
/**
* TODO: figure out what kind of tests we can write for this
@@ -133,8 +134,7 @@
private void goToState(LauncherState state, boolean animated, long delay,
Runnable onCompleteRunnable) {
- if (mLauncher.isInState(state) && mConfig.mCurrentAnimation == null
- && !mAllAppsController.isTransitioning()) {
+ if (mLauncher.isInState(state) && mConfig.mCurrentAnimation == null) {
// Run any queued runnable
if (onCompleteRunnable != null) {
@@ -159,27 +159,42 @@
return;
}
- AnimatorSet animation = createAnimationToNewWorkspace(state, onCompleteRunnable);
+ // Since state NORMAL can be reached from multiple states, just assume that the
+ // transition plays in reverse and use the same duration as previous state.
+ mConfig.duration = state == NORMAL ? mState.transitionDuration : state.transitionDuration;
+
+ AnimatorSet animation = createAnimationToNewWorkspaceInternal(state, onCompleteRunnable);
Runnable runnable = new StartAnimRunnable(animation, state.getFinalFocus(mLauncher));
if (delay > 0) {
mUiHandler.postDelayed(runnable, delay);
- } else if (mConfig.shouldPost) {
- mUiHandler.post(runnable);
} else {
- runnable.run();
+ mUiHandler.post(runnable);
}
}
- protected AnimatorSet createAnimationToNewWorkspace(final LauncherState state,
- final Runnable onCompleteRunnable) {
+ /**
+ * Creates a {@link AnimatorPlaybackController} that can be used for a controlled
+ * state transition.
+ * @param state the final state for the transition.
+ * @param duration intended duration for normal playback. Use higher duration for better
+ * accuracy.
+ */
+ protected AnimatorPlaybackController createAnimationToNewWorkspace(
+ LauncherState state, long duration) {
mConfig.reset();
- mConfig.duration = state == NORMAL ? mState.transitionDuration : state.transitionDuration;
+ mConfig.userControlled = true;
+ mConfig.duration = duration;
+ return AnimatorPlaybackController.wrap(
+ createAnimationToNewWorkspaceInternal(state, null), duration);
+ }
+
+ protected AnimatorSet createAnimationToNewWorkspaceInternal(final LauncherState state,
+ final Runnable onCompleteRunnable) {
final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
final AnimationLayerSet layerViews = new AnimationLayerSet();
- mAllAppsController.animateToFinalProgress(state.verticalProgress,
- state.hasVerticalSpring, animation, mConfig);
+ mAllAppsController.animateToFinalProgress(state.verticalProgress, animation, mConfig);
mLauncher.getWorkspace().setStateWithAnimation(state,
layerViews, animation, mConfig);
@@ -242,14 +257,14 @@
}
public static class AnimationConfig extends AnimatorListenerAdapter {
- public boolean shouldPost;
public long duration;
+ public boolean userControlled;
private AnimatorSet mCurrentAnimation;
public void reset() {
- shouldPost = true;
duration = 0;
+ userControlled = false;
if (mCurrentAnimation != null) {
mCurrentAnimation.setDuration(0);
diff --git a/src/com/android/launcher3/PinchToOverviewListener.java b/src/com/android/launcher3/PinchToOverviewListener.java
index d1a2538..27edaf6 100644
--- a/src/com/android/launcher3/PinchToOverviewListener.java
+++ b/src/com/android/launcher3/PinchToOverviewListener.java
@@ -22,18 +22,19 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.ScaleGestureDetector.OnScaleGestureListener;
-import com.android.launcher3.compat.AnimatorSetCompat;
+import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.util.TouchController;
/**
* Detects pinches and animates the Workspace to/from overview mode.
*/
-public class PinchToOverviewListener
- implements TouchController, OnScaleGestureListener, Runnable {
+public class PinchToOverviewListener extends AnimatorListenerAdapter
+ implements TouchController, OnScaleGestureListener {
private static final float ACCEPT_THRESHOLD = 0.65f;
/**
@@ -47,7 +48,7 @@
private Workspace mWorkspace = null;
private boolean mPinchStarted = false;
- private AnimatorSetCompat mCurrentAnimation;
+ private AnimatorPlaybackController mCurrentAnimation;
private float mCurrentScale;
private boolean mShouldGoToFinalState;
@@ -100,8 +101,9 @@
}
mToState = mLauncher.isInState(OVERVIEW) ? NORMAL : OVERVIEW;
- mCurrentAnimation = AnimatorSetCompat.wrap(mLauncher.getStateManager()
- .createAnimationToNewWorkspace(mToState, this), OVERVIEW_TRANSITION_MS);
+ mCurrentAnimation = mLauncher.getStateManager()
+ .createAnimationToNewWorkspace(mToState, OVERVIEW_TRANSITION_MS);
+ mCurrentAnimation.getTarget().addListener(this);
mPinchStarted = true;
mCurrentScale = 1;
mShouldGoToFinalState = false;
@@ -111,7 +113,7 @@
}
@Override
- public void run() {
+ public void onAnimationEnd(Animator animation) {
mCurrentAnimation = null;
mPinchStarted = false;
}
@@ -121,9 +123,9 @@
if (mShouldGoToFinalState) {
mCurrentAnimation.start();
} else {
- mCurrentAnimation.addListener(new AnimatorListenerAdapter() {
+ mCurrentAnimation.setEndAction(new Runnable() {
@Override
- public void onAnimationEnd(Animator animation) {
+ public void run() {
mLauncher.getStateManager().goToState(
mToState == OVERVIEW ? NORMAL : OVERVIEW, false);
}
diff --git a/src/com/android/launcher3/VerticalSwipeController.java b/src/com/android/launcher3/VerticalSwipeController.java
new file mode 100644
index 0000000..12c6916
--- /dev/null
+++ b/src/com/android/launcher3/VerticalSwipeController.java
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3;
+
+import static com.android.launcher3.LauncherState.ALL_APPS;
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
+import static com.android.launcher3.anim.SpringAnimationHandler.Y_DIRECTION;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.support.animation.SpringAnimation;
+import android.util.Log;
+import android.view.MotionEvent;
+
+import com.android.launcher3.allapps.AllAppsContainerView;
+import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.anim.SpringAnimationHandler;
+import com.android.launcher3.touch.SwipeDetector;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+import com.android.launcher3.util.TouchController;
+
+import java.util.ArrayList;
+
+/**
+ * Handles vertical touch gesture on the DragLayer
+ */
+public class VerticalSwipeController extends AnimatorListenerAdapter
+ implements TouchController, SwipeDetector.Listener {
+
+ private static final String TAG = "VerticalSwipeController";
+
+ private static final float RECATCH_REJECTION_FRACTION = .0875f;
+ private static final int SINGLE_FRAME_MS = 16;
+
+ // Progress after which the transition is assumed to be a success in case user does not fling
+ private static final float SUCCESS_TRANSITION_PROGRESS = 0.5f;
+
+ private final Launcher mLauncher;
+ private final SwipeDetector mDetector;
+
+ private boolean mNoIntercept;
+ private int mStartContainerType;
+
+ private AnimatorPlaybackController mCurrentAnimation;
+ private LauncherState mToState;
+
+ private float mStartProgress;
+ // Ratio of transition process [0, 1] to drag displacement (px)
+ private float mProgressMultiplier;
+
+ private SpringAnimationHandler[] mSpringHandlers;
+
+ public VerticalSwipeController(Launcher l) {
+ mLauncher = l;
+ mDetector = new SwipeDetector(l, this, SwipeDetector.VERTICAL);
+ }
+
+ private boolean canInterceptTouch(MotionEvent ev) {
+ if (!mLauncher.isInState(NORMAL) && !mLauncher.isInState(ALL_APPS)) {
+ // Don't listen for the pinch gesture if on all apps, widget picker, -1, etc.
+ return false;
+ }
+ if (mCurrentAnimation != null) {
+ // If we are already animating from a previous state, we can intercept.
+ return true;
+ }
+ if (mLauncher.isInState(ALL_APPS) && !mLauncher.getAppsView().shouldContainerScroll(ev)) {
+ return false;
+ }
+ if (AbstractFloatingView.getTopOpenView(mLauncher) != null) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ if (mCurrentAnimation != null && animation == mCurrentAnimation.getTarget()) {
+ Log.e(TAG, "Who dare cancel the animation when I am in control", new Exception());
+ mDetector.finishedScrolling();
+ mCurrentAnimation = null;
+ }
+ }
+
+ private void initSprings() {
+ AllAppsContainerView appsView = mLauncher.getAppsView();
+
+ SpringAnimationHandler handler = appsView.getSpringAnimationHandler();
+ if (handler == null) {
+ mSpringHandlers = new SpringAnimationHandler[0];
+ return;
+ }
+
+ ArrayList<SpringAnimationHandler> handlers = new ArrayList<>();
+ handlers.add(handler);
+
+ SpringAnimation searchSpring = appsView.getSearchUiManager().getSpringForFling();
+ if (searchSpring != null) {
+ SpringAnimationHandler searchHandler =
+ new SpringAnimationHandler(Y_DIRECTION, handler.getFactory());
+ searchHandler.add(searchSpring, true /* setDefaultValues */);
+ handlers.add(searchHandler);
+ }
+
+ mSpringHandlers = handlers.toArray(new SpringAnimationHandler[handlers.size()]);
+ }
+
+ @Override
+ public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ mNoIntercept = !canInterceptTouch(ev);
+ if (mNoIntercept) {
+ return false;
+ }
+
+ // Now figure out which direction scroll events the controller will start
+ // calling the callbacks.
+ final int directionsToDetectScroll;
+ boolean ignoreSlopWhenSettling = false;
+
+ if (mCurrentAnimation != null) {
+ if (mCurrentAnimation.getProgressFraction() > 1 - RECATCH_REJECTION_FRACTION) {
+ directionsToDetectScroll = SwipeDetector.DIRECTION_POSITIVE;
+ } else if (mCurrentAnimation.getProgressFraction() < RECATCH_REJECTION_FRACTION ) {
+ directionsToDetectScroll = SwipeDetector.DIRECTION_NEGATIVE;
+ } else {
+ directionsToDetectScroll = SwipeDetector.DIRECTION_BOTH;
+ ignoreSlopWhenSettling = true;
+ }
+ } else {
+ if (mLauncher.isInState(ALL_APPS)) {
+ directionsToDetectScroll = SwipeDetector.DIRECTION_NEGATIVE;
+ mStartContainerType = ContainerType.ALLAPPS;
+ } else {
+ directionsToDetectScroll = SwipeDetector.DIRECTION_POSITIVE;
+ mStartContainerType = mLauncher.getDragLayer().isEventOverHotseat(ev) ?
+ ContainerType.HOTSEAT : ContainerType.WORKSPACE;
+ }
+ }
+
+ mDetector.setDetectableScrollConditions(
+ directionsToDetectScroll, ignoreSlopWhenSettling);
+
+ if (mSpringHandlers == null) {
+ initSprings();
+ }
+ }
+
+ if (mNoIntercept) {
+ return false;
+ }
+
+ onControllerTouchEvent(ev);
+ return mDetector.isDraggingOrSettling();
+ }
+
+ @Override
+ public boolean onControllerTouchEvent(MotionEvent ev) {
+ for (SpringAnimationHandler h : mSpringHandlers) {
+ h.addMovement(ev);
+ }
+ return mDetector.onTouchEvent(ev);
+ }
+
+ @Override
+ public void onDragStart(boolean start) {
+ if (mCurrentAnimation == null) {
+ float range = getShiftRange();
+ long maxAccuracy = (long) (2 * range);
+
+ // Build current animation
+ mToState = mLauncher.isInState(ALL_APPS) ? NORMAL : ALL_APPS;
+ mCurrentAnimation = mLauncher.getStateManager()
+ .createAnimationToNewWorkspace(mToState, maxAccuracy);
+ mCurrentAnimation.getTarget().addListener(this);
+ mStartProgress = 0;
+ mProgressMultiplier = (mLauncher.isInState(ALL_APPS) ? 1 : -1) / range;
+ mCurrentAnimation.dispatchOnStart();
+ } else {
+ mCurrentAnimation.pause();
+ mStartProgress = mCurrentAnimation.getProgressFraction();
+ }
+
+ for (SpringAnimationHandler h : mSpringHandlers) {
+ h.skipToEnd();
+ }
+ }
+
+ private float getShiftRange() {
+ return mLauncher.mAllAppsController.getShiftRange();
+ }
+
+ @Override
+ public boolean onDrag(float displacement, float velocity) {
+ float deltaProgress = mProgressMultiplier * displacement;
+ mCurrentAnimation.setPlayFraction(deltaProgress + mStartProgress);
+ return true;
+ }
+
+ @Override
+ public void onDragEnd(float velocity, boolean fling) {
+ final long animationDuration;
+ final int logAction;
+ final LauncherState targetState;
+ final float progress = mCurrentAnimation.getProgressFraction();
+
+ if (fling) {
+ logAction = Touch.FLING;
+ if (velocity < 0) {
+ targetState = ALL_APPS;
+ animationDuration = SwipeDetector.calculateDuration(velocity,
+ mToState == ALL_APPS ? (1 - progress) : progress);
+ } else {
+ targetState = NORMAL;
+ animationDuration = SwipeDetector.calculateDuration(velocity,
+ mToState == ALL_APPS ? progress : (1 - progress));
+ }
+ // snap to top or bottom using the release velocity
+ } else {
+ logAction = Touch.SWIPE;
+ if (progress > SUCCESS_TRANSITION_PROGRESS) {
+ targetState = mToState;
+ animationDuration = SwipeDetector.calculateDuration(velocity, 1 - progress);
+ } else {
+ targetState = mToState == ALL_APPS ? NORMAL : ALL_APPS;
+ animationDuration = SwipeDetector.calculateDuration(velocity, progress);
+ }
+ }
+
+ if (fling && targetState == ALL_APPS) {
+ for (SpringAnimationHandler h : mSpringHandlers) {
+ // The icons are moving upwards, so we go to 0 from 1. (y-axis 1 is below 0.)
+ h.animateToFinalPosition(0 /* pos */, 1 /* startValue */);
+ }
+ }
+
+ mCurrentAnimation.setEndAction(new Runnable() {
+ @Override
+ public void run() {
+ if (targetState == mToState) {
+ // Transition complete. log the action
+ mLauncher.getUserEventDispatcher().logActionOnContainer(logAction,
+ mToState == ALL_APPS ? Direction.UP : Direction.DOWN,
+ mStartContainerType, mLauncher.getWorkspace().getCurrentPage());
+ } else {
+ mLauncher.getStateManager().goToState(
+ mToState == ALL_APPS ? NORMAL : ALL_APPS, false);
+ }
+ mDetector.finishedScrolling();
+ mCurrentAnimation = null;
+ }
+ });
+
+ float nextFrameProgress = Utilities.boundToRange(
+ progress + velocity * SINGLE_FRAME_MS / getShiftRange(), 0f, 1f);
+
+ ValueAnimator anim = mCurrentAnimation.getAnimationPlayer();
+ anim.setFloatValues(nextFrameProgress, targetState == mToState ? 1f : 0f);
+ anim.setDuration(animationDuration);
+ anim.setInterpolator(scrollInterpolatorForVelocity(velocity));
+ anim.start();
+ }
+}
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index e72f54e..24c4704 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -1582,6 +1582,7 @@
ValueAnimator stepAnimator = ValueAnimator.ofFloat(0, 1);
stepAnimator.addUpdateListener(listener);
+ stepAnimator.setDuration(config.duration);
anim.play(stepAnimator);
anim.addListener(listener);
}
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index a84172d..9b64043 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -1,25 +1,19 @@
package com.android.launcher3.allapps;
-import static com.android.launcher3.LauncherState.ALL_APPS;
-import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
+import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
import android.animation.Animator;
import android.animation.AnimatorInflater;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
-import android.support.animation.SpringAnimation;
import android.util.Property;
-import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Interpolator;
-import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.Hotseat;
import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAnimUtils;
import com.android.launcher3.LauncherState;
import com.android.launcher3.LauncherStateManager.AnimationConfig;
import com.android.launcher3.R;
@@ -27,16 +21,9 @@
import com.android.launcher3.Workspace;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.anim.SpringAnimationHandler;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.graphics.GradientView;
-import com.android.launcher3.touch.SwipeDetector;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.util.SystemUiController;
import com.android.launcher3.util.Themes;
-import com.android.launcher3.util.TouchController;
/**
* Handles AllApps view transition.
@@ -48,8 +35,7 @@
* 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, SwipeDetector.Listener,
- SearchUiManager.OnScrollRangeChangeListener {
+public class AllAppsTransitionController implements SearchUiManager.OnScrollRangeChangeListener {
private static final Property<AllAppsTransitionController, Float> PROGRESS =
new Property<AllAppsTransitionController, Float>(Float.class, "progress") {
@@ -65,24 +51,16 @@
}
};
- // Spring values used when the user has not flung all apps.
- private static final float SPRING_MAX_RELEASE_VELOCITY = 10000;
- // The delay (as a % of the animation duration) to start the springs.
- private static final float SPRING_DELAY = 0.3f;
-
private final Interpolator mWorkspaceAccelnterpolator = Interpolators.ACCEL_2;
private final Interpolator mHotseatAccelInterpolator = Interpolators.ACCEL_1_5;
- private final Interpolator mFastOutSlowInInterpolator = Interpolators.FAST_OUT_SLOW_IN;
private static final float PARALLAX_COEFFICIENT = .125f;
- private static final int SINGLE_FRAME_MS = 16;
private AllAppsContainerView mAppsView;
private Workspace mWorkspace;
private Hotseat mHotseat;
private final Launcher mLauncher;
- private final SwipeDetector mDetector;
private final boolean mIsDarkTheme;
// Animation in this class is controlled by a single variable {@link mProgress}.
@@ -91,190 +69,25 @@
// When {@link mProgress} is 0, all apps container is pulled up.
// When {@link mProgress} is 1, all apps container is pulled down.
- private float mShiftStart; // [0, mShiftRange]
private float mShiftRange; // changes depending on the orientation
private float mProgress; // [0, 1], mShiftRange * mProgress = shiftCurrent
- // Velocity of the container. Unit is in px/ms.
- private float mContainerVelocity;
-
private static final float DEFAULT_SHIFT_RANGE = 10;
- private static final float RECATCH_REJECTION_FRACTION = .0875f;
-
- private long mAnimationDuration;
-
- private boolean mNoIntercept;
- private boolean mTouchEventStartedOnHotseat;
-
- // Used in discovery bounce animation to provide the transition without workspace changing.
private boolean mIsTranslateWithoutWorkspace = false;
private Animator mDiscoBounceAnimation;
private GradientView mGradientView;
- private SpringAnimation mSearchSpring;
- private SpringAnimationHandler mSpringAnimationHandler;
-
public AllAppsTransitionController(Launcher l) {
mLauncher = l;
- mDetector = new SwipeDetector(l, this, SwipeDetector.VERTICAL);
mShiftRange = DEFAULT_SHIFT_RANGE;
mProgress = 1f;
mIsDarkTheme = Themes.getAttrBoolean(mLauncher, R.attr.isMainColorDark);
}
- @Override
- public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
- if (ev.getAction() == MotionEvent.ACTION_DOWN) {
- mNoIntercept = false;
- mTouchEventStartedOnHotseat = mLauncher.getDragLayer().isEventOverHotseat(ev);
- if (!mLauncher.isInState(ALL_APPS) && !mLauncher.isInState(NORMAL)) {
- mNoIntercept = true;
- } else if (mLauncher.isInState(ALL_APPS) &&
- !mAppsView.shouldContainerScroll(ev)) {
- mNoIntercept = true;
- } else if (AbstractFloatingView.getTopOpenView(mLauncher) != null) {
- mNoIntercept = true;
- } else {
- // Now figure out which direction scroll events the controller will start
- // calling the callbacks.
- int directionsToDetectScroll = 0;
- boolean ignoreSlopWhenSettling = false;
-
- if (mDetector.isIdleState()) {
- if (mLauncher.isInState(ALL_APPS)) {
- directionsToDetectScroll |= SwipeDetector.DIRECTION_NEGATIVE;
- } else {
- directionsToDetectScroll |= SwipeDetector.DIRECTION_POSITIVE;
- }
- } else {
- if (isInDisallowRecatchBottomZone()) {
- directionsToDetectScroll |= SwipeDetector.DIRECTION_POSITIVE;
- } else if (isInDisallowRecatchTopZone()) {
- directionsToDetectScroll |= SwipeDetector.DIRECTION_NEGATIVE;
- } else {
- directionsToDetectScroll |= SwipeDetector.DIRECTION_BOTH;
- ignoreSlopWhenSettling = true;
- }
- }
- mDetector.setDetectableScrollConditions(directionsToDetectScroll,
- ignoreSlopWhenSettling);
- }
- }
-
- if (mNoIntercept) {
- return false;
- }
- mDetector.onTouchEvent(ev);
- if (mDetector.isSettlingState() && (isInDisallowRecatchBottomZone() || isInDisallowRecatchTopZone())) {
- return false;
- }
- return mDetector.isDraggingOrSettling();
- }
-
- @Override
- public boolean onControllerTouchEvent(MotionEvent ev) {
- if (hasSpringAnimationHandler()) {
- mSpringAnimationHandler.addMovement(ev);
- }
- return mDetector.onTouchEvent(ev);
- }
-
- private boolean isInDisallowRecatchTopZone() {
- return mProgress < RECATCH_REJECTION_FRACTION;
- }
-
- private boolean isInDisallowRecatchBottomZone() {
- return mProgress > 1 - RECATCH_REJECTION_FRACTION;
- }
-
- @Override
- public void onDragStart(boolean start) {
- mLauncher.getStateManager().cancelAnimation();
- cancelDiscoveryAnimation();
- mShiftStart = mAppsView.getTranslationY();
- onProgressAnimationStart();
- if (hasSpringAnimationHandler()) {
- mSpringAnimationHandler.skipToEnd();
- }
- }
-
- @Override
- public boolean onDrag(float displacement, float velocity) {
- if (mAppsView == null) {
- return false; // early termination.
- }
-
- mContainerVelocity = velocity;
-
- float shift = Math.min(Math.max(0, mShiftStart + displacement), mShiftRange);
- setProgress(shift / mShiftRange);
-
- return true;
- }
-
- @Override
- public void onDragEnd(float velocity, boolean fling) {
- if (mAppsView == null) {
- return; // early termination.
- }
-
- final int containerType = mTouchEventStartedOnHotseat
- ? ContainerType.HOTSEAT : ContainerType.WORKSPACE;
- if (fling) {
- if (velocity < 0) {
- calculateDuration(velocity, mAppsView.getTranslationY());
- if (!mLauncher.isInState(ALL_APPS)) {
- logSwipeOnContainer(Touch.FLING, Direction.UP, containerType);
- }
- mLauncher.getStateManager().goToState(ALL_APPS);
- if (hasSpringAnimationHandler()) {
- mSpringAnimationHandler.add(mSearchSpring, true /* setDefaultValues */);
- // The icons are moving upwards, so we go to 0 from 1. (y-axis 1 is below 0.)
- mSpringAnimationHandler.animateToFinalPosition(0 /* pos */, 1 /* startValue */);
- }
- } else {
- calculateDuration(velocity, Math.abs(mShiftRange - mAppsView.getTranslationY()));
- if (mLauncher.isInState(ALL_APPS)) {
- logSwipeOnContainer(Touch.FLING, Direction.DOWN, ContainerType.ALLAPPS);
- }
- mLauncher.getStateManager().goToState(NORMAL);
- }
- // snap to top or bottom using the release velocity
- } else {
- if (mAppsView.getTranslationY() > mShiftRange / 2) {
- calculateDuration(velocity, Math.abs(mShiftRange - mAppsView.getTranslationY()));
- if (mLauncher.isInState(ALL_APPS)) {
- logSwipeOnContainer(Touch.SWIPE, Direction.DOWN, ContainerType.ALLAPPS);
- }
- mLauncher.getStateManager().goToState(NORMAL);
- } else {
- calculateDuration(velocity, Math.abs(mAppsView.getTranslationY()));
- if (!mLauncher.isInState(ALL_APPS)) {
- logSwipeOnContainer(Touch.SWIPE, Direction.UP, containerType);
- }
- mLauncher.getStateManager().goToState(ALL_APPS);
- }
- }
- }
-
- /**
- * Important, make sure that this method is called only when actual launcher state transition
- * happen and not when user swipes in one direction only to cancel that swipe seconds later.
- *
- * @param touchType Swipe or Fling
- * @param direction Up or Down
- * @param containerType Workspace or Allapps
- */
- private void logSwipeOnContainer(int touchType, int direction, int containerType) {
- mLauncher.getUserEventDispatcher().logActionOnContainer(
- touchType, direction, containerType,
- mLauncher.getWorkspace().getCurrentPage());
- }
-
- public boolean isTransitioning() {
- return mDetector.isDraggingOrSettling();
+ public float getShiftRange() {
+ return mShiftRange;
}
private void onProgressAnimationStart() {
@@ -310,10 +123,9 @@
* @param progress value between 0 and 1, 0 shows all apps and 1 shows workspace
*
* @see #setFinalProgress(float)
- * @see #animateToFinalProgress(float, boolean, AnimatorSet, AnimationConfig)
+ * @see #animateToFinalProgress(float, AnimatorSet, AnimationConfig)
*/
public void setProgress(float progress) {
- float shiftPrevious = mProgress * mShiftRange;
mProgress = progress;
float shiftCurrent = progress * mShiftRange;
@@ -341,11 +153,6 @@
mWorkspace.setWorkspaceYTranslationAndAlpha(
PARALLAX_COEFFICIENT * (-mShiftRange + shiftCurrent), workspaceAlpha);
- if (!mDetector.isDraggingState()) {
- mContainerVelocity = mDetector.computeVelocity(shiftCurrent - shiftPrevious,
- System.currentTimeMillis());
- }
-
updateLightStatusBar(shiftCurrent);
}
@@ -353,10 +160,6 @@
return mProgress;
}
- private void calculateDuration(float velocity, float disp) {
- mAnimationDuration = SwipeDetector.calculateDuration(velocity, disp / mShiftRange);
- }
-
/**
* Sets the vertical transition progress to {@param progress} and updates all the dependent UI
* accordingly.
@@ -371,34 +174,20 @@
* dependent UI using various animation events
*
* @param progress the final vertical progress at the end of the animation
- * @param addSpring should there be an addition spring animation for the sub-views
* @param animationOut the target AnimatorSet where this animation should be added
* @param outConfig an in/out configuration which can be shared with other animations
*/
- public void animateToFinalProgress(float progress, boolean addSpring,
- AnimatorSet animationOut, AnimationConfig outConfig) {
+ public void animateToFinalProgress(
+ float progress, AnimatorSet animationOut, AnimationConfig outConfig) {
if (Float.compare(mProgress, progress) == 0) {
// Fail fast
onProgressAnimationEnd();
return;
}
- outConfig.shouldPost = true;
- Interpolator interpolator;
- if (mDetector.isIdleState()) {
- mAnimationDuration = LauncherAnimUtils.ALL_APPS_TRANSITION_MS;
- mShiftStart = mAppsView.getTranslationY();
- interpolator = mFastOutSlowInInterpolator;
- } else {
- interpolator = scrollInterpolatorForVelocity(mContainerVelocity);
- mProgress = Utilities.boundToRange(
- mProgress + mContainerVelocity * SINGLE_FRAME_MS / mShiftRange, 0f, 1f);
- outConfig.shouldPost = false;
- }
-
- outConfig.duration = mAnimationDuration;
+ Interpolator interpolator = outConfig.userControlled ? LINEAR : FAST_OUT_SLOW_IN;
ObjectAnimator anim = ObjectAnimator.ofFloat(this, PROGRESS, mProgress, progress);
- anim.setDuration(mAnimationDuration);
+ anim.setDuration(outConfig.duration);
anim.setInterpolator(interpolator);
anim.addListener(new AnimationSuccessListener() {
@Override
@@ -413,20 +202,6 @@
});
animationOut.play(anim);
- if (addSpring) {
- ValueAnimator springAnim = ValueAnimator.ofFloat(0, 1);
- springAnim.setDuration((long) (mAnimationDuration * SPRING_DELAY));
- springAnim.addListener(new AnimationSuccessListener() {
- @Override
- public void onAnimationSuccess(Animator animator) {
- if (!mSpringAnimationHandler.isRunning()) {
- float velocity = mProgress * SPRING_MAX_RELEASE_VELOCITY;
- mSpringAnimationHandler.animateToPositionWithVelocity(0, 1, velocity);
- }
- }
- });
- animationOut.play(anim);
- }
}
public void showDiscoveryBounce() {
@@ -476,12 +251,6 @@
mWorkspace = workspace;
mHotseat.bringToFront();
mAppsView.getSearchUiManager().addOnScrollRangeChangeListener(this);
- mSpringAnimationHandler = mAppsView.getSpringAnimationHandler();
- mSearchSpring = mAppsView.getSearchUiManager().getSpringForFling();
- }
-
- private boolean hasSpringAnimationHandler() {
- return FeatureFlags.LAUNCHER3_PHYSICS && mSpringAnimationHandler != null;
}
@Override
@@ -503,13 +272,5 @@
mHotseat.setVisibility(View.INVISIBLE);
mAppsView.setVisibility(View.VISIBLE);
}
- if (hasSpringAnimationHandler()) {
- mSpringAnimationHandler.remove(mSearchSpring);
- mSpringAnimationHandler.reset();
- }
-
- // TODO: This call should no longer be needed once caret stops animating.
- setProgress(mProgress);
- mDetector.finishedScrolling();
}
}
diff --git a/src/com/android/launcher3/anim/AnimationSuccessListener.java b/src/com/android/launcher3/anim/AnimationSuccessListener.java
index feebc6c..9448632 100644
--- a/src/com/android/launcher3/anim/AnimationSuccessListener.java
+++ b/src/com/android/launcher3/anim/AnimationSuccessListener.java
@@ -24,7 +24,7 @@
*/
public abstract class AnimationSuccessListener extends AnimatorListenerAdapter {
- private boolean mCancelled = false;
+ protected boolean mCancelled = false;
@Override
public void onAnimationCancel(Animator animation) {
diff --git a/src/com/android/launcher3/compat/AnimatorSetCompat.java b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
similarity index 75%
rename from src/com/android/launcher3/compat/AnimatorSetCompat.java
rename to src/com/android/launcher3/anim/AnimatorPlaybackController.java
index 6676725..826a20e 100644
--- a/src/com/android/launcher3/compat/AnimatorSetCompat.java
+++ b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
@@ -13,37 +13,32 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.launcher3.compat;
+package com.android.launcher3.anim;
import android.animation.Animator;
import android.animation.Animator.AnimatorListener;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
-import android.annotation.TargetApi;
-import android.os.Build;
-
-import com.android.launcher3.Utilities;
-import com.android.launcher3.anim.AnimationSuccessListener;
-import com.android.launcher3.anim.Interpolators;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
- * Compat implementation for various new APIs in {@link AnimatorSet}
+ * Helper class to control the playback of an {@link AnimatorSet}, with custom interpolators
+ * and durations.
*
- * Note: The compat implementation does not support start delays on child animations or
+ * Note: The implementation does not support start delays on child animations or
* sequential playbacks.
*/
-public abstract class AnimatorSetCompat implements ValueAnimator.AnimatorUpdateListener {
+public abstract class AnimatorPlaybackController implements ValueAnimator.AnimatorUpdateListener {
- public static AnimatorSetCompat wrap(AnimatorSet anim, int duration) {
- if (Utilities.ATLEAST_OREO) {
- return new AnimatorSetCompatVO(anim, duration);
- } else {
- return new AnimatorSetCompatVL(anim, duration);
- }
+ public static AnimatorPlaybackController wrap(AnimatorSet anim, long duration) {
+
+ /**
+ * TODO: use {@link AnimatorSet#setCurrentPlayTime(long)} once b/68382377 is fixed.
+ */
+ return new AnimatorPlaybackControllerVL(anim, duration);
}
private final ValueAnimator mAnimationPlayer;
@@ -52,23 +47,28 @@
protected final AnimatorSet mAnim;
protected float mCurrentFraction;
+ private Runnable mEndAction;
- protected AnimatorSetCompat(AnimatorSet anim, int duration) {
+ protected AnimatorPlaybackController(AnimatorSet anim, long duration) {
mAnim = anim;
mDuration = duration;
mAnimationPlayer = ValueAnimator.ofFloat(0, 1);
mAnimationPlayer.setInterpolator(Interpolators.LINEAR);
+ mAnimationPlayer.addListener(new OnAnimationEndDispatcher());
mAnimationPlayer.addUpdateListener(this);
}
+ public AnimatorSet getTarget() {
+ return mAnim;
+ }
+
/**
* Starts playing the animation forward from current position.
*/
public void start() {
mAnimationPlayer.setFloatValues(mCurrentFraction, 1);
mAnimationPlayer.setDuration(clampDuration(1 - mCurrentFraction));
- mAnimationPlayer.addListener(new OnAnimationEndDispatcher());
mAnimationPlayer.start();
}
@@ -78,20 +78,38 @@
public void reverse() {
mAnimationPlayer.setFloatValues(mCurrentFraction, 0);
mAnimationPlayer.setDuration(clampDuration(mCurrentFraction));
- mAnimationPlayer.addListener(new OnAnimationEndDispatcher());
mAnimationPlayer.start();
}
/**
+ * Pauses the currently playing animation.
+ */
+ public void pause() {
+ mAnimationPlayer.cancel();
+ }
+
+ /**
+ * Returns the underlying animation used for controlling the set.
+ */
+ public ValueAnimator getAnimationPlayer() {
+ return mAnimationPlayer;
+ }
+
+ /**
* Sets the current animation position and updates all the child animators accordingly.
*/
public abstract void setPlayFraction(float fraction);
+ public float getProgressFraction() {
+ return mCurrentFraction;
+ }
+
/**
- * @see Animator#addListener(AnimatorListener)
+ * Sets the action to be called when the animation is completed. Also clears any
+ * previously set action.
*/
- public void addListener(Animator.AnimatorListener listener) {
- mAnimationPlayer.addListener(listener);
+ public void setEndAction(Runnable runnable) {
+ mEndAction = runnable;
}
@Override
@@ -124,11 +142,11 @@
}
}
- public static class AnimatorSetCompatVL extends AnimatorSetCompat {
+ public static class AnimatorPlaybackControllerVL extends AnimatorPlaybackController {
private final ValueAnimator[] mChildAnimations;
- private AnimatorSetCompatVL(AnimatorSet anim, int duration) {
+ private AnimatorPlaybackControllerVL(AnimatorSet anim, long duration) {
super(anim, duration);
// Build animation list
@@ -164,25 +182,19 @@
}
- @TargetApi(Build.VERSION_CODES.O)
- private static class AnimatorSetCompatVO extends AnimatorSetCompat {
-
- private AnimatorSetCompatVO(AnimatorSet anim, int duration) {
- super(anim, duration);
- }
+ private class OnAnimationEndDispatcher extends AnimationSuccessListener {
@Override
- public void setPlayFraction(float fraction) {
- mCurrentFraction = fraction;
- mAnim.setCurrentPlayTime(clampDuration(fraction));
+ public void onAnimationStart(Animator animation) {
+ mCancelled = false;
}
- }
-
- private class OnAnimationEndDispatcher extends AnimationSuccessListener {
@Override
public void onAnimationSuccess(Animator animator) {
dispatchOnEndRecursively(mAnim);
+ if (mEndAction != null) {
+ mEndAction.run();
+ }
}
private void dispatchOnEndRecursively(Animator animator) {
diff --git a/src/com/android/launcher3/anim/SpringAnimationHandler.java b/src/com/android/launcher3/anim/SpringAnimationHandler.java
index eec3a48..29a2430 100644
--- a/src/com/android/launcher3/anim/SpringAnimationHandler.java
+++ b/src/com/android/launcher3/anim/SpringAnimationHandler.java
@@ -83,6 +83,10 @@
mAnimations.add(spring);
}
+ public AnimationFactory<T> getFactory() {
+ return mAnimationFactory;
+ }
+
/**
* Adds a new or recycled animation to the list of springs handled by this class.
*
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index 5b1a4dc..0f0b20d 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -46,6 +46,7 @@
import com.android.launcher3.R;
import com.android.launcher3.ShortcutAndWidgetContainer;
import com.android.launcher3.Utilities;
+import com.android.launcher3.VerticalSwipeController;
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.config.FeatureFlags;
@@ -96,6 +97,7 @@
// Handles all apps pull up interaction
private AllAppsTransitionController mAllAppsController;
+ private VerticalSwipeController mVerticalSwipeController;
private TouchController mActiveController;
/**
@@ -121,6 +123,7 @@
mLauncher = launcher;
mDragController = dragController;
mAllAppsController = allAppsTransitionController;
+ mVerticalSwipeController = new VerticalSwipeController(mLauncher);
boolean isAccessibilityEnabled = ((AccessibilityManager) mLauncher.getSystemService(
Context.ACCESSIBILITY_SERVICE)).isEnabled();
@@ -191,8 +194,8 @@
return true;
}
- if (mAllAppsController.onControllerInterceptTouchEvent(ev)) {
- mActiveController = mAllAppsController;
+ if (mVerticalSwipeController.onControllerInterceptTouchEvent(ev)) {
+ mActiveController = mVerticalSwipeController;
return true;
}
diff --git a/src/com/android/launcher3/states/AllAppsState.java b/src/com/android/launcher3/states/AllAppsState.java
index ee35b4d..ed3023a 100644
--- a/src/com/android/launcher3/states/AllAppsState.java
+++ b/src/com/android/launcher3/states/AllAppsState.java
@@ -32,7 +32,7 @@
public static final String APPS_VIEW_SHOWN = "launcher.apps_view_shown";
- private static final int STATE_FLAGS = FLAG_DISABLE_ACCESSIBILITY | FLAG_HAS_SPRING;
+ private static final int STATE_FLAGS = FLAG_DISABLE_ACCESSIBILITY;
public AllAppsState(int id) {
super(id, ContainerType.ALLAPPS, ALL_APPS_TRANSITION_MS, 0f, STATE_FLAGS);