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

Commit eac1dac2 authored by Hyunyoung Song's avatar Hyunyoung Song
Browse files

All apps pull up work

b/28917826
b/29469966
b/29542376

- Move state transition to when the finger is lifted and not when
the view settles.
- Refactor the vertical pull detector to use bit operation to define
which direction scroll to allow.
- Fixed many issues regarding screen rotation
- Fixes issue where slowly sliding the all apps goes into overview mode
- Fixes issue where quick slide up leads to animation starting from middle

Change-Id: I2384a0fcc5550e17359a044ef506bcc66507da5f
parent 314d53fb
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -88,6 +88,7 @@
    <dimen name="all_apps_search_bar_bg_overflow">-6dp</dimen>
    <dimen name="all_apps_search_bar_divider_width">1dp</dimen>

    <dimen name="all_apps_bezel_swipe_height">24dp</dimen>
<!-- Widget tray -->
    <dimen name="widget_preview_label_vertical_padding">8dp</dimen>
    <dimen name="widget_preview_label_horizontal_padding">8dp</dimen>
+13 −19
Original line number Diff line number Diff line
@@ -420,7 +420,7 @@ public class LauncherStateTransitionAnimation {
                      pCb.onTransitionComplete();
                  }
            });
            mAllAppsController.animateToAllApps(animation, revealDuration);
            mAllAppsController.animateToAllApps(animation, revealDuration, false);

            dispatchOnLauncherTransitionPrepare(fromView, animated, false);
            dispatchOnLauncherTransitionPrepare(toView, animated, false);
@@ -861,37 +861,31 @@ public class LauncherStateTransitionAnimation {
            return animation;
        } else if (animType == PULLUP) {
            animation.addListener(new AnimatorListenerAdapter() {
                boolean canceled = false;
                @Override
                public void onAnimationEnd(Animator animation) {
                    dispatchOnLauncherTransitionEnd(fromView, animated, false);
                    dispatchOnLauncherTransitionEnd(toView, animated, false);
                    cleanupAnimation();
                    pCb.onTransitionComplete();
                public void onAnimationCancel(Animator animation) {
                    canceled = true;
                }

            });
            mAllAppsController.animateToWorkspace(animation, revealDuration);

            // Dispatch the prepare transition signal
            dispatchOnLauncherTransitionPrepare(fromView, animated, false);
            dispatchOnLauncherTransitionPrepare(toView, animated, false);

            animation.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    dispatchOnLauncherTransitionEnd(fromView, animated, true);
                    dispatchOnLauncherTransitionEnd(toView, animated, true);

                    if (canceled) return;
                    dispatchOnLauncherTransitionEnd(fromView, animated, false);
                    dispatchOnLauncherTransitionEnd(toView, animated, false);
                    // Run any queued runnables
                    if (onCompleteRunnable != null) {
                        onCompleteRunnable.run();
                    }

                    // This can hold unnecessary references to views.
                    cleanupAnimation();
                    pCb.onTransitionComplete();
                }

            });
            mAllAppsController.animateToWorkspace(animation, revealDuration, false);

            // Dispatch the prepare transition signal
            dispatchOnLauncherTransitionPrepare(fromView, animated, false);
            dispatchOnLauncherTransitionPrepare(toView, animated, false);

            final AnimatorSet stateAnimation = animation;
            final Runnable startAnimRunnable = new Runnable() {
+18 −2
Original line number Diff line number Diff line
@@ -30,6 +30,7 @@ import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.animation.DecelerateInterpolator;

import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.util.Thunk;

@@ -43,6 +44,7 @@ class AlphaUpdateListener extends AnimatorListenerAdapter implements ValueAnimat

    private View mView;
    private boolean mAccessibilityEnabled;
    private boolean mCanceled = false;

    public AlphaUpdateListener(View v, boolean accessibilityEnabled) {
        mView = v;
@@ -66,8 +68,14 @@ class AlphaUpdateListener extends AnimatorListenerAdapter implements ValueAnimat
        }
    }

    @Override
    public void onAnimationCancel(Animator animation) {
        mCanceled = true;
    }

    @Override
    public void onAnimationEnd(Animator arg0) {
        if (mCanceled) return;
        updateVisibility(mView, mAccessibilityEnabled);
    }

@@ -322,8 +330,10 @@ public class WorkspaceStateTransitionAnimation {
            boolean isCurrentPage = (i == toPage);
            float initialAlpha = cl.getShortcutsAndWidgets().getAlpha();
            float finalAlpha;
            if (states.stateIsNormalHidden || states.stateIsOverviewHidden) {
            if (states.stateIsOverviewHidden) {
                finalAlpha = 0f;
            } else if(states.stateIsNormalHidden) {
                finalAlpha = FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP  ? 1 : 0;
            } else if (states.stateIsNormal && mWorkspaceFadeInAdjacentScreens) {
                finalAlpha = (i == toPage || i < customPageCount) ? 1f : 0f;
            } else {
@@ -447,10 +457,16 @@ public class WorkspaceStateTransitionAnimation {
            mStateAnimator.play(hotseatAlpha);
            mStateAnimator.play(pageIndicatorAlpha);
            mStateAnimator.addListener(new AnimatorListenerAdapter() {
                boolean canceled = false;
                @Override
                public void onAnimationCancel(Animator animation) {
                    canceled = true;
                }

                @Override
                public void onAnimationEnd(Animator animation) {
                    mStateAnimator = null;

                    if (canceled) return;
                    if (accessibilityEnabled && overviewPanel.getVisibility() == View.VISIBLE) {
                        overviewPanel.getChildAt(0).performAccessibilityAction(
                                AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
+148 −94
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@ import com.android.launcher3.Hotseat;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAnimUtils;
import com.android.launcher3.PagedView;
import com.android.launcher3.R;
import com.android.launcher3.Workspace;
import com.android.launcher3.Workspace.Direction;
import com.android.launcher3.util.TouchController;
@@ -39,8 +40,8 @@ public class AllAppsTransitionController implements TouchController, VerticalPul
    private final Interpolator mAccelInterpolator = new AccelerateInterpolator(2f);
    private final Interpolator mDecelInterpolator = new DecelerateInterpolator(1f);

    private static final float ANIMATION_DURATION = 2000;
    public static final float ALL_APPS_FINAL_ALPHA = .8f;
    private static final float ANIMATION_DURATION = 1200;
    public static final float ALL_APPS_FINAL_ALPHA = .9f;

    private static final float PARALLAX_COEFFICIENT = .125f;

@@ -54,33 +55,34 @@ public class AllAppsTransitionController implements TouchController, VerticalPul
    private final Launcher mLauncher;
    private final VerticalPullDetector mDetector;

    // Animation in this class is controlled by a single variable {@link mProgressTransY}.
    // Animation in this class is controlled by a single variable {@link mShiftCurrent}.
    // Visually, it represents top y coordinate of the all apps container. Using the
    // {@link mTranslation} as the denominator, this fraction value ranges in [0, 1].
    private float mProgressTransY;   // numerator
    private float mTranslation = -1; // denominator
    // {@link mShiftRange} as the denominator, this fraction value ranges in [0, 1].
    //
    // When {@link mShiftCurrent} is 0, all apps container is pulled up.
    // When {@link mShiftCurrent} is {@link mShirtRange}, all apps container is pulled down.
    private float mShiftStart;      // [0, mShiftRange]
    private float mShiftCurrent;    // [0, mShiftRange]
    private float mShiftRange;      // changes depending on the orientation

    private static final float RECATCH_REJECTION_FRACTION = .0875f;

    // Used in landscape.
    private static final float BAZEL_PULL_UP_HEIGHT = 60;
    private static final float RECATCH_REJECTION_FRACTION = .0875f;

    private int mBezelSwipeUpHeight;
    private long mAnimationDuration;
    private float mCurY;


    private AnimatorSet mCurrentAnimation;
    private boolean mNoIntercept;

    private boolean mLightStatusBar;

    // At the end of scroll settling, this class also sets the state of the launcher.
    // If it's already set,do not call the #mLauncher.setXXX method.
    private boolean mStateAlreadyChanged;

    public AllAppsTransitionController(Launcher launcher) {
        mLauncher = launcher;
        mDetector = new VerticalPullDetector(launcher);
        mDetector.setListener(this);
        mBezelSwipeUpHeight = launcher.getResources().getDimensionPixelSize(
                R.dimen.all_apps_bezel_swipe_height);
    }

    @Override
@@ -95,27 +97,50 @@ public class AllAppsTransitionController implements TouchController, VerticalPul
            } else if (!mLauncher.isAllAppsVisible() && !shouldPossiblyIntercept(ev)) {
                mNoIntercept = true;
            } else {
                mDetector.setDetectableScrollConditions(mLauncher.isAllAppsVisible() /* down */,
                        isInDisallowRecatchTopZone(), isInDisallowRecatchBottomZone());
                // Now figure out which direction scroll events the controller will start
                // calling the callbacks.
                int conditionsToReportScroll = 0;

                if (mDetector.isRestingState()) {
                    if (mLauncher.isAllAppsVisible()) {
                        conditionsToReportScroll |= VerticalPullDetector.THRESHOLD_DOWN;
                    } else {
                        conditionsToReportScroll |= VerticalPullDetector.THRESHOLD_UP;
                    }
                } else {
                    if (isInDisallowRecatchBottomZone()) {
                        conditionsToReportScroll |= VerticalPullDetector.THRESHOLD_UP;
                    } else if (isInDisallowRecatchTopZone()) {
                        conditionsToReportScroll |= VerticalPullDetector.THRESHOLD_DOWN;
                    } else {
                        conditionsToReportScroll |= VerticalPullDetector.THRESHOLD_ONLY;
                    }
                }
                mDetector.setDetectableScrollConditions(conditionsToReportScroll);
            }
        }
        if (mNoIntercept) {
            return false;
        }
        mDetector.onTouchEvent(ev);
        if (mDetector.isScrollingState() && (isInDisallowRecatchBottomZone() || isInDisallowRecatchTopZone())) {
            return false;
        }
        return mDetector.shouldIntercept();
    }

    private boolean shouldPossiblyIntercept(MotionEvent ev) {
        DeviceProfile grid = mLauncher.getDeviceProfile();
        if (mDetector.isRestingState()) {
            if (mLauncher.getDragLayer().isEventOverHotseat(ev) && !grid.isLandscape) {
            if (grid.isVerticalBarLayout()) {
                if (ev.getY() > mLauncher.getDeviceProfile().heightPx - mBezelSwipeUpHeight) {
                    return true;
                }
            if (ev.getY() > mLauncher.getDeviceProfile().heightPx - BAZEL_PULL_UP_HEIGHT &&
                    grid.isLandscape) {
            } else {
                if (mLauncher.getDragLayer().isEventOverHotseat(ev) && !grid.isVerticalBarLayout()) {
                    return true;
                }
            }
            return false;
        } else {
            return true;
@@ -128,32 +153,92 @@ public class AllAppsTransitionController implements TouchController, VerticalPul
    }

    private boolean isInDisallowRecatchTopZone() {
        return mProgressTransY / mTranslation < RECATCH_REJECTION_FRACTION;
        return mShiftCurrent / mShiftRange < RECATCH_REJECTION_FRACTION;
    }

    private boolean isInDisallowRecatchBottomZone() {
        return mProgressTransY / mTranslation > 1 - RECATCH_REJECTION_FRACTION;
        return mShiftCurrent / mShiftRange > 1 - RECATCH_REJECTION_FRACTION;
    }

    @Override
    public void onScrollStart(boolean start) {
        cancelAnimation();
        mCurrentAnimation = LauncherAnimUtils.createAnimatorSet();
        mShiftStart = mAppsView.getTranslationY();
        preparePull(start);
    }

    @Override
    public boolean onScroll(float displacement, float velocity) {
        if (mAppsView == null) {
            return false;   // early termination.
        }
        if (0 <= mShiftStart + displacement && mShiftStart + displacement < mShiftRange) {
            setProgress(mShiftStart + displacement);
        }
        return true;
    }

    @Override
    public void onScrollEnd(float velocity, boolean fling) {
        if (mAppsView == null) {
            return; // early termination.
        }

        if (fling) {
            if (velocity < 0) {
                calculateDuration(velocity, mAppsView.getTranslationY());

                if (!mLauncher.isAllAppsVisible()) {
                    mLauncher.showAppsView(true, true, false, false);
                } else {
                    animateToAllApps(mCurrentAnimation, mAnimationDuration, true);
                }
            } else {
                calculateDuration(velocity, Math.abs(mShiftRange - mAppsView.getTranslationY()));
                if (mLauncher.isAllAppsVisible()) {
                    mLauncher.showWorkspace(true);
                } else {
                    animateToWorkspace(mCurrentAnimation, mAnimationDuration, true);
                }
            }
            // snap to top or bottom using the release velocity
        } else {
            if (mAppsView.getTranslationY() > mShiftRange / 2) {
                calculateDuration(velocity, Math.abs(mShiftRange - mAppsView.getTranslationY()));
                if (mLauncher.isAllAppsVisible()) {
                    mLauncher.showWorkspace(true);
                } else {
                    animateToWorkspace(mCurrentAnimation, mAnimationDuration, true);
                }
            } else {
                calculateDuration(velocity, Math.abs(mAppsView.getTranslationY()));
                if (!mLauncher.isAllAppsVisible()) {
                    mLauncher.showAppsView(true, true, false, false);
                } else {
                    animateToAllApps(mCurrentAnimation, mAnimationDuration, true);
                }

            }
        }
    }
    /**
     * @param start {@code true} if start of new drag.
     */
    public void preparePull(boolean start) {
        if (start) {
            // Initialize values that should not change until #onScrollEnd
        mCurY = mAppsView.getTranslationY();
            mStatusBarHeight = mLauncher.getDragLayer().getInsets().top;
            mHotseat.setVisibility(View.VISIBLE);
            mHotseat.bringToFront();
        if (start) {
            if (!mLauncher.getDeviceProfile().isVerticalBarLayout()) {
                mShiftRange = mHotseat.getTop();
            } else {
                mShiftRange = mHotseat.getBottom();
            }
            if (!mLauncher.isAllAppsVisible()) {
                mLauncher.tryAndUpdatePredictedApps();

                mHotseatBackgroundAlpha = mHotseat.getBackground().getAlpha() / 255f;
                mHotseat.setBackgroundTransparent(true /* transparent */);
                mAppsView.setVisibility(View.VISIBLE);
@@ -163,12 +248,13 @@ public class AllAppsTransitionController implements TouchController, VerticalPul
                mAppsView.getRevealView().setAlpha(mHotseatBackgroundAlpha);

                DeviceProfile grid= mLauncher.getDeviceProfile();
                if (!grid.isLandscape) {
                    mTranslation = mHotseat.getTop();
                if (!grid.isVerticalBarLayout()) {
                    mShiftRange = mHotseat.getTop();
                } else {
                    mTranslation = mHotseat.getBottom();
                    mShiftRange = mHotseat.getBottom();
                }
                setProgress(mTranslation);
                mAppsView.getRevealView().setAlpha(mHotseatBackgroundAlpha);
                setProgress(mShiftRange);
            } else {
                // TODO: get rid of this workaround to override state change by workspace transition
                mWorkspace.onLauncherTransitionPrepare(mLauncher, false, false);
@@ -177,6 +263,8 @@ public class AllAppsTransitionController implements TouchController, VerticalPul
                child.setVisibility(View.VISIBLE);
                child.setAlpha(1f);
            }
        } else {
            setProgress(mShiftCurrent);
        }
    }

@@ -199,23 +287,12 @@ public class AllAppsTransitionController implements TouchController, VerticalPul
        mLightStatusBar = enable;
    }

    @Override
    public boolean onScroll(float displacement, float velocity) {
        if (mAppsView == null) {
            return false;   // early termination.
        }
        if (0 <= mCurY + displacement && mCurY + displacement < mTranslation) {
            setProgress(mCurY + displacement);
        }
        return true;
    }

    /**
     * @param progress y value of the border between hotseat and all apps
     */
    public void setProgress(float progress) {
        updateLightStatusBar(progress);
        mProgressTransY = progress;
        mShiftCurrent = progress;
        float alpha = calcAlphaAllApps(progress);
        float workspaceHotseatAlpha = 1 - alpha;

@@ -224,67 +301,45 @@ public class AllAppsTransitionController implements TouchController, VerticalPul
        mAppsView.getContentView().setAlpha(alpha);
        mAppsView.setTranslationY(progress);
        mWorkspace.setWorkspaceTranslation(Direction.Y,
                PARALLAX_COEFFICIENT *(-mTranslation + progress),
                PARALLAX_COEFFICIENT * (-mShiftRange + progress),
                mAccelInterpolator.getInterpolation(workspaceHotseatAlpha));
        mWorkspace.setHotseatTranslation(Direction.Y, -mTranslation + progress,
        if (!mLauncher.getDeviceProfile().isVerticalBarLayout()) {
            mWorkspace.setHotseatTranslation(Direction.Y, -mShiftRange + progress,
                    mAccelInterpolator.getInterpolation(workspaceHotseatAlpha));
        } else {
            mWorkspace.setHotseatTranslation(Direction.Y,
                    PARALLAX_COEFFICIENT * (-mShiftRange + progress),
                    mAccelInterpolator.getInterpolation(workspaceHotseatAlpha));
        }
    }

    public float getProgress() {
        return mProgressTransY;
        return mShiftCurrent;
    }

    private float calcAlphaAllApps(float progress) {
        return ((mTranslation - progress)/mTranslation);
    }

    @Override
    public void onScrollEnd(float velocity, boolean fling) {
        if (mAppsView == null) {
            return; // early termination.
        }

        if (fling) {
            if (velocity < 0) {
                calculateDuration(velocity, mAppsView.getTranslationY());
                animateToAllApps(mCurrentAnimation, mAnimationDuration);
            } else {
                calculateDuration(velocity, Math.abs(mTranslation - mAppsView.getTranslationY()));
                animateToWorkspace(mCurrentAnimation, mAnimationDuration);
            }
            // snap to top or bottom using the release velocity
        } else {
            if (mAppsView.getTranslationY() > mTranslation / 2) {
                calculateDuration(velocity, Math.abs(mTranslation - mAppsView.getTranslationY()));
                animateToWorkspace(mCurrentAnimation, mAnimationDuration);
            } else {
                calculateDuration(velocity, Math.abs(mAppsView.getTranslationY()));
                animateToAllApps(mCurrentAnimation, mAnimationDuration);
            }
        }
        mCurrentAnimation.start();
        return ((mShiftRange - progress)/ mShiftRange);
    }

    private void calculateDuration(float velocity, float disp) {
        // TODO: make these values constants after tuning.
        float velocityDivisor = Math.max(1.5f, Math.abs(0.5f * velocity));
        float travelDistance = Math.max(0.2f, disp / mTranslation);
        float travelDistance = Math.max(0.2f, disp / mShiftRange);
        mAnimationDuration = (long) Math.max(100, ANIMATION_DURATION / velocityDivisor * travelDistance);
        if (DBG) {
            Log.d(TAG, String.format("calculateDuration=%d, v=%f, d=%f", mAnimationDuration, velocity, disp));
        }
    }

    public void animateToAllApps(AnimatorSet animationOut, long duration) {
    public void animateToAllApps(AnimatorSet animationOut, long duration, boolean start) {
        if (animationOut == null){
            return;
        }
        if (mDetector.isRestingState()) {
            preparePull(true);
            mAnimationDuration = duration;
            mStateAlreadyChanged = true;
            mShiftStart = mAppsView.getTranslationY();
        }
        mCurY = mAppsView.getTranslationY();
        final float fromAllAppsTop = mAppsView.getTranslationY();
        final float toAllAppsTop = 0;

@@ -311,19 +366,22 @@ public class AllAppsTransitionController implements TouchController, VerticalPul
                }
            }});
        mCurrentAnimation = animationOut;
        if (start) {
            mCurrentAnimation.start();
        }
    }

    public void animateToWorkspace(AnimatorSet animationOut, long duration) {
    public void animateToWorkspace(AnimatorSet animationOut, long duration, boolean start) {
        if (animationOut == null){
            return;
        }
        if(mDetector.isRestingState()) {
            preparePull(true);
            mAnimationDuration = duration;
            mStateAlreadyChanged = true;
            mShiftStart = mAppsView.getTranslationY();
        }
        final float fromAllAppsTop = mAppsView.getTranslationY();
        final float toAllAppsTop = mTranslation;
        final float toAllAppsTop = mShiftRange;

        ObjectAnimator driftAndAlpha = ObjectAnimator.ofFloat(this, "progress",
                fromAllAppsTop, toAllAppsTop);
@@ -336,6 +394,7 @@ public class AllAppsTransitionController implements TouchController, VerticalPul
             @Override
             public void onAnimationCancel(Animator animation) {
                 canceled = true;
                 setProgress(mShiftCurrent);
             }

             @Override
@@ -349,16 +408,14 @@ public class AllAppsTransitionController implements TouchController, VerticalPul
                 }
             }});
        mCurrentAnimation = animationOut;
        if (start) {
            mCurrentAnimation.start();
        }
    }

    private void finishPullUp() {
        mHotseat.setVisibility(View.INVISIBLE);
        setProgress(0f);
        if (!mStateAlreadyChanged) {
            mLauncher.showAppsView(false /* animated */, true /* resetListToTop */,
                    false /* updatePredictedApps */, false /* focusSearchBar */);
        }
        mStateAlreadyChanged = false;
    }

    public void finishPullDown() {
@@ -368,15 +425,12 @@ public class AllAppsTransitionController implements TouchController, VerticalPul
        mAppsView.setVisibility(View.INVISIBLE);
        mHotseat.setBackgroundTransparent(false /* transparent */);
        mHotseat.setVisibility(View.VISIBLE);
        setProgress(mTranslation);
        if (!mStateAlreadyChanged) {
            mLauncher.showWorkspace(false);
        }
        mStateAlreadyChanged = false;
        setProgress(mShiftRange);
    }

    private void cancelAnimation() {
        if (mCurrentAnimation != null) {
            mCurrentAnimation.setDuration(0);
            mCurrentAnimation.cancel();
            mCurrentAnimation = null;
        }
+67 −56

File changed.

Preview size limit exceeded, changes collapsed.