Android 下拉回弹BounceScrollView

Android的ScrollView默认是没有弹性回缩的,不像iOS拉到底部会再向下滑动一段距离然后像弹簧一样回退回来,Android的ScrollView拉到底部就是死板的一下子卡住了,给人很不爽的感觉。然后就想拓展一下ScrollView,让ScrollView在拉到底部或者顶部时能弹性回缩。
于是就先在网上找了一下,好多都是在onTouchEvent做处理,一看就感觉很麻烦,然后就很欣喜的从中找到一篇简单方法,只要重写overScrollBy方法就可以了!代码如下:

import android.content.Context;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.widget.ScrollView;

public class BounceScrollView extends ScrollView {
    // 这个值控制可以把ScrollView包裹的控件拉出偏离顶部或底部的距离。
    private static final int MAX_OVER_SCROLL_Y = 100;
    private int newMaxOverScrollY;

    public BounceScrollView(Context context) {
        super(context);
    }

    public BounceScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public BounceScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    private void init(Context context) {
        DisplayMetrics metrics = context.getResources().getDisplayMetrics();
        float density = metrics.density;
        newMaxOverScrollY = (int) (density * MAX_OVER_SCROLL_Y);
        //false:隐藏ScrollView的滚动条。
        this.setVerticalScrollBarEnabled(false);
        //不管装载的控件填充的数据是否满屏,都允许橡皮筋一样的弹性回弹。
        this.setOverScrollMode(ScrollView.OVER_SCROLL_ALWAYS);
    }

    @Override
    protected boolean overScrollBy(int deltaX, int deltaY, int scrollX,
                                   int scrollY, int scrollRangeX, int scrollRangeY,
                                   int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) {
        return super.overScrollBy(deltaX, (int) (deltaY * ratio), scrollX, scrollY,
                scrollRangeX, scrollRangeY, maxOverScrollX, newMaxOverScrollY,
                isTouchEvent);
    }
}

还有这等好事?吓得我赶紧试试。结果是果然可以欸,不错不错,原来Android内部早就实现了啊,为什么不给个接口呢,还隐藏这么深?
等等!还没等我继续开心下去,我就发现问题了。我就简简单单的划着划着,怎么偶尔view会卡住啊?我划出到底部一段距离后怎么不会自动回弹啊?这是怎么回事啊?Shit!果然便宜没好货!鉴于ScrollView源码又那么长,我就果断放弃了从源码中找原因的想法。
怎么办?继续百度吧,然后巴拉巴拉吭哧度娘了半天也没找到相关解决办法,然后灵机一动,去google一下吧。结果google也没给好结果。就在我打算放弃的时候,突然看到StackOverflow上的一个链接标题overScrollBy doesn’t always bounce back in Lollipop (5.x) platform,卧槽,好像有戏,赶紧进去看一下,果然有大牛给出了解决办法,只需要再重写一下dispatchNestedFling方法就OK了!

@Override
    public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
        // Not consumed means it wasn't handled because ScrollView
        // doesn't take over scrolling bounds into scroll range,
        // so we fling it ourselves to get it bounce back
        if (getOverScrollMode() == OVER_SCROLL_ALWAYS && !consumed) {
            fling((int) velocityY);
            return true;
        } else {
            return super.dispatchNestedFling(velocityX, velocityY, consumed);
        }
    }

本着要搞清楚为什么的想法,还是要看一下源码啊。原来是嵌套滑动的锅,手指滑动时间有可能被ScrollView的子View消费掉,导致ScrollView就没办法再收到滑动消息,因此就造成卡住的现象,所以在dispatchNestedFling中,如果consumed=false,就自己去fling一下。
事实上,在ScrollView中dispatchNestedFling被flingWithNestedDispatch调用了,

private void flingWithNestedDispatch(int velocityY) {
        final boolean canFling = (mScrollY > 0 || velocityY > 0) &&
                (mScrollY < getScrollRange() || velocityY < 0);
        if (!dispatchNestedPreFling(0, velocityY)) {
            dispatchNestedFling(0, velocityY, canFling);
            if (canFling) {
                fling(velocityY);
            }
        }
    }

如果是下拉越过ScrollRangeY,这时mScrollY>0是true,mScrollY < getScrollRange()是false,如果此时view卡住不滑动了,那么一定是因为velocityY < 0也是false的。为什么velocityY < 0是false?这就是因为此时的velocityY 是被子View消费过剩下的,所以有可能导致velocityY < 0也为false。说了一大堆,我自己也快绕晕了,其实主要是因为我对这部分逻辑也没深入理解,以上只是看了源码之后的自己的一点推测,如果说错了,还请一定指出来!
加上上面代码之后就大功告成了!然后再也不用担心滑动卡住了!但是总感觉少点什么,拉着没感觉,少点劲道,那就在代码中加点料吧(阻尼效果)。思路是这样的,view越是被拉的超出边界,滑动应该越吃力,表现在代码中就是给deltaY加个系数ratio,该系数与越界的距离成反比,因此就会有着越拉越吃力的感觉。下面只是简单了写了一个一次线性函数

    @Override
    protected boolean overScrollBy(int deltaX, int deltaY, int scrollX,
                                   int scrollY, int scrollRangeX, int scrollRangeY,
                                   int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) {
        //增加阻尼效果,使滑动有吃力感
        double ratio = 1d;
        //在顶部并且是向下拖动
        if (deltaY < 0 && scrollY + deltaY < 0) {
            ratio = 1.05d + scrollY / (newMaxOverScrollY * 1.2);
        } else if (deltaY > 0 && scrollY + deltaY > scrollRangeY) { //滑动到底部并且向下滑动
            ratio = 1.05d + (scrollRangeY - scrollY) / (newMaxOverScrollY * 1.2);
        }
        return super.overScrollBy(deltaX, (int) (deltaY * ratio), scrollX, scrollY,
                scrollRangeX, scrollRangeY, maxOverScrollX, newMaxOverScrollY,
                isTouchEvent);
    }

你可以自己写什么二次函数啦,指数函数啦,随便都行,但是注意边界条件,别让ratio太小,否则会发生意料不到的效果!(如果(newMaxOverScrollY-1) * ratio < 1,那你就完蛋了,不信可以试试~)
该文章还有下文,有兴趣的可以继续看下去Android 下拉回弹BounceScrollView, 同时去除EdgeEffect

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值