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

Commit fa37c529 authored by Tony Wickham's avatar Tony Wickham
Browse files

Translate recents when attaching to app window instead of fading

When attaching recents, translate it offscreen and use a spring to pull it
into position. When detaching, use the same spring to pull it back offscreen.

Bug: 129985827
Change-Id: I05339e2ec0932070365023bfafc83cbf2a4e178e
parent c11ea4e9
Loading
Loading
Loading
Loading
+81 −17
Original line number Diff line number Diff line
@@ -16,16 +16,23 @@
package com.android.quickstep;

import static android.view.View.TRANSLATION_Y;

import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
import static com.android.launcher3.LauncherState.BACKGROUND_APP;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.LauncherStateManager.ANIM_ALL;
import static com.android.launcher3.allapps.AllAppsTransitionController.SPRING_DAMPING_RATIO;
import static com.android.launcher3.allapps.AllAppsTransitionController.SPRING_STIFFNESS;
import static com.android.launcher3.anim.Interpolators.ACCEL_2;
import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
import static com.android.launcher3.anim.Interpolators.INSTANT;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.quickstep.WindowTransformSwipeHandler.RECENTS_ATTACH_DURATION;

import static androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_LOW_BOUNCY;
import static androidx.dynamicanimation.animation.SpringForce.STIFFNESS_LOW;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
@@ -44,14 +51,18 @@ import android.view.animation.Interpolator;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import androidx.dynamicanimation.animation.SpringAnimation;
import androidx.dynamicanimation.animation.SpringForce;

import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherInitListenerEx;
import com.android.launcher3.LauncherState;
import com.android.launcher3.LauncherStateManager;
import com.android.launcher3.allapps.DiscoveryBounce;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.anim.SpringObjectAnimator;
import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.testing.TestProtocol;
@@ -99,6 +110,13 @@ public final class LauncherActivityControllerHelper implements ActivityControlHe
        DiscoveryBounce.showForOverviewIfNeeded(activity);
    }

    @Override
    public void onSwipeUpToHomeComplete(Launcher activity) {
        // Ensure recents is at the correct position for NORMAL state. For example, when we detach
        // recents, we assume the first task is invisible, making translation off by one task.
        activity.getStateManager().reapplyState();
    }

    @Override
    public void onAssistantVisibilityChanged(float visibility) {
        Launcher launcher = getCreatedActivity();
@@ -156,18 +174,23 @@ public final class LauncherActivityControllerHelper implements ActivityControlHe
            public AnimatorPlaybackController createActivityAnimationToHome() {
                // Return an empty APC here since we have an non-user controlled animation to home.
                long accuracy = 2 * Math.max(dp.widthPx, dp.heightPx);
                AnimatorSet as = new AnimatorSet();
                as.addListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationStart(Animator animation) {
                        activity.getStateManager().goToState(NORMAL, false);
                    }
                });
                return AnimatorPlaybackController.wrap(as, accuracy);
                return activity.getStateManager().createAnimationToNewWorkspace(NORMAL, accuracy,
                        0 /* animComponents */);
            }

            @Override
            public void playAtomicAnimation(float velocity) {
                // Setup workspace with 0 duration to prepare for our staggered animation.
                LauncherStateManager stateManager = activity.getStateManager();
                AnimatorSetBuilder builder = new AnimatorSetBuilder();
                // setRecentsAttachedToAppWindow() will animate recents out.
                builder.addFlag(AnimatorSetBuilder.FLAG_DONT_ANIMATE_OVERVIEW);
                stateManager.createAtomicAnimation(BACKGROUND_APP, NORMAL, builder, ANIM_ALL, 0);
                builder.build().start();

                // Stop scrolling so that it doesn't interfere with the translation offscreen.
                recentsView.getScroller().forceFinished(true);

                new StaggeredWorkspaceAnim(activity, workspaceView, velocity).start();
            }
        };
@@ -201,7 +224,8 @@ public final class LauncherActivityControllerHelper implements ActivityControlHe
        return new AnimationFactory() {
            private Animator mShelfAnim;
            private ShelfAnimState mShelfState;
            private Animator mAttachToWindowAnim;
            private Animator mAttachToWindowFadeAnim;
            private SpringAnimation mAttachToWindowTranslationXAnim;
            private boolean mIsAttachedToWindow;

            @Override
@@ -267,20 +291,60 @@ public final class LauncherActivityControllerHelper implements ActivityControlHe
                    return;
                }
                mIsAttachedToWindow = attached;
                if (mAttachToWindowAnim != null) {
                    mAttachToWindowAnim.cancel();
                if (mAttachToWindowFadeAnim != null) {
                    mAttachToWindowFadeAnim.cancel();
                }
                mAttachToWindowAnim = ObjectAnimator.ofFloat(activity.getOverviewPanel(),
                RecentsView recentsView = activity.getOverviewPanel();
                mAttachToWindowFadeAnim = ObjectAnimator.ofFloat(recentsView,
                        RecentsView.CONTENT_ALPHA, attached ? 1 : 0);
                mAttachToWindowAnim.addListener(new AnimatorListenerAdapter() {

                int runningTaskIndex = recentsView.getRunningTaskIndex();
                if (runningTaskIndex == 0) {
                    // If we are on the first task (we haven't quick switched), translate recents in
                    // from the side. Calculate the start translation based on current scale/scroll.
                    float currScale = recentsView.getScaleX();
                    float scrollOffsetX = recentsView.getScrollOffset();

                    float offscreenX = NORMAL.getOverviewScaleAndTranslation(activity).translationX;
                    // The first task is hidden, so offset by its width.
                    int firstTaskWidth = recentsView.getTaskViewAt(0).getWidth();
                    offscreenX -= (firstTaskWidth + recentsView.getPageSpacing()) * currScale;
                    // Offset since scale pushes tasks outwards.
                    offscreenX += firstTaskWidth * (currScale - 1) / 2;
                    offscreenX = Math.max(0, offscreenX);
                    if (recentsView.isRtl()) {
                        offscreenX = -offscreenX;
                    }

                    float fromTranslationX = attached ? offscreenX - scrollOffsetX : 0;
                    float toTranslationX = attached ? 0 : offscreenX - scrollOffsetX;
                    if (mAttachToWindowTranslationXAnim == null) {
                        mAttachToWindowTranslationXAnim = new SpringAnimation(recentsView,
                                SpringAnimation.TRANSLATION_X).setSpring(new SpringForce()
                                .setDampingRatio(DAMPING_RATIO_LOW_BOUNCY)
                                .setStiffness(STIFFNESS_LOW));
                    }
                    if (!recentsView.isShown() && animate) {
                        recentsView.setTranslationX(fromTranslationX);
                        mAttachToWindowTranslationXAnim.setStartValue(fromTranslationX);
                    }
                    mAttachToWindowTranslationXAnim.animateToFinalPosition(toTranslationX);
                    if (!animate && mAttachToWindowTranslationXAnim.canSkipToEnd()) {
                        mAttachToWindowTranslationXAnim.skipToEnd();
                    }

                    mAttachToWindowFadeAnim.setInterpolator(attached ? INSTANT : ACCEL_2);
                } else {
                    mAttachToWindowFadeAnim.setInterpolator(ACCEL_DEACCEL);
                }
                mAttachToWindowFadeAnim.addListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        mAttachToWindowAnim = null;
                        mAttachToWindowFadeAnim = null;
                    }
                });
                mAttachToWindowAnim.setInterpolator(ACCEL_DEACCEL);
                mAttachToWindowAnim.setDuration(animate ? RECENTS_ATTACH_DURATION : 0);
                mAttachToWindowAnim.start();
                mAttachToWindowFadeAnim.setDuration(animate ? RECENTS_ATTACH_DURATION : 0);
                mAttachToWindowFadeAnim.start();
            }
        };
    }
+14 −16
Original line number Diff line number Diff line
@@ -21,7 +21,6 @@ import static com.android.launcher3.Utilities.SINGLE_FRAME_MS;
import static com.android.launcher3.Utilities.postAsyncCallback;
import static com.android.launcher3.anim.Interpolators.ACCEL_1_5;
import static com.android.launcher3.anim.Interpolators.DEACCEL;
import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
@@ -218,7 +217,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
            Math.min(1 / MIN_PROGRESS_FOR_OVERVIEW, 1 / (1 - MIN_PROGRESS_FOR_OVERVIEW));
    private static final String SCREENSHOT_CAPTURED_EVT = "ScreenshotCaptured";

    private static final long SHELF_ANIM_DURATION = 120;
    private static final long SHELF_ANIM_DURATION = 240;
    public static final long RECENTS_ATTACH_DURATION = 300;

    // Start resisting when swiping past this factor of mTransitionDragLength.
@@ -602,7 +601,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
    }

    public void onMotionPauseChanged(boolean isPaused) {
        setShelfState(isPaused ? PEEK : HIDE, FAST_OUT_SLOW_IN, SHELF_ANIM_DURATION);
        setShelfState(isPaused ? PEEK : HIDE, OVERSHOOT_1_2, SHELF_ANIM_DURATION);
    }

    public void maybeUpdateRecentsAttachedState() {
@@ -625,25 +624,27 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
                : mRecentsAnimationWrapper.targetSet.findTask(mRunningTaskId);
        final boolean recentsAttachedToAppWindow;
        int runningTaskIndex = mRecentsView.getRunningTaskIndex();
        if (mContinuingLastGesture) {
        if (mGestureEndTarget != null) {
            recentsAttachedToAppWindow = mGestureEndTarget.recentsAttachedToAppWindow;
        } else if (mContinuingLastGesture
                && mRecentsView.getRunningTaskIndex() != mRecentsView.getNextPage()) {
            recentsAttachedToAppWindow = true;
            animate = false;
        } else if (runningTaskTarget != null && isNotInRecents(runningTaskTarget)) {
            // The window is going away so make sure recents is always visible in this case.
            recentsAttachedToAppWindow = true;
            animate = false;
        } else {
            if (mGestureEndTarget != null) {
                recentsAttachedToAppWindow = mGestureEndTarget.recentsAttachedToAppWindow;
        } else {
            recentsAttachedToAppWindow = mIsShelfPeeking || mIsLikelyToStartNewTask;
            }
            if (animate) {
                // Only animate if an adjacent task view is visible on screen.
                TaskView adjacentTask1 = mRecentsView.getTaskViewAt(runningTaskIndex + 1);
                TaskView adjacentTask2 = mRecentsView.getTaskViewAt(runningTaskIndex - 1);
                float prevTranslationX = mRecentsView.getTranslationX();
                mRecentsView.setTranslationX(0);
                animate = (adjacentTask1 != null && adjacentTask1.getGlobalVisibleRect(TEMP_RECT))
                        || (adjacentTask2 != null && adjacentTask2.getGlobalVisibleRect(TEMP_RECT));
                mRecentsView.setTranslationX(prevTranslationX);
            }
        }
        mAnimationFactory.setRecentsAttachedToAppWindow(recentsAttachedToAppWindow, animate);
@@ -701,13 +702,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>

        SwipeAnimationTargetSet controller = mRecentsAnimationWrapper.getController();
        if (controller != null) {
            float offsetX = 0;
            if (mRecentsView != null) {
                int startScroll = mRecentsView.getScrollForPage(mRecentsView.indexOfChild(
                        mRecentsView.getRunningTaskView()));
                offsetX = startScroll - mRecentsView.getScrollX();
                offsetX *= mRecentsView.getScaleX();
            }
            float offsetX = mRecentsView == null ? 0 : mRecentsView.getScrollOffset();
            float offsetScale = getTaskCurveScaleForOffsetX(offsetX,
                    mClipAnimationHelper.getTargetRect().width());
            mTransformParams.setProgress(shift).setOffsetX(offsetX).setOffsetScale(offsetScale);
@@ -1217,6 +1212,9 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
                if (mRecentsView != null) {
                    mRecentsView.post(mRecentsView::resetTaskVisuals);
                }
                // Make sure recents is in its final state
                maybeUpdateRecentsAttachedState(false);
                mActivityControlHelper.onSwipeUpToHomeComplete(mActivity);
            }
        });
        return anim;
+10 −0
Original line number Diff line number Diff line
@@ -1676,6 +1676,16 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
        return mClearAllButton;
    }

    /**
     * @return How many pixels the running task is offset on the x-axis due to the current scrollX.
     */
    public float getScrollOffset() {
        int startScroll = getScrollForPage(getRunningTaskIndex());
        int offsetX = startScroll - getScrollX();
        offsetX *= getScaleX();
        return offsetX;
    }

    public Consumer<MotionEvent> getEventDispatcher(RotationMode rotationMode) {
        if (rotationMode.isTransposed) {
            Matrix transform = new Matrix();
+1 −0
Original line number Diff line number Diff line
@@ -53,6 +53,7 @@ public interface ActivityControlHelper<T extends BaseDraggingActivity> {

    void onSwipeUpToRecentsComplete(T activity);

    default void onSwipeUpToHomeComplete(T activity) { }
    void onAssistantVisibilityChanged(float visibility);

    @NonNull HomeAnimationFactory prepareHomeUI(T activity);