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

Commit 7a80b59e authored by Sunny Goyal's avatar Sunny Goyal
Browse files

Swipe interaction changes on home screen

> Increasing the distance to travel for the first swipe
> Adding support for custom interpolators when building an animation
> When quickly swiping twice from home, finished the first animation

Change-Id: Ibc3c8667e9b927376fd99f08f0ca027f2398914b
parent 9869f758
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -89,7 +89,7 @@ public class AllAppsState extends LauncherState {
    }

    @Override
    public float getOverviewTranslationX(Launcher launcher) {
    public float getOverviewTranslationFactor(Launcher launcher) {
        return 0;
    }

+1 −1
Original line number Diff line number Diff line
@@ -58,7 +58,7 @@ public class OverviewState extends LauncherState {
    }

    @Override
    public float getOverviewTranslationX(Launcher launcher) {
    public float getOverviewTranslationFactor(Launcher launcher) {
        return 0;
    }

+166 −3
Original line number Diff line number Diff line
@@ -18,16 +18,24 @@ 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 com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.touch.AbstractStateChangeTouchController;
import com.android.launcher3.touch.SwipeDetector;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.touch.AbstractStateChangeTouchController;
import com.android.quickstep.TouchInteractionService;
import com.android.quickstep.util.SysuiEventLogger;

@@ -36,6 +44,47 @@ import com.android.quickstep.util.SysuiEventLogger;
 */
public class PortraitStatesTouchController extends AbstractStateChangeTouchController {

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

    // Much 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 > 0, the animation progress is clamped at that value as long as user is dragging.
    private float mClampProgressUpdate = -1;

    // 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);
    }
@@ -43,6 +92,11 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr
    @Override
    protected boolean canInterceptTouch(MotionEvent ev) {
        if (mCurrentAnimation != null) {
            if (mFinishFastOnSecondTouch) {
                // TODO: Animate to finish instead.
                mCurrentAnimation.getAnimationPlayer().end();
            }

            // If we are already animating from a previous state, we can intercept.
            return true;
        }
@@ -100,17 +154,49 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr
        }
    }

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

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

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

    @Override
    protected void updateProgress(float fraction) {
        if (mClampProgressUpdate > 0) {
            mCurrentAnimation.setPlayFraction(Math.min(fraction, mClampProgressUpdate));
        } else {
            super.updateProgress(fraction);
        }
    }

    @Override
    protected float initCurrentAnimation() {
        float range = getShiftRange();
        long maxAccuracy = (long) (2 * range);
        mCurrentAnimation = mLauncher.getStateManager()
                .createAnimationToNewWorkspace(mToState, maxAccuracy);

        float startVerticalShift = mFromState.getVerticalProgress(mLauncher) * range;
        float endVerticalShift = mToState.getVerticalProgress(mLauncher) * range;

        float totalShift = endVerticalShift - startVerticalShift;

        final AnimatorSetBuilder builder;

        if (mFromState == NORMAL && mToState == OVERVIEW && totalShift != 0) {
            builder = getNormalToOverviewAnimation();
            totalShift = totalShift * TOTAL_DISTANCE_MULTIPLIER;
            mClampProgressUpdate = MAXIMUM_DISTANCE_FACTOR;
        } else {
            builder = new AnimatorSetBuilder();
            mClampProgressUpdate = -1;
        }

        mCurrentAnimation = mLauncher.getStateManager()
                .createAnimationToNewWorkspace(mToState, builder, maxAccuracy);

        if (totalShift == 0) {
            totalShift = Math.signum(mFromState.ordinal - mToState.ordinal)
                    * OverviewState.getDefaultSwipeHeight(mLauncher);
@@ -118,6 +204,73 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr
        return 1 / totalShift;
    }

    @Override
    protected void updateSwipeCompleteAnimation(ValueAnimator animator, long expectedDuration,
            LauncherState targetState, float velocity, 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;
                super.updateSwipeCompleteAnimation(animator, expectedDuration, targetState,
                        velocity, isFling);
                return;
            }
            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;
        }
        mFinishFastOnSecondTouch = false;
        super.updateSwipeCompleteAnimation(animator, expectedDuration, targetState,
                velocity, isFling);
    }

    @Override
    protected void onSwipeInteractionCompleted(LauncherState targetState, int logAction) {
        super.onSwipeInteractionCompleted(targetState, logAction);
@@ -125,4 +278,14 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr
            SysuiEventLogger.writeDummyRecentsTransition(0);
        }
    }

    private static class InterpolatorWrapper implements Interpolator {

        public TimeInterpolator baseInterpolator = LINEAR;

        @Override
        public float getInterpolation(float v) {
            return baseInterpolator.getInterpolation(v);
        }
    }
}
+24 −81
Original line number Diff line number Diff line
@@ -16,32 +16,30 @@
package com.android.launcher3.uioverrides;

import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.anim.AlphaUpdateListener.updateVisibility;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATION;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.compat.AccessibilityManagerCompat.isAccessibilityEnabled;
import static com.android.quickstep.views.LauncherRecentsView.TRANSLATION_FACTOR;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.view.View;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.os.Build;

import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.LauncherStateManager.AnimationConfig;
import com.android.launcher3.LauncherStateManager.StateHandler;
import com.android.launcher3.PagedView;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.anim.Interpolators;
import com.android.quickstep.AnimatedFloat;
import com.android.quickstep.views.RecentsView;
import com.android.launcher3.anim.PropertySetter;
import com.android.quickstep.views.LauncherRecentsView;

@TargetApi(Build.VERSION_CODES.O)
public class RecentsViewStateController implements StateHandler {

    private final Launcher mLauncher;
    private final RecentsView mRecentsView;

    private final AnimatedFloat mTransitionProgress = new AnimatedFloat(this::onTransitionProgress);
    // The fraction representing the visibility of the RecentsView. This allows delaying the
    // overall transition while the RecentsView is being shown or hidden.
    private final AnimatedFloat mVisibilityMultiplier = new AnimatedFloat(this::onVisibilityProgress);
    private final LauncherRecentsView mRecentsView;

    public RecentsViewStateController(Launcher launcher) {
        mLauncher = launcher;
@@ -50,14 +48,12 @@ public class RecentsViewStateController implements StateHandler {

    @Override
    public void setState(LauncherState state) {
        setVisibility(state.overviewUi);
        setTransitionProgress(state.overviewUi ? 1 : 0);
        mRecentsView.setAlpha(state.overviewUi ? 1 : 0);
        updateVisibility(mRecentsView, isAccessibilityEnabled(mLauncher));
        mRecentsView.setTranslationFactor(state.getOverviewTranslationFactor(mLauncher));
        if (state.overviewUi) {
            mRecentsView.resetTaskVisuals();
        }
        float overviewTranslationX = state.getOverviewTranslationX(mLauncher);
        int direction = mRecentsView.isRtl() ? -1 : 1;
        mRecentsView.setTranslationX(overviewTranslationX * direction);
    }

    @Override
@@ -76,73 +72,20 @@ public class RecentsViewStateController implements StateHandler {
            builder.setStartDelay(snapDuration / 4);
        }

        ObjectAnimator progressAnim =
                mTransitionProgress.animateToValue(toState.overviewUi ? 1 : 0);
        progressAnim.setDuration(config.duration);
        progressAnim.setInterpolator(Interpolators.LINEAR);
        builder.play(progressAnim);

        ObjectAnimator visibilityAnim = animateVisibility(toState.overviewUi);
        visibilityAnim.setDuration(config.duration);
        visibilityAnim.setInterpolator(Interpolators.LINEAR);
        builder.play(visibilityAnim);
        PropertySetter setter = config.getProperSetter(builder);
        setter.setFloat(mRecentsView, TRANSLATION_FACTOR,
                toState.getOverviewTranslationFactor(mLauncher),
                builder.getInterpolator(ANIM_OVERVIEW_TRANSLATION, LINEAR));
        setter.setViewAlpha(mRecentsView, toState.overviewUi ? 1 : 0, LINEAR);

        int direction = mRecentsView.isRtl() ? -1 : 1;
        float fromTranslationX = fromState.getOverviewTranslationX(mLauncher) * direction;
        float toTranslationX = toState.getOverviewTranslationX(mLauncher) * direction;
        ObjectAnimator translationXAnim = ObjectAnimator.ofFloat(mRecentsView, View.TRANSLATION_X,
                fromTranslationX, toTranslationX);
        translationXAnim.setDuration(config.duration);
        translationXAnim.setInterpolator(Interpolators.ACCEL);
        if (toState.overviewUi) {
            translationXAnim.addUpdateListener(valueAnimator -> {
            ValueAnimator updateAnim = ValueAnimator.ofFloat(0, 1);
            updateAnim.addUpdateListener(valueAnimator -> {
                // While animating into recents, update the visible task data as needed
                mRecentsView.loadVisibleTaskData();
            });
            updateAnim.setDuration(config.duration);
            builder.play(updateAnim);
        }
        builder.play(translationXAnim);
    }

    public void setVisibility(boolean isVisible) {
        mVisibilityMultiplier.cancelAnimation();
        mRecentsView.setVisibility(isVisible ? View.VISIBLE : View.GONE);
        mVisibilityMultiplier.updateValue(isVisible ? 1 : 0);
    }

    public ObjectAnimator animateVisibility(boolean isVisible) {
        ObjectAnimator anim = mVisibilityMultiplier.animateToValue(isVisible ? 1 : 0);
        if (isVisible) {
            anim.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationStart(Animator animation) {
                    mRecentsView.setVisibility(View.VISIBLE);
                }
            });
        } else {
            anim.addListener(new AnimationSuccessListener() {
                @Override
                public void onAnimationSuccess(Animator animator) {
                    mRecentsView.setVisibility(View.GONE);
                }
            });
        }
        return anim;
    }

    public void setTransitionProgress(float progress) {
        mTransitionProgress.cancelAnimation();
        mTransitionProgress.updateValue(progress);
    }

    private void onTransitionProgress() {
        applyProgress();
    }

    private void onVisibilityProgress() {
        applyProgress();
    }

    private void applyProgress() {
        mRecentsView.setAlpha(mTransitionProgress.value * mVisibilityMultiplier.value);
    }
}
+37 −0
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@ package com.android.quickstep.views;

import static com.android.launcher3.LauncherState.NORMAL;

import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
@@ -30,7 +31,9 @@ import android.graphics.Shader;
import android.graphics.Shader.TileMode;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.AttributeSet;
import android.util.FloatProperty;
import android.widget.FrameLayout;

import com.android.launcher3.DeviceProfile;
@@ -41,14 +44,31 @@ import com.android.launcher3.R;
/**
 * {@link RecentsView} used in Launcher activity
 */
@TargetApi(Build.VERSION_CODES.O)
public class LauncherRecentsView extends RecentsView<Launcher> implements Insettable {

    public static final FloatProperty<LauncherRecentsView> TRANSLATION_FACTOR =
            new FloatProperty<LauncherRecentsView>("translationFactor") {

                @Override
                public void setValue(LauncherRecentsView view, float v) {
                    view.setTranslationFactor(v);
                }

                @Override
                public Float get(LauncherRecentsView view) {
                    return view.mTranslationFactor;
                }
            };

    private Bitmap mScrim;
    private Paint mFadePaint;
    private Shader mFadeShader;
    private Matrix mFadeMatrix;
    private boolean mScrimOnLeft;

    private float mTranslationFactor;

    public LauncherRecentsView(Context context) {
        this(context, null);
    }
@@ -131,4 +151,21 @@ public class LauncherRecentsView extends RecentsView<Launcher> implements Insett
    protected void onAllTasksRemoved() {
        mActivity.getStateManager().goToState(NORMAL);
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);

        int width = right - left;
        setTranslationX(mTranslationFactor * (mIsRtl ? -width : width));
    }

    public void setTranslationFactor(float translationFactor) {
        mTranslationFactor = translationFactor;
        setTranslationX(translationFactor * (mIsRtl ? -getWidth() : getWidth()));
    }

    public float getTranslationFactor() {
        return mTranslationFactor;
    }
}
Loading