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

Commit ad3194ec authored by Jon Miranda's avatar Jon Miranda
Browse files

Add overscroll w/ physics to All Apps.

Bug: 62628421
Bug: 38349031

Change-Id: If3ba6dfbbd3a4b1c87e69df0066f801f963752aa
parent d3922555
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -220,7 +220,7 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc
        });

        // Load the all apps recycler view
        mAppsRecyclerView = (AllAppsRecyclerView) findViewById(R.id.apps_list_view);
        mAppsRecyclerView = findViewById(R.id.apps_list_view);
        mAppsRecyclerView.setApps(mApps);
        mAppsRecyclerView.setLayoutManager(mLayoutManager);
        mAppsRecyclerView.setAdapter(mAdapter);
+126 −0
Original line number Diff line number Diff line
@@ -15,12 +15,14 @@
 */
package com.android.launcher3.allapps;

import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.util.Property;
import android.util.SparseIntArray;
import android.view.MotionEvent;
import android.view.View;
@@ -54,6 +56,22 @@ public class AllAppsRecyclerView extends BaseRecyclerView {
    private int mEmptySearchBackgroundTopOffset;

    private SpringAnimationHandler mSpringAnimationHandler;
    private OverScrollHelper mOverScrollHelper;
    private VerticalPullDetector mPullDetector;

    private float mContentTranslationY = 0;
    public static final Property<AllAppsRecyclerView, Float> CONTENT_TRANS_Y =
            new Property<AllAppsRecyclerView, Float>(Float.class, "appsRecyclerViewContentTransY") {
                @Override
                public Float get(AllAppsRecyclerView allAppsRecyclerView) {
                    return allAppsRecyclerView.getContentTranslationY();
                }

                @Override
                public void set(AllAppsRecyclerView allAppsRecyclerView, Float y) {
                    allAppsRecyclerView.setContentTranslationY(y);
                }
            };

    public AllAppsRecyclerView(Context context) {
        this(context, null);
@@ -74,14 +92,27 @@ public class AllAppsRecyclerView extends BaseRecyclerView {
        addOnItemTouchListener(this);
        mEmptySearchBackgroundTopOffset = res.getDimensionPixelSize(
                R.dimen.all_apps_empty_search_bg_top_offset);

        mOverScrollHelper = new OverScrollHelper();
        mPullDetector = new VerticalPullDetector(getContext());
        mPullDetector.setListener(mOverScrollHelper);
        mPullDetector.setDetectableScrollConditions(VerticalPullDetector.DIRECTION_UP
                | VerticalPullDetector.DIRECTION_DOWN, true);
    }

    public void setSpringAnimationHandler(SpringAnimationHandler springAnimationHandler) {
        mSpringAnimationHandler = springAnimationHandler;
    }

    @Override
    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent ev) {
        mPullDetector.onTouchEvent(ev);
        return super.onInterceptTouchEvent(rv, ev) || mOverScrollHelper.isInOverScroll();
    }

    @Override
    public boolean onTouchEvent(MotionEvent e) {
        mPullDetector.onTouchEvent(e);
        if (FeatureFlags.LAUNCHER3_PHYSICS && mSpringAnimationHandler != null) {
            mSpringAnimationHandler.addMovement(e);
        }
@@ -168,6 +199,8 @@ public class AllAppsRecyclerView extends BaseRecyclerView {

    @Override
    public void onDraw(Canvas c) {
        c.translate(0, mContentTranslationY);

        // Draw the background
        if (mEmptySearchBackground != null && mEmptySearchBackground.getAlpha() > 0) {
            mEmptySearchBackground.draw(c);
@@ -176,6 +209,19 @@ public class AllAppsRecyclerView extends BaseRecyclerView {
        super.onDraw(c);
    }

    public float getContentTranslationY() {
        return mContentTranslationY;
    }

    /**
     * Use this method instead of calling {@link #setTranslationY(float)}} directly to avoid drawing
     * on top of other Views.
     */
    public void setContentTranslationY(float y) {
        mContentTranslationY = y;
        invalidate();
    }

    @Override
    protected boolean verifyDrawable(Drawable who) {
        return who == mEmptySearchBackground || super.verifyDrawable(who);
@@ -434,4 +480,84 @@ public class AllAppsRecyclerView extends BaseRecyclerView {
                y + mEmptySearchBackground.getIntrinsicHeight());
    }

    private class OverScrollHelper implements VerticalPullDetector.Listener {

        private static final float MAX_RELEASE_VELOCITY = 5000; // px / s
        private static final float MAX_OVERSCROLL_PERCENTAGE = 0.07f;

        private boolean mIsInOverScroll;

        @Override
        public void onDragStart(boolean start) {
        }

        @Override
        public boolean onDrag(float displacement, float velocity) {
            // We are in overscroll iff we are trying to drag further down when we're already at
            // the bottom of All Apps.
            mIsInOverScroll = !canScrollVertically(1) && displacement < 0;

            if (mIsInOverScroll) {
                displacement = getDampedOverScroll(displacement);
                setContentTranslationY(displacement);
            }
            return mIsInOverScroll;
        }

        @Override
        public void onDragEnd(float velocity, boolean fling) {
            float y = getContentTranslationY();
            if (mIsInOverScroll && Float.compare(y, 0) != 0) {
                if (FeatureFlags.LAUNCHER3_PHYSICS) {
                    // We calculate our own velocity to give the springs the desired effect.
                    velocity = y / getDampedOverScroll(getHeight()) * MAX_RELEASE_VELOCITY;
                    mSpringAnimationHandler.animateToPositionWithVelocity(0, -velocity);
                }

                ObjectAnimator.ofFloat(AllAppsRecyclerView.this,
                        AllAppsRecyclerView.CONTENT_TRANS_Y, 0)
                        .setDuration(100)
                        .start();
            }
            mIsInOverScroll = false;
        }

        public boolean isInOverScroll() {
            return mIsInOverScroll;
        }

        private float getDampedOverScroll(float y) {
            return dampedOverScroll(y, getHeight()) * MAX_OVERSCROLL_PERCENTAGE;
        }

        /**
         * This curve determines how the effect of scrolling over the limits of the page diminishes
         * as the user pulls further and further from the bounds
         *
         * @param f The percentage of how much the user has overscrolled.
         * @return A transformed percentage based on the influence curve.
         */
        private float overScrollInfluenceCurve(float f) {
            f -= 1.0f;
            return f * f * f + 1.0f;
        }

        /**
         * @param amount The original amount overscrolled.
         * @param max The maximum amount that the View can overscroll.
         * @return The dampened overscroll amount.
         */
        private float dampedOverScroll(float amount, float max) {
            float f = amount / max;
            if (Float.compare(f, 0) == 0) return 0;
            f = f / (Math.abs(f)) * (overScrollInfluenceCurve(Math.abs(f)));

            // Clamp this factor, f, to -1 < f < 1
            if (Math.abs(f) >= 1) {
                f /= Math.abs(f);
            }

            return Math.round(f * max);
        }
    }
}
+15 −0
Original line number Diff line number Diff line
@@ -127,6 +127,19 @@ public class SpringAnimationHandler<T> {
        reset();
    }

    /**
     * Similar to {@link #animateToFinalPosition(float)}, but used in cases where we want to
     * manually set the velocity.
     */
    public void animateToPositionWithVelocity(float position, float velocity) {
        if (DEBUG) Log.d(TAG, "animateToPosition#velocity=" + velocity);

        setStartVelocity(velocity);
        mShouldComputeVelocity = false;
        animateToFinalPosition(position);
    }


    public boolean isRunning() {
        // All the animations run at the same time so we can just check the first one.
        return !mAnimations.isEmpty() && mAnimations.get(0).isRunning();
@@ -153,6 +166,8 @@ public class SpringAnimationHandler<T> {
    }

    private void setStartVelocity(float velocity) {
        if (DEBUG) Log.d(TAG, "setStartVelocity=" + velocity);

        int size = mAnimations.size();
        for (int i = 0; i < size; ++i) {
            mAnimations.get(i).setStartVelocity(velocity);