From 031022029b1cc5731d85327f5bd4e183a72cfb3c Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Fri, 27 Oct 2017 11:05:26 -0700 Subject: [PATCH] Using state animation to control all-apps transition > Separating all-apps transtions control and vertical shift touch handling > Creating separate spring handler for search box (to avoid adding and removing spring) > Driving all-apps vertical shift using state AnimatorSet Bug: 67678570 Change-Id: I3b6a4d1f43275a5f485b399444742b6b9a8c4bb9 --- src/com/android/launcher3/LauncherState.java | 3 - .../launcher3/LauncherStateManager.java | 41 ++- .../launcher3/PinchToOverviewListener.java | 20 +- .../launcher3/VerticalSwipeController.java | 282 ++++++++++++++++++ src/com/android/launcher3/Workspace.java | 1 + .../allapps/AllAppsTransitionController.java | 259 +--------------- .../anim/AnimationSuccessListener.java | 2 +- .../AnimatorPlaybackController.java} | 84 +++--- .../anim/SpringAnimationHandler.java | 4 + .../launcher3/dragndrop/DragLayer.java | 7 +- .../launcher3/states/AllAppsState.java | 2 +- 11 files changed, 391 insertions(+), 314 deletions(-) create mode 100644 src/com/android/launcher3/VerticalSwipeController.java rename src/com/android/launcher3/{compat/AnimatorSetCompat.java => anim/AnimatorPlaybackController.java} (75%) diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java index 661ba11411..63c232d3f6 100644 --- a/src/com/android/launcher3/LauncherState.java +++ b/src/com/android/launcher3/LauncherState.java @@ -39,7 +39,6 @@ public class LauncherState { 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 @@ public class LauncherState { * @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 @@ public class LauncherState { 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 863cae7329..fd940677aa 100644 --- a/src/com/android/launcher3/LauncherStateManager.java +++ b/src/com/android/launcher3/LauncherStateManager.java @@ -28,6 +28,7 @@ import android.view.View; 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 @@ public class LauncherStateManager { 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 @@ public class LauncherStateManager { 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 class LauncherStateManager { } 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 d1a253893a..27edaf69a1 100644 --- a/src/com/android/launcher3/PinchToOverviewListener.java +++ b/src/com/android/launcher3/PinchToOverviewListener.java @@ -22,18 +22,19 @@ import static com.android.launcher3.LauncherState.OVERVIEW; 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 @@ public class PinchToOverviewListener private Workspace mWorkspace = null; private boolean mPinchStarted = false; - private AnimatorSetCompat mCurrentAnimation; + private AnimatorPlaybackController mCurrentAnimation; private float mCurrentScale; private boolean mShouldGoToFinalState; @@ -100,8 +101,9 @@ public class PinchToOverviewListener } 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 @@ public class PinchToOverviewListener } @Override - public void run() { + public void onAnimationEnd(Animator animation) { mCurrentAnimation = null; mPinchStarted = false; } @@ -121,9 +123,9 @@ public class PinchToOverviewListener 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 0000000000..12c6916764 --- /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 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 e72f54e014..24c470408a 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -1582,6 +1582,7 @@ public class Workspace extends PagedView 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 a84172ddf5..9b64043426 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.Utilities; 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 @@ import com.android.launcher3.util.TouchController; * 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 PROGRESS = new Property(Float.class, "progress") { @@ -65,24 +51,16 @@ public class AllAppsTransitionController implements TouchController, SwipeDetect } }; - // 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 @@ public class AllAppsTransitionController implements TouchController, SwipeDetect // 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 @@ public class AllAppsTransitionController implements TouchController, SwipeDetect * @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 @@ public class AllAppsTransitionController implements TouchController, SwipeDetect mWorkspace.setWorkspaceYTranslationAndAlpha( PARALLAX_COEFFICIENT * (-mShiftRange + shiftCurrent), workspaceAlpha); - if (!mDetector.isDraggingState()) { - mContainerVelocity = mDetector.computeVelocity(shiftCurrent - shiftPrevious, - System.currentTimeMillis()); - } - updateLightStatusBar(shiftCurrent); } @@ -353,10 +160,6 @@ public class AllAppsTransitionController implements TouchController, SwipeDetect 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 @@ public class AllAppsTransitionController implements TouchController, SwipeDetect * 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 @@ public class AllAppsTransitionController implements TouchController, SwipeDetect }); 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 @@ public class AllAppsTransitionController implements TouchController, SwipeDetect 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 @@ public class AllAppsTransitionController implements TouchController, SwipeDetect 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 feebc6c5de..9448632ac5 100644 --- a/src/com/android/launcher3/anim/AnimationSuccessListener.java +++ b/src/com/android/launcher3/anim/AnimationSuccessListener.java @@ -24,7 +24,7 @@ import android.animation.AnimatorListenerAdapter; */ 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 6676725930..826a20e7a2 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 @@ public abstract class AnimatorSetCompat implements ValueAnimator.AnimatorUpdateL 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 abstract class AnimatorSetCompat implements ValueAnimator.AnimatorUpdateL 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 abstract class AnimatorSetCompat implements ValueAnimator.AnimatorUpdateL } } - 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 @@ public abstract class AnimatorSetCompat implements ValueAnimator.AnimatorUpdateL } - @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 eec3a48ee3..29a2430a56 100644 --- a/src/com/android/launcher3/anim/SpringAnimationHandler.java +++ b/src/com/android/launcher3/anim/SpringAnimationHandler.java @@ -83,6 +83,10 @@ public class SpringAnimationHandler { mAnimations.add(spring); } + public AnimationFactory 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 5b1a4dc6cf..0f0b20d2bf 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.PinchToOverviewListener; 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 @@ public class DragLayer extends InsettableFrameLayout { // Handles all apps pull up interaction private AllAppsTransitionController mAllAppsController; + private VerticalSwipeController mVerticalSwipeController; private TouchController mActiveController; /** @@ -121,6 +123,7 @@ public class DragLayer extends InsettableFrameLayout { mLauncher = launcher; mDragController = dragController; mAllAppsController = allAppsTransitionController; + mVerticalSwipeController = new VerticalSwipeController(mLauncher); boolean isAccessibilityEnabled = ((AccessibilityManager) mLauncher.getSystemService( Context.ACCESSIBILITY_SERVICE)).isEnabled(); @@ -191,8 +194,8 @@ public class DragLayer extends InsettableFrameLayout { 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 ee35b4d7c3..ed3023ab13 100644 --- a/src/com/android/launcher3/states/AllAppsState.java +++ b/src/com/android/launcher3/states/AllAppsState.java @@ -32,7 +32,7 @@ public class AllAppsState extends LauncherState { 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);