相信大家已经对下拉刷新熟悉得不能再熟悉了,市面上的下拉刷新琳琅满目,然而有很多在我看来略有缺陷,接下来我将说明一下存在的缺陷问题,然后提供一种思路来解决这一缺陷,废话不多说!往下看嘞!
1.市面一些下拉刷新控件普遍缺陷演示
以直播吧APP为例:
第1种情况:
滑动控件在初始的0位置时,手势往下滑动然后再往上滑动,可以看到滑动到初始位置时滑动控件不能滑动。
原因:
下拉刷新控件响应了触摸事件,后续的一系列事件都由它来处理,当滑动控件到顶端的时候,滑动事件都被下拉刷新控件消费掉了,传递不到它的子控件即滑动控件,因此滑动控件不能滑动。
第2种情况:
滑动控件滑动到某个非0位置时,这时下拉回0位置时,可以看到下拉刷新头部没有被拉出来。
原因:
滑动控件响应了触摸事件,后续的一系列事件都由它来处理,当滑动控件到顶端的时候,滑动事件都被滑动控件消费掉了,父控件即下拉刷新控件消费不了滑动事件,因此下拉刷新头部没有被拉出来。
可能大部分人觉得无关痛痒,把手指抬起再下拉就可以了,but对于强迫症的我而言,能提供一个无痕过渡才是最符合操作逻辑的,因此接下来我来讲解下实现的思路。
2.实现的思路讲解
2.1.事件分发机制简介(来源于Android开发艺术探索)
dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent方法的关系伪代码
1.由代码可知若当前View拦截事件,就交给自己的onTouchEvent去处理,否则就丢给子View继续走相同的流程。
2.事件传递顺序:Activity -> Window -> View,如果View都不处理,最终将由Activity的onTouchEvent
处理,是一种责任链模式的实现。
3.正常情况,一个事件序列只能被一个View拦截且消耗。
4.某个View一旦决定拦截,这一个事件序列只能由它处理,并且它的onInterceptTouchEvent不会再被调用
5.不消耗ACTION_DOWN,则事件序列都会由其父元素处理。
2.2.一般下拉刷新的实现思路猜想
首先,下拉刷新控件作为一个容器,需要重写onInterceptTouchEvent和onTouchEvent这两个方法,
然后在onInterceptTouchEvent中判断ACTION_DOWN事件,根据子控件的滑动距离做出判断,若还没滑动过,则onInterceptTouchEvent返回true表示其拦截事件,然后在onTouchEvent中进行下拉刷新的头部显示隐藏的逻辑处理;
若子控件滑动过了,不拦截事件,onInterceptTouchEvent返回false,后续其下拉刷新的头部显示隐藏的逻辑处理就无法被调用了。
2.3.无痕过渡下拉刷新控件的实现思路
从2.2中可以看出,要想无痕过渡,下拉刷新控件不能拦截事件,这时候你可能会问,既然把事件给了子控件,后续拉刷新头部逻辑怎么实现呢?
这时候就要用到一般都忽略的事件分发方法dispatchTouchEvent了,此方法在ViewGroup默认返回true表示分发事件,即使子控件拦截了事件,父布局的dispatchTouchEvent仍然会被调用,因为事件是传递下来的,这个方法必定被调用。
所以我们可以在dispatchTouchEvent时对子控件的滑动距离做出判断,在这里把下拉刷新的头部的逻辑处理掉,同时在函数调用return super.dispatchTouchEvent(event)
前把event的action设置为ACTION_CANCEL,这样子子控件就不会响应滑动的操作。
3.代码实现
3.1.确定需求
- 需要适配任意控件,例如RecyclerView、ListView、ViewPager、WebView以及普通的不能滑动的View
- 不能影响子控件原来的事件逻辑
- 暴露方法提供手动调用刷新功能
- 可以设置禁止下拉刷新功能
3.2.代码讲解
需要的变量
|
|
初始化时创建头部执行显示隐藏的值动画,添加头部到布局中,并且通过设置paddingTop隐藏头部
|
|
在填充完布局后取出内容控件
|
|
重头戏来了,分发对于下拉刷新的特殊处理:
1.mContentViewOffset用于判别内容页的滑动距离,在无偏移值时才去处理下拉刷新的操作;
2.在mContentViewOffset!=0即内容页滑动的第一个瞬间,强制把MOVE事件改为DOWN,是因为之前MOVE都被拦截掉了,若不给个DOWN让内容页重新定下滑动起点,会有一瞬间滑动一大段距离的坑爹效果。
|
|
头部的处理逻辑:拿到下拉偏移量,然后动态去设置头部的paddingTop值,即可实现显示隐藏;手指抬起时根据状态决定是显示刷新还是直接隐藏头部
|
|
你可能会问了,这个mContentViewOffset怎么知道呢?接下来就是处理的方法,我会针对不同的滑动控件,去设置它们的滑动距离的监听,方法各种各样,通过handleTargetOffset去判别View的类型采取不同的策略;然后你可能会觉得要是我那个控件我也要实现监听咋办?这个简单,继承我已经实现的监听器,再补充你想要的功能即可,这个时候就不能再调handleTargetOffset这个方法了呗。
|
|
最后参考谷歌大大的SwipeRefreshLayout提供setRefreshing来开启或关闭刷新动画,至于openHeader为啥要post(Runnable)呢?相信用过SwipeRefreshLayout在onCreate的时候直接调用setRefreshing(true)没有小圆圈出来的都知道这个坑!
|
|
3.3.效果展示
除了以上三个还有在Demo中实现了ListView、ViewPager、ScrollView、NestedScrollView,具体看代码即可
Demo地址:Github:RefreshLayoutDemo,觉得还不错的话给个Star哦。