一种无痕过渡下拉刷新控件的实现思路
相信大家已经对下拉刷新熟悉得不能再熟悉了,市面上的下拉刷新琳琅满目,然而有很多在我看来略有缺陷,接下来我将说明一下存在的缺陷问题,然后提供一种思路来解决这一缺陷,废话不多说!往下看嘞!
1.市面一些下拉刷新控件普遍缺陷演示
以直播吧APP为例:
第1种情况:
滑动控件在初始的0位置时,手势往下滑动然后再往上滑动,可以看到滑动到初始位置时滑动控件不能滑动。
原因:
下拉刷新控件响应了触摸事件,后续的一系列事件都由它来处理,当滑动控件到顶端的时候,滑动事件都被下拉刷新控件消费掉了,传递不到它的子控件即滑动控件,因此滑动控件不能滑动。
第2种情况:
滑动控件滑动到某个非0位置时,这时下拉回0位置时,可以看到下拉刷新头部没有被拉出来。
原因:
滑动控件响应了触摸事件,后续的一系列事件都由它来处理,当滑动控件到顶端的时候,滑动事件都被滑动控件消费掉了,父控件即下拉刷新控件消费不了滑动事件,因此下拉刷新头部没有被拉出来。
可能大部分人觉得无关痛痒,把手指抬起再下拉就可以了,but对于强迫症的我而言,能提供一个无痕过渡才是最符合操作逻辑的,因此接下来我来讲解下实现的思路。
2.实现的思路讲解
2.1.事件分发机制简介(来源于Android开发艺术探索)
dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent方法的关系伪代码
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean consume = false;
if(onInterceptTouchEvent(ev)) {
consume = onTouchEvent(ev);
} else {
consume = child.dispatchTouchEvent(ev);
}
return consume;
}
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.代码讲解
需要的变量
public class RefreshLayout extends LinearLayout {
// 隐藏的状态
private static final int HIDE = 0;
// 下拉刷新的状态
private static final int PULL_TO_REFRESH = 1;
// 松开刷新的状态
private static final int RELEASE_TO_REFRESH = 2;
// 正在刷新的状态
private static final int REFRESHING = 3;
// 正在隐藏的状态
private static final int HIDING = 4;
// 当前状态
private int mCurrentState = HIDE;
// 头部动画的默认时间(单位:毫秒)
public static final int DEFAULT_DURATION = 200;
// 头部高度
private int mHeaderHeight;
// 内容控件的滑动距离
private int mContentViewOffset;
// 最小滑动响应距离
private int mScaledTouchSlop;
// 记录上次的Y坐标
private float mLastMotionY;
// 记录一开始的Y坐标
private float mInitDownY;
// 响应的手指
private