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

Commit 3006c811 authored by George Mount's avatar George Mount
Browse files

Make internal recycler view use stretch edge effect

Bug: 171228096

The internal RecyclerView now supports stretch overscroll.

Implementation taken from androidx-platform-dev RecyclerView.

Test: manual testing with share sheet

Change-Id: I2b68530baf5d85438ec4483ab940f47d2fc8f11c
parent 659dc01e
Loading
Loading
Loading
Loading
+143 −1
Original line number Diff line number Diff line
@@ -16,10 +16,16 @@
package com.android.internal.widget;
import static android.widget.EdgeEffect.TYPE_GLOW;
import static android.widget.EdgeEffect.TYPE_STRETCH;
import static android.widget.EdgeEffect.USE_STRETCH_EDGE_EFFECT_BY_DEFAULT;
import static android.widget.EdgeEffect.USE_STRETCH_EDGE_EFFECT_FOR_SUPPORTED;
import android.annotation.CallSuper;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.Compatibility;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.TypedArray;
@@ -460,6 +466,8 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro
    private final int[] mScrollConsumed = new int[2];
    private final int[] mNestedOffsets = new int[2];
    private int mEdgeEffectType;
    /**
     * These are views that had their a11y importance changed during a layout. We defer these events
     * until the end of the layout because a11y service may make sync calls back to the RV while
@@ -587,6 +595,14 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro
            setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
        }
        boolean defaultToStretch = Compatibility.isChangeEnabled(USE_STRETCH_EDGE_EFFECT_BY_DEFAULT)
                || Compatibility.isChangeEnabled(USE_STRETCH_EDGE_EFFECT_FOR_SUPPORTED);
        TypedArray a = context.obtainStyledAttributes(attrs,
                com.android.internal.R.styleable.EdgeEffect);
        mEdgeEffectType = a.getInt(com.android.internal.R.styleable.EdgeEffect_edgeEffectType,
                defaultToStretch ? TYPE_STRETCH : TYPE_GLOW);
        a.recycle();
        // Re-set whether nested scrolling is enabled so that it is set on all API levels
        setNestedScrollingEnabled(nestedScrollingEnabled);
    }
@@ -609,6 +625,28 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro
        setAccessibilityDelegate(mAccessibilityDelegate);
    }
    /**
     * Returns the {@link EdgeEffect#getType()} used for all EdgeEffects.
     *
     * @return @link EdgeEffect#getType()} used for all EdgeEffects.
     */
    @EdgeEffect.EdgeEffectType
    public int getEdgeEffectType() {
        return mEdgeEffectType;
    }
    /**
     * Sets the {@link EdgeEffect#getType()} used in all EdgeEffects.
     * Any existing over-scroll effects are cleared and new effects are created as needed.
     *
     * @param type the {@link EdgeEffect#getType()} used in all EdgeEffects.
     */
    public void setEdgeEffectType(@EdgeEffect.EdgeEffectType int type) {
        mEdgeEffectType = type;
        invalidateGlows();
        invalidate();
    }
    /**
     * Instantiate and set a LayoutManager, if specified in the attributes.
     */
@@ -2183,6 +2221,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro
            return;
        }
        mLeftGlow = new EdgeEffect(getContext());
        mLeftGlow.setType(mEdgeEffectType);
        if (mClipToPadding) {
            mLeftGlow.setSize(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(),
                    getMeasuredWidth() - getPaddingLeft() - getPaddingRight());
@@ -2196,6 +2235,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro
            return;
        }
        mRightGlow = new EdgeEffect(getContext());
        mRightGlow.setType(mEdgeEffectType);
        if (mClipToPadding) {
            mRightGlow.setSize(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(),
                    getMeasuredWidth() - getPaddingLeft() - getPaddingRight());
@@ -2209,6 +2249,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro
            return;
        }
        mTopGlow = new EdgeEffect(getContext());
        mTopGlow.setType(mEdgeEffectType);
        if (mClipToPadding) {
            mTopGlow.setSize(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(),
                    getMeasuredHeight() - getPaddingTop() - getPaddingBottom());
@@ -2223,6 +2264,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro
            return;
        }
        mBottomGlow = new EdgeEffect(getContext());
        mBottomGlow.setType(mEdgeEffectType);
        if (mClipToPadding) {
            mBottomGlow.setSize(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(),
                    getMeasuredHeight() - getPaddingTop() - getPaddingBottom());
@@ -2663,7 +2705,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro
                mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f);
                mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f);
                if (mScrollState == SCROLL_STATE_SETTLING) {
                if (stopGlowAnimations(e) || mScrollState == SCROLL_STATE_SETTLING) {
                    getParent().requestDisallowInterceptTouchEvent(true);
                    setScrollState(SCROLL_STATE_DRAGGING);
                }
@@ -2731,6 +2773,38 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro
        return mScrollState == SCROLL_STATE_DRAGGING;
    }
    /**
     * This stops any edge glow animation that is currently running by applying a
     * 0 length pull at the displacement given by the provided MotionEvent. On pre-S devices,
     * this method does nothing, allowing any animating edge effect to continue animating and
     * returning <code>false</code> always.
     *
     * @param e The motion event to use to indicate the finger position for the displacement of
     *          the current pull.
     * @return <code>true</code> if any edge effect had an existing effect to be drawn ond the
     * animation was stopped or <code>false</code> if no edge effect had a value to display.
     */
    private boolean stopGlowAnimations(MotionEvent e) {
        boolean stopped = false;
        if (mLeftGlow != null && mLeftGlow.getDistance() != 0) {
            mLeftGlow.onPullDistance(0, 1 - (e.getY() / getHeight()));
            stopped = true;
        }
        if (mRightGlow != null && mRightGlow.getDistance() != 0) {
            mRightGlow.onPullDistance(0, e.getY() / getHeight());
            stopped = true;
        }
        if (mTopGlow != null && mTopGlow.getDistance() != 0) {
            mTopGlow.onPullDistance(0, e.getX() / getWidth());
            stopped = true;
        }
        if (mBottomGlow != null && mBottomGlow.getDistance() != 0) {
            mBottomGlow.onPullDistance(0, 1 - e.getX() / getWidth());
            stopped = true;
        }
        return stopped;
    }
    @Override
    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
        final int listenerCount = mOnItemTouchListeners.size();
@@ -2807,6 +2881,8 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro
                final int y = (int) (e.getY(index) + 0.5f);
                int dx = mLastTouchX - x;
                int dy = mLastTouchY - y;
                dx -= releaseHorizontalGlow(dx, e.getY());
                dy -= releaseVerticalGlow(dy, e.getX());
                if (dispatchNestedPreScroll(dx, dy, mScrollConsumed, mScrollOffset)) {
                    dx -= mScrollConsumed[0];
@@ -2887,6 +2963,72 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro
        return true;
    }
    /**
     * If either of the horizontal edge glows are currently active, this consumes part or all of
     * deltaX on the edge glow.
     *
     * @param deltaX The pointer motion, in pixels, in the horizontal direction, positive
     *                         for moving down and negative for moving up.
     * @param y The vertical position of the pointer.
     * @return The amount of <code>deltaX</code> that has been consumed by the
     * edge glow.
     */
    private int releaseHorizontalGlow(int deltaX, float y) {
        // First allow releasing existing overscroll effect:
        float consumed = 0;
        float displacement = y / getHeight();
        float pullDistance = (float) deltaX / getWidth();
        if (mLeftGlow != null && mLeftGlow.getDistance() != 0) {
            consumed = -mLeftGlow.onPullDistance(-pullDistance, 1 - displacement);
            if (mLeftGlow.getDistance() == 0) {
                mLeftGlow.onRelease();
            }
        } else if (mRightGlow != null && mRightGlow.getDistance() != 0) {
            consumed = mRightGlow.onPullDistance(pullDistance, displacement);
            if (mRightGlow.getDistance() == 0) {
                mRightGlow.onRelease();
            }
        }
        int pixelsConsumed = Math.round(consumed * getWidth());
        if (pixelsConsumed != 0) {
            invalidate();
        }
        return pixelsConsumed;
    }
    /**
     * If either of the vertical edge glows are currently active, this consumes part or all of
     * deltaY on the edge glow.
     *
     * @param deltaY The pointer motion, in pixels, in the vertical direction, positive
     *                         for moving down and negative for moving up.
     * @param x The vertical position of the pointer.
     * @return The amount of <code>deltaY</code> that has been consumed by the
     * edge glow.
     */
    private int releaseVerticalGlow(int deltaY, float x) {
        // First allow releasing existing overscroll effect:
        float consumed = 0;
        float displacement = x / getWidth();
        float pullDistance = (float) deltaY / getHeight();
        if (mTopGlow != null && mTopGlow.getDistance() != 0) {
            consumed = -mTopGlow.onPullDistance(-pullDistance, displacement);
            if (mTopGlow.getDistance() == 0) {
                mTopGlow.onRelease();
            }
        } else if (mBottomGlow != null && mBottomGlow.getDistance() != 0) {
            consumed = mBottomGlow.onPullDistance(pullDistance, 1 - displacement);
            if (mBottomGlow.getDistance() == 0) {
                mBottomGlow.onRelease();
            }
        }
        int pixelsConsumed = Math.round(consumed * getHeight());
        if (pixelsConsumed != 0) {
            invalidate();
        }
        return pixelsConsumed;
    }
    private void resetTouch() {
        if (mVelocityTracker != null) {
            mVelocityTracker.clear();