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