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

更多相关文章

  1. 直播一对一源码在Android音频开发中如何实现对讲机实时语音对话
  2. Android(安卓)Butterknife 框架源码解析(2)——谈谈Java的注解
  3. IDEA/Android(安卓)Studio报Ambiguous method call的一种解决方
  4. httpClient及android 原生接口实现下载并显示图片
  5. 使用 Android(安卓)自带的 proguard 混淆源码
  6. Android(安卓)AsyncTask使用以及源码解析
  7. Android事件分发机制完全解析,带你从源码的角度彻底理解(下) 。
  8. android源码下载以及编译
  9. AsyncTask的使用及源码分析

随机推荐

  1. Android(安卓)HIDL基础篇(一)
  2. Android引用项目出现ClassNotFoundExcept
  3. andriod 4.0以上版本添加了 android:conf
  4. Activity 跳转时的生命周期流程
  5. 使用maven构建android项目
  6. ERROR: the user data image is used by
  7. Ubuntu12.04下的Android 源码编译
  8. Android 自定义Toast
  9. 使用Maven编译Android下i-jetty的一些问
  10. Android时出现Error 41的解决方法