1. 手动实现一个下拉刷新功能。
2. 效果图:
![](https://img-blog.csdnimg.cn/f8c172455a274c74b15ac00c0d51e0b9.gif)
3. view结构
![](https://img-blog.csdnimg.cn/d30f7fca685b444e8b292917eec373dd.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAYV90aGlua29r,size_20,color_FFFFFF,t_70,g_se,x_16)
4.实现思路
<com.luocc.tim.recycler.RefreshLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/refresh"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_marginTop="-50dp"
android:gravity="bottom|center" />
<com.luocc.tim.recycler.ChatListRecycler
android:id="@+id/chat_list"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.luocc.tim.recycler.RefreshLayout>
给refresh部分设置负的margin,让他显示在布局外面,平常时这个view是看不见的,当下拉的时候通过scrollBy/scrollTo方法滑动,松手后让他完全隐藏或者显示一个合适的高度。
5.实现要点
5.1何时滑动
本功能实现的基础是View的事件的分发、拦截、处理。
继承一个ViewGroup,按需要重写touch相关方法:
// 分发事件
public boolean dispatchTouchEvent(MotionEvent ev)
// 拦截事件
public boolean onInterceptTouchEvent(MotionEvent ev)
// 处理事件
public boolean onTouchEvent(MotionEvent event)
我对这几个方法的了解仅仅是看过一些博客,简单解释一下:
手指触摸手机屏幕,产生DOWN事件,由最外层(父View)ViewGroup的dispatchTouchEvent方法分发,到onInterceptTouchEvent,如果该方法返回true,说明本ViewGroup需要处理这个事件,下一步执行onTouchEvent;如果返回false,说明本ViewGroup不对这个事件做处理,让这个事件继续向下流转,然后重复父ViewGroup的操作,直到某个View.
一次事件一般是这样的:
ACTION_DOWN -> ACTION_MOVE .... 无数个ACTION_MOVE -> ACTION_UP
实现下拉功能需要做的就是,在满足下拉条件时,让RecyclerView的父View的onInterceptEvent方法返回true,拦截此事件,这样事件就走不到RecyclerView;不满足条件时就让事件走到RecyclerView,让其顺利滑动。
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.d(TAG, "onInterceptTouchEvent: action = " + ev.getAction());
switch (ev.getAction()) {
......
case MotionEvent.ACTION_MOVE:
// 是否是向下滑动
boolean moveDown = ev.getY() - mLastY > 0;
Log.d(TAG, "onInterceptTouchEvent: moveDown ? " + moveDown);
// 该方法判断RecyclerView是否滑动到最顶部了
if (moveDown && !chatList.canScrollVertically(-1)) {
return true;
}
break;
......
}
return super.onInterceptTouchEvent(ev);
}
5.2 滑动冲突
当DOWN事件被某一层VIewGroup拦截后,后续的MOVE、UP事件都会直接走到它的onTouchEvent方法中,不会经过onInterceptTouchEvent,这就会产生一些问题。
当RefreshLayout(RecyclerView的父View)拦截了事件,正在处理下拉操作时,刷新区域出现,这很正常,但是这时候向上滑动会发现刷新区域已经完全闭合了,RecyclerView的滑动却不起作用。
![](https://img-blog.csdnimg.cn/b530a561290e416c98520163710181db.gif)
当然,你也可以这种现象当做一个特性,但我很无聊,所以我决定把他修一下。
类似的还有,当RecyclerView在滑动的时候,滑动到最顶部继续下拉,刷新区域出不来。这是因为滑动事件被RecyclerView拦截后,后续的事件都走到他这了,导致RefreshLayout收不到滑动事件。
解决第一点的做法是,在明确知道RefreshLayout不需要处理后续MOVE事件后,手动发送一个DOWN事件。
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.d(TAG, "dispatchTouchEvent: action = " + ev.getAction());
switch (ev.getAction()) {
case MotionEvent.ACTION_MOVE:
if (mChildMove) {
dispatchTouchEvent(
// MetaState参数不清楚干啥用的,我就随便传了一个
MotionEvent.obtain(
SystemClock.uptimeMillis(),
SystemClock.uptimeMillis(),
MotionEvent.ACTION_DOWN,
ev.getX(),
ev.getY(),
ev.getMetaState()));
return false;
}
break;
case MotionEvent.ACTION_UP:
break;
}
return super.dispatchTouchEvent(ev);
}
在某个View在处理事件时,后续的MOVE都往他这里跑,原因是ViewGroup里有个链表
private TouchTarget mFirstTouchTarget;
// Handle an initial down.
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Throw away all previous state when starting a new touch gesture.
// The framework may have dropped the up or cancel event for the previous gesture
// due to an app switch, ANR, or some other state change.
cancelAndClearTouchTargets(ev);
resetTouchState();
}
他会记住当前正在处理事件的View,而重新发送一个DOWN事件会重置它(不知道有没有更好的办法),重置后onInterceptTouchEvent就能重新工作了。
解决第二点的做法是,子View不想处理事件了,想让父View继续拦截事件。
// 在onTouchEvent中判断
if (!canScrollVertically(-1)) {
getParent().requestDisallowInterceptTouchEvent(false);
}
该方法让父View重新拦截。
6. 总结
实现下拉刷新最关键的地方在于理解View事件的流转机制,分发、拦截、处理。
最后附上代码:gitee地址