滑动冲突解决-联动子View
在做SuperRefreshLayout这个下拉刷新的控件时,发现了一个问题,这也是很多下拉刷新控件都会有的问题.
如果用户在顶部下拉时触发了控件的下拉效果,此时用户不放手向上滑动,会取消该控件的下拉效果.但是**用户继续向上滑动时,子View却不会向下滚动了.**一定要用户抬起手重新向下滚动才可以.
界面状态分别为:
普通状态(图1) | 拉动中(图2) | 刷新中(图3) |
---|---|---|
当用户从普通状态(图1)向下拉动进入拉动中状态(图2),用户向上滑动取消拉动动作,恢复普通状态(图1),此时继续向上滑动,界面并不会跟随用户手指滚动.
问题分析:
外层刷新控件称为RefreshLayout, 内部子控件称为子View.
RefreshLayout在由图1向下滑动至图2的过程中,其实是RefreshLayout拦截了当前的Touch事件,此时:
- RefreshLayout的
onInterceptTouchEvent()
方法返回true,后续的Touch事件都会交给RefreshLayout的onTouchEvent()
处理. - 子View的
dispatchTouchEvent()
方法会接收到Action_cancel事件,表示当前触摸事件已经结束,不会再处理接收到的除Action_down之外的事件. - RefreshLayout的
onTouchEvent()
方法消费Touch事件,界面逐渐变为图2.
- RefreshLayout的
RefreshLayout由图2向上滑动恢复图1状态后并继续向上滑动时,界面无法向下滑动,因为:
- RefreshLayout拦截了Touch事件,子View无法接收.
- RefreshLayout的
onTouchEvent()
方法返回false(因为当子View可以向下滑动时不需要处理).
解决思路:
我们的最终目标就是把RefreshLayout”错误”拦截的Touch事件交给子View来处理.
问题 | 原因 | 解决 |
---|---|---|
怎么判断是”错误”的拦截 | 有需要拦截的Touch事件(如滑动到顶部后继续下拉) | 在拦截后记录标志位, 在RefreshLayout的onTouchEvent() 方法中不该处理(return false)的情况中判断该标志位 |
为什么子View无法接收Touch事件 | 在一开始滚动的过程中RefreshLayout的onInterceptTouchEvent() 方法返回true,拦截了所有的Touch事件 |
事件传递机制是系统设定的,不好更改.但可以在RefreshLayout的onTouchEvent() 方法中调用子View的dispatchTouchEvent() 方法,让子View接受并处理该Touch事件 |
怎么让子View能够处理该Touch事件 | RefreshLayout拦截Touch事件后,子View会接收到Action_cancel事件,后续事件无法处理 | 判断是否为拦截后的第一个Touch事件,是的话先模拟一个Action_down事件并传递给子View,使子View认为这是一个新的Touch事件序列. |
解决代码:
以下代码为简略版:
//首先定义一个变量,检测是否是同一点击事件序列中第一个拦截后应该处理的move事件
private boolean isFirstMoveAfterIntercept = false;
//在dispatchTouchEvent方法中初始化
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
isFirstMoveAfterIntercept = true;
}
if (ev.getAction() == MotionEvent.ACTION_UP || ev.getAction() == MotionEvent.ACTION_CANCEL) {
isFirstMoveAfterIntercept = false;
}
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
......
//mIsBeingDragged表示被拦截了
if (mIsBeingDragged) {
//此时是错误的拦截(不需要拦截的情况)
if (originalDragPercent < 0) {
//判断是否是拦截后的第一个Move事件
if (isFirstMoveAfterIntercept) {
// 先为子View模拟一个Action_Down事件,使其认为这是一个新的Touch事件序列
MotionEvent event = MotionEvent.obtain(ev);
event.setAction(MotionEvent.ACTION_DOWN);
mTarget.dispatchTouchEvent(event);
isFirstMoveAfterIntercept = false;
}
//将Move事件传递到子View
mTarget.dispatchTouchEvent(ev);
return false;
}
...
}
....
}