Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6becf7c0 authored by Tony Wickham's avatar Tony Wickham
Browse files

Add atomic recents animation while swiping up

State handlers can now specify atomic and non-atomic components of
their animations to states, which can be specified when creating a
new animation. There is now one atomic animation, when going from
NORMAL to OVERVIEW (and in reverse):
- RecentsViewStateController's animation (scale/alpha) is all atomic
- WorkspaceStateTransitionAnimation has atomic and non-atomic:
  - Hotseat and workspace alpha is atomic, as is workspace scale
  - Everything else (scrim, translation, qsb and drag handle alpha) is
    non-atomic
- All apps progress is non-atomic

Also simplified dragging through overview; no longer pulls against you,
so we use an OvershootInterpolator when flinging instead of our custom
interpolator for the spring effect.

Bug: 76449024
Bug: 78089840
Change-Id: Iafac84d0c2b99ee9cf9dd5b30e2218286713b449
parent 639b0717
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -84,7 +84,7 @@ public class AllAppsState extends LauncherState {

    @Override
    public float[] getOverviewScaleAndTranslationYFactor(Launcher launcher) {
        return new float[] {1f, -0.2f};
        return new float[] {0.9f, -0.2f};
    }

    @Override
+4 −3
Original line number Diff line number Diff line
@@ -9,6 +9,7 @@ import android.view.MotionEvent;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.LauncherStateManager.AnimationComponents;
import com.android.launcher3.touch.AbstractStateChangeTouchController;
import com.android.launcher3.touch.SwipeDetector;
import com.android.launcher3.userevent.nano.LauncherLogProto;
@@ -56,11 +57,11 @@ public class LandscapeEdgeSwipeController extends AbstractStateChangeTouchContro
    }

    @Override
    protected float initCurrentAnimation() {
    protected float initCurrentAnimation(@AnimationComponents int animComponent) {
        float range = getShiftRange();
        long maxAccuracy = (long) (2 * range);
        mCurrentAnimation = mLauncher.getStateManager()
                .createAnimationToNewWorkspace(mToState, maxAccuracy);
        mCurrentAnimation = mLauncher.getStateManager().createAnimationToNewWorkspace(mToState,
                maxAccuracy, animComponent);
        return (mLauncher.getDeviceProfile().isSeascape() ? 2 : -2) / range;
    }

+7 −2
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.Workspace;
import com.android.launcher3.allapps.DiscoveryBounce;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.quickstep.views.RecentsView;
@@ -47,8 +48,12 @@ public class OverviewState extends LauncherState {

    @Override
    public float[] getWorkspaceScaleAndTranslation(Launcher launcher) {
        // TODO: provide a valid value
        return new float[]{1, 0, -launcher.getDeviceProfile().hotseatBarSizePx / 2};
        RecentsView recentsView = launcher.getOverviewPanel();
        Workspace workspace = launcher.getWorkspace();
        recentsView.getTaskSize(sTempRect);
        float scale = (float) sTempRect.width() / workspace.getWidth();
        float parallaxFactor = 0.4f;
        return new float[]{scale, 0, -getDefaultSwipeHeight(launcher) * parallaxFactor};
    }

    @Override
+21 −94
Original line number Diff line number Diff line
@@ -18,20 +18,20 @@ package com.android.launcher3.uioverrides;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATION;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_VERTICAL_PROGRESS;
import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
import static com.android.launcher3.anim.Interpolators.LINEAR;

import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.view.MotionEvent;
import android.view.animation.Interpolator;
import android.view.animation.OvershootInterpolator;

import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.LauncherStateManager.AnimationComponents;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.anim.Interpolators;
@@ -52,43 +52,11 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr

    private static final String TAG = "PortraitStatesTouchCtrl";

    private static final float TOTAL_DISTANCE_MULTIPLIER = 3f;
    private static final float LINEAR_SCALE_LIMIT = 1 / TOTAL_DISTANCE_MULTIPLIER;

    // Must be greater than LINEAR_SCALE_LIMIT;
    private static final float MAXIMUM_DISTANCE_FACTOR = 0.9f;

    // Maximum amount to overshoot.
    private static final float MAX_OVERSHOOT = 0.3f;

    private static final double PI_BY_2 = Math.PI / 2;

    private InterpolatorWrapper mAllAppsInterpolatorWrapper = new InterpolatorWrapper();

    // If true, we will finish the current animation instantly on second touch.
    private boolean mFinishFastOnSecondTouch;

    private final Interpolator mAllAppsDampedInterpolator = new Interpolator() {

        private final double mAngleMultiplier = Math.PI /
                (2 * (MAXIMUM_DISTANCE_FACTOR - LINEAR_SCALE_LIMIT));

        @Override
        public float getInterpolation(float v) {
            if (v <= LINEAR_SCALE_LIMIT) {
                return v * TOTAL_DISTANCE_MULTIPLIER;
            }
            float overshoot = (v - LINEAR_SCALE_LIMIT);
            return (float) (1 + MAX_OVERSHOOT * Math.sin(overshoot * mAngleMultiplier));
        }
    };

    private final Interpolator mOverviewBoundInterpolator = (v) -> {
            if (v >= MAXIMUM_DISTANCE_FACTOR) {
                return 1;
            }
            return FAST_OUT_SLOW_IN.getInterpolation(v / MAXIMUM_DISTANCE_FACTOR);
    };

    public PortraitStatesTouchController(Launcher l) {
        super(l, SwipeDetector.VERTICAL);
@@ -144,17 +112,16 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr
    }

    private AnimatorSetBuilder getNormalToOverviewAnimation() {
        mAllAppsInterpolatorWrapper.baseInterpolator = mAllAppsDampedInterpolator;
        mAllAppsInterpolatorWrapper.baseInterpolator = LINEAR;

        AnimatorSetBuilder builder = new AnimatorSetBuilder();
        builder.setInterpolator(ANIM_VERTICAL_PROGRESS, mAllAppsInterpolatorWrapper);

        builder.setInterpolator(ANIM_OVERVIEW_TRANSLATION, mOverviewBoundInterpolator);
        return builder;
    }

    @Override
    protected float initCurrentAnimation() {
    protected float initCurrentAnimation(@AnimationComponents int animComponents) {
        float range = getShiftRange();
        long maxAccuracy = (long) (2 * range);

@@ -167,7 +134,6 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr

        if (mFromState == NORMAL && mToState == OVERVIEW && totalShift != 0) {
            builder = getNormalToOverviewAnimation();
            totalShift = totalShift * TOTAL_DISTANCE_MULTIPLIER;
        } else {
            builder = new AnimatorSetBuilder();
        }
@@ -190,7 +156,8 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr
            mLauncher.getStateManager().setCurrentUserControlledAnimation(mCurrentAnimation);
        } else {
            mCurrentAnimation = mLauncher.getStateManager()
                    .createAnimationToNewWorkspace(mToState, builder, maxAccuracy, this::clearState);
                    .createAnimationToNewWorkspace(mToState, builder, maxAccuracy, this::clearState,
                            animComponents);
        }

        if (totalShift == 0) {
@@ -210,9 +177,9 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr
    @Override
    protected void updateSwipeCompleteAnimation(ValueAnimator animator, long expectedDuration,
            LauncherState targetState, float velocity, boolean isFling) {
        handleFirstSwipeToOverview(animator, expectedDuration, targetState, velocity, isFling);
        super.updateSwipeCompleteAnimation(animator, expectedDuration, targetState,
                velocity, isFling);
        handleFirstSwipeToOverview(animator, expectedDuration, targetState, velocity, isFling);
    }

    private void handleFirstSwipeToOverview(final ValueAnimator animator,
@@ -220,63 +187,23 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr
            final boolean isFling) {
        if (mFromState == NORMAL && mToState == OVERVIEW && targetState == OVERVIEW) {
            mFinishFastOnSecondTouch = true;

            // Update all apps interpolator
            float currentFraction = mCurrentAnimation.getProgressFraction();
            float absVelocity = Math.abs(velocity);
            float currentValue = mAllAppsDampedInterpolator.getInterpolation(currentFraction);

            if (isFling && absVelocity > 1 && currentFraction < LINEAR_SCALE_LIMIT) {

                // TODO: Clean up these magic calculations
                // Linearly interpolate the max value based on the velocity.
                float maxValue = Math.max(absVelocity > 4 ? 1 + MAX_OVERSHOOT :
                                1 + (absVelocity - 1) * MAX_OVERSHOOT / 3,
                        currentValue);
                double angleToPeak = PI_BY_2 - Math.asin(currentValue / maxValue);

                if (expectedDuration != 0 && angleToPeak != 0) {

                    float distanceLeft = 1 - currentFraction;
                    mAllAppsInterpolatorWrapper.baseInterpolator = (f) -> {
                        float scaledF = (f - currentFraction) / distanceLeft;

                        if (scaledF < 0.5f) {
                            double angle = PI_BY_2 - angleToPeak + scaledF * angleToPeak / 0.5f;
                            return (float) (maxValue * Math.sin(angle));
                        }

                        scaledF = ((scaledF - .5f) / .5f);
                        double angle = PI_BY_2 + 3 * scaledF * PI_BY_2;
                        float amplitude = (1 - scaledF) * (1 - scaledF) * (maxValue - 1);
                        return 1 + (float) (amplitude * Math.sin(angle));
                    };

                    animator.setDuration(expectedDuration).setInterpolator(LINEAR);
                    return;
                }
            }

            if (currentFraction < LINEAR_SCALE_LIMIT) {
                mAllAppsInterpolatorWrapper.baseInterpolator = LINEAR;
                return;
            if (isFling && expectedDuration != 0) {
                // Update all apps interpolator to add a bit of overshoot starting from currFraction
                final float currFraction = mCurrentAnimation.getProgressFraction();
                mAllAppsInterpolatorWrapper.baseInterpolator
                        = new OvershootInterpolator(Math.min(Math.abs(velocity) / 3, 3f)) {
                    @Override
                    public float getInterpolation(float t) {
                        return super.getInterpolation(t) + ((1 - t) * currFraction);
                    }
            float extraValue = mAllAppsDampedInterpolator.getInterpolation(currentFraction) - 1;
            float distanceLeft = 1 - currentFraction;

            animator.setFloatValues(currentFraction, 1);
            mAllAppsInterpolatorWrapper.baseInterpolator = (f) -> {
                float scaledF = (f - currentFraction) / distanceLeft;

                double angle = scaledF * 1.5 * Math.PI;
                float amplitude = (1 - scaledF) * (1 - scaledF) * extraValue;
                return 1 + (float) (amplitude * Math.sin(angle));
                };
            animator.setDuration(200).setInterpolator(LINEAR);
            return;
                animator.setFloatValues(0, 1);
                animator.setDuration(Math.max(expectedDuration, 300)).setInterpolator(LINEAR);
            }
        } else {
            mFinishFastOnSecondTouch = false;
        }
    }

    @Override
    protected void onSwipeInteractionCompleted(LauncherState targetState, int logAction) {
+6 −5
Original line number Diff line number Diff line
@@ -15,7 +15,6 @@
 */
package com.android.launcher3.uioverrides;

import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATION;
import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE_IN_OUT;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.quickstep.views.LauncherRecentsView.TRANSLATION_Y_FACTOR;
@@ -63,12 +62,14 @@ public class RecentsViewStateController implements StateHandler {
    @Override
    public void setStateWithAnimation(final LauncherState toState,
            AnimatorSetBuilder builder, AnimationConfig config) {
        if (!config.playAtomicComponent()) {
            // The entire recents animation is played atomically.
            return;
        }
        PropertySetter setter = config.getPropertySetter(builder);
        float[] scaleTranslationYFactor = toState.getOverviewScaleAndTranslationYFactor(mLauncher);
        setter.setFloat(mRecentsView, ADJACENT_SCALE, scaleTranslationYFactor[0],
                builder.getInterpolator(ANIM_OVERVIEW_TRANSLATION, LINEAR));
        setter.setFloat(mRecentsView, TRANSLATION_Y_FACTOR, scaleTranslationYFactor[1],
                builder.getInterpolator(ANIM_OVERVIEW_TRANSLATION, LINEAR));
        setter.setFloat(mRecentsView, ADJACENT_SCALE, scaleTranslationYFactor[0], LINEAR);
        setter.setFloat(mRecentsView, TRANSLATION_Y_FACTOR, scaleTranslationYFactor[1], LINEAR);
        setter.setFloat(mRecentsViewContainer, CONTENT_ALPHA, toState.overviewUi ? 1 : 0,
                AGGRESSIVE_EASE_IN_OUT);

Loading