让ScrollView拥有回弹效果

其实,本来ScrollView就拥有回弹效果,但不知道为什么没有开放出来,导致网上好多方案都是自己去实现一个自定义view。这篇文章就是要用一种简单的方式来实现回弹效果,我们只需要稍微修改下就可以了。
github地址:https://github.com/hunter0147/SpringScroll

首先看下最终成品的效果。


aafmm-46rgs.gif

因为我们是在对ScrollView进行扩展,所以第一步,新建一个类用来继承ScrollView。

class SpringScrollView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : ScrollView(context, attrs, defStyleAttr) {

接着重写overScrollBy方法,把maxOverScrollY参数改成你想要的最大回弹距离。ScrollView没有回弹效果就是这边原来传的maxOverScrollY是0,那肯定就没有回弹效果了。

    override fun overScrollBy(
        deltaX: Int,
        deltaY: Int,
        scrollX: Int,
        scrollY: Int,
        scrollRangeX: Int,
        scrollRangeY: Int,
        maxOverScrollX: Int,
        maxOverScrollY: Int,
        isTouchEvent: Boolean
    ): Boolean {
        return super.overScrollBy(
            deltaX,
            deltaY,
            scrollX,
            scrollY,
            scrollRangeX,
            scrollRangeY,
            maxOverScrollX,
            MAX_OVER_SCROLL_Y,
            isTouchEvent
        )
    }

这时候,已经支持了回弹。但是,这边还是有一个问题,如果我们来测试的话会发现慢划的时候x可以回弹,但是快划ScrollView却会停在滑动的位置,不会回弹了。
查看源码发现,在快划的时候走的是flingWithNestedDispatch逻辑,慢划的时候才走的mScroller.springBack,所以慢划才支持回弹。

            case MotionEvent.ACTION_UP:
                if (mIsBeingDragged) {
                    final VelocityTracker velocityTracker = mVelocityTracker;
                    velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
                    int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);

                    if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
                        flingWithNestedDispatch(-initialVelocity);
                    } else if (mScroller.springBack(mScrollX, mScrollY, 0, 0, 0,
                            getScrollRange())) {
                        postInvalidateOnAnimation();
                    }

                    mActivePointerId = INVALID_POINTER;
                    endDrag();
                }
                break;

为了让快划也支持回弹,我们还需要再重写一个方法dispatchNestedFling。在这个方法里面我们先用反射取到mScroller,再用mScroller去调用回弹方法springBack。

    private val scrollRange: Int
        get() = if (childCount > 0) {
            Math.max(0, getChildAt(0).height - (height - paddingBottom - paddingTop))
        } else 0

    override fun dispatchNestedFling(
        velocityX: Float,
        velocityY: Float,
        consumed: Boolean
    ): Boolean {
        if (!consumed) {
            try {
                val scrollview = ScrollView::class.java
                val scrollField = scrollview.getDeclaredField("mScroller")
                scrollField.isAccessible = true
                val scroller = scrollField.get(this) as OverScroller
                if (scroller.springBack(
                        scrollX, scrollY, 0, 0, 0,
                        scrollRange
                    )
                ) {
                    postInvalidateOnAnimation()
                }
            } catch (e: NoSuchFieldException) {
                e.printStackTrace()
            } catch (e: IllegalAccessException) {
                e.printStackTrace()
            }

        }
        return super.dispatchNestedFling(velocityX, velocityY, consumed)
    }

现在再试下,就是我们上面gif上的效果了。

你可能感兴趣的:(让ScrollView拥有回弹效果)