From ad3194ec81a2eb1d5823dd892fc8590c613c991b Mon Sep 17 00:00:00 2001 From: Jon Miranda Date: Wed, 7 Jun 2017 17:01:35 -0700 Subject: [PATCH] Add overscroll w/ physics to All Apps. Bug: 62628421 Bug: 38349031 Change-Id: If3ba6dfbbd3a4b1c87e69df0066f801f963752aa --- .../allapps/AllAppsContainerView.java | 2 +- .../allapps/AllAppsRecyclerView.java | 126 ++++++++++++++++++ .../anim/SpringAnimationHandler.java | 15 +++ 3 files changed, 142 insertions(+), 1 deletion(-) diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java index 47b68a2ee1..189b9358f9 100644 --- a/src/com/android/launcher3/allapps/AllAppsContainerView.java +++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java @@ -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); diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java index 2b2fddcdd2..a2bd43d848 100644 --- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java +++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java @@ -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 CONTENT_TRANS_Y = + new Property(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); + } + } } diff --git a/src/com/android/launcher3/anim/SpringAnimationHandler.java b/src/com/android/launcher3/anim/SpringAnimationHandler.java index 038f82682c..1efc4e4e11 100644 --- a/src/com/android/launcher3/anim/SpringAnimationHandler.java +++ b/src/com/android/launcher3/anim/SpringAnimationHandler.java @@ -127,6 +127,19 @@ public class SpringAnimationHandler { 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 { } 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);