这两天今天重新回看了ViewGroup 事件分发,发现之前的认识有很多知识的盲点,这里记录下自己的学习结果,最后虽然理解了,但是发现很难讲诉清楚。
疑惑
在学习的时候大致流程,总有几个疑惑:
- onInterceptTouchEvent 什么时候会被调用
- onInterceptTouchEvent 是不是每次都会被调用
- onInterceptTouchEvent 在拦截ACTION_DOWN,后续会不会调用。此时,viewGroup的onTouchEvent会不会调用
- onInterceptTouchEvent 在拦截ACTION_MOVE,后续会不会调用。此时,viewGroup的onTouchEvent会不会调用
- View/ViewGroup不消费Action_Down,后续事件会不会走DispatchTouchEvent、onTouchEvent
事件类型 MotionEvent
ACTION_DOWN 按下
ACTION_MOVE 移动
ACTION_UP 抬起
ACTION_CANCEL 取消(跟 onInterceptTouchEvent 拦截事件有关)
重点: 一系列事件是以 ACTION_DOWN,中间经过0次或多次 ACTION_MOVE,最后以 ACTION_UP 结束。
后续的分发事件的都是以上面一整个系列事件为基础的讨论,以一次ACTION_DOWN开始,终止于ACTION_UP。
ViewGroup关于事件分发主要有三个方法:
bool dispatchTouchEvent( )
viewGroup/view 用来进行事件分发,一定会被调用
返回结果表示是否处理消耗事件
bool onInterceptTouchEvent( )
viewGroup 用于是否拦截某个事件 ,若没有选择拦截,则每个事件都会经过onIntercepteTouchtEvent,但如果一旦拦截成功,后续就不会走onInterceptTouchEvent,直接进入该viewGroup的onTouchEvent处理
返回结果表示 viewGroup 是否拦截
bool onTouchEvent( )
处理事件,返回值决定当前控件是否消费了这个事件
事件流程图
重要原则:
- 一系列事件是以 ACTION_DOWN,中间经过0次或多次 ACTION_MOVE,最后以 ACTION_UP 结束
- ViewGroup 默认不处理事件,会分发给View处理,如果View不处理,则由ViewGroup处理事件,如果ViewGroup不处理则最后由Activity的onTouchEvent
- ViewGroup 在 onInterceptTouchEvent 拦截开始事件ACTION_DOWN,后续的事件不会走onInterceptTouchEvent,同时事件ACTION_DOWN和后续事件会传递到该 ViewGroup 的 onTouchEvent 方法中
- VIewGroup 在 onInterceptTouchEvent 拦截半路事件如ACTION_MOVE,后续也不会走onInterceptTouchEvent,同时会转成一个ACTION_CANCEL事件传给之前处理事件的View的onTouchEvent,而且ACTION_MOVE事件是不会传到ViewGroup的onTouchEvent。只有后续的事件如ACTION_UP才会传递到 ViewGroup 的 onTouchEvent
- ACTION_DOWN 会重置拦截标志和负责处理事件的View(mFirstTouchTarget ),表示是一次事件开始,这也是requestDisallowInterceptTouchEvent失效的原因
验证:
为了验证和理清之间的关系,特地写了一个demo证实:
MyActivity 充当Acivity
MyViewGroup 继承之 LinearLayout,充当ViewGroup
MyTextView 继承于TextView,充当TextView
1. MyTextView 不处理消耗任何事件,即在onTouchEvent()返回false
MyActivity : dispatchTouchEvent: ACTION_DOWN
MyViewGroup: dispatchTouchEvent: ACTION_DOWN
MyViewGroup: onInterceptTouchEvent: ACTION_DOWN
MyTextView: dispatchTouchEvent: ACTION_DOWN
MyTextView: onTouchEvent: ACTION_DOWN (返回了false, 表示不消耗)
MyViewGroup: onTouchEvent: ACTION_DOWN
MyActivity : onTouchEvent: ACTION_DOWN
MyActivity : dispatchTouchEvent: ACTION_MOVE
MyActivity : onTouchEvent: ACTION_MOVE
MyActivity : dispatchTouchEvent: ACTION_UP
MyActivity : onTouchEvent: ACTION_UP
由上面可见
- 由dispatchTouchEvent 由 MyActivity -> MyViewGroup -> MyTextView,再由TouchEvent由 MyTextView-> MyViewGroup -> MyActivity 传递回来
- 如果ViewGroup和View都不处理Action_down事件,后续的事件不会分发下去,都由Activity处理
2. MyTextView接收ACTION_DOWN 和 ACTION_MOVE
MyActivity: dispatchTouchEvent: ACTION_DOWN
MyViewGroup: dispatchTouchEvent: ACTION_DOWN
MyViewGroup: onInterceptTouchEvent: ACTION_DOWN
MyTextview: dispatchTouchEvent: ACTION_DOWN
MyTextview: onTouchEvent: ACTION_DOWN (返回了true, 表示消耗)
MyActivity: dispatchTouchEvent: ACTION_MOVE
MyViewGroup: dispatchTouchEvent: ACTION_MOVE
MyViewGroup: onInterceptTouchEvent: ACTION_MOVE
MyTextview: dispatchTouchEvent: ACTION_MOVE (返回了true, 表示消耗)
MyTextview: onTouchEvent: ACTION_MOVE
MyActivity: onTouchEvent: ACTION_MOVE
MyActivity: dispatchTouchEvent: ACTION_UP
MyViewGroup: dispatchTouchEvent: ACTION_UP
MyViewGroup: onInterceptTouchEvent: ACTION_UP
MyTextview: dispatchTouchEvent: ACTION_UP (返回了true, 表示消耗)
MyTextview: onTouchEvent: ACTION_UP
MyActivity: onTouchEvent: ACTION_UP
上面是一个完整的流程 ACTION_DOWN -> ACTION_MOVE -> ACTION_UP
3. MyViewGroup 拦截ACTION_DOWN,但是MyViewGroup不消费ACTION_DOWN事件
MyActivity: dispatchTouchEvent: ACTION_DOWN
MyViewGroup: dispatchTouchEvent: ACTION_DOWN
MyViewGroup: onInterceptTouchEvent: ACTION_DOWN
MyViewGroup: onTouchEvent: ACTION_DOWN (在这里返回了false,表示不消费)
MyActivity: onTouchEvent: ACTION_DOWN
MyActivity: dispatchTouchEvent: ACTION_MOVE
MyActivity: onTouchEvent: ACTION_MOVE
MyActivity: dispatchTouchEvent: ACTION_UP
MyActivity: onTouchEvent: ACTION_UP
可见
- ViewGroup 在 onInterceptTouchEvent 拦截开始事件ACTION_DOWN,后续的事件不会走onInterceptTouchEvent,同时事件ACTION_DOWN和后续事件会传递到该 ViewGroup 的 onTouchEvent 方法中
- ViewGroup不处理ACTION_DOWN,后续的事件MOVE、UP也不会传给该ViewGroup,最后都被Activity处理
4. MyViewGroup 拦截ACTION_DOWN并在 onTouchEvent消费ACTION_DOWN
MainActivity: dispatchTouchEvent: ACTION_DOWN
MyViewGroup: dispatchTouchEvent: ACTION_DOWN
MyViewGroup: onInterceptTouchEvent: ACTION_DOWN
(拦截了,Action_Down 会去到MyViewGroup的onTouchEvent)
MyViewGroup: onTouchEvent: ACTION_DOWN
MainActivity: dispatchTouchEvent: ACTION_MOVE
MyViewGroup: dispatchTouchEvent: ACTION_MOVE
MyViewGroup: onTouchEvent: ACTION_MOVE
MainActivity: onTouchEvent: ACTION_MOVE
MainActivity: dispatchTouchEvent: ACTION_UP
MyViewGroup: dispatchTouchEvent: ACTION_UP
MyViewGroup: onTouchEvent: ACTION_UP
MainActivity: onTouchEvent: ACTION_UP
上面验证
- ViewGroup 在 onInterceptTouchEvent 拦截开始事件ACTION_DOWN,后续的事件不会走onInterceptTouchEvent,同时事件ACTION_DOWN和后续事件会传递到该 ViewGroup 的 onTouchEvent 方法中
5. MyViewGroup 不拦截 ACTION_DOWN, 但是拦截ACTION_MOVE,并且ACTION_DOWN在MyTextView消费
MainActivity: dispatchTouchEvent: ACTION_DOWN
MyViewGroup: dispatchTouchEvent: ACTION_DOWN
MyViewGroup: onInterceptTouchEvent: ACTION_DOWN
(不拦截,ACTION_DOWN)
MyTextview: dispatchTouchEvent: ACTION_DOWN
MyTextview: onTouchEvent: ACTION_DOWN
MainActivity: dispatchTouchEvent: ACTION_MOVE
MyViewGroup: dispatchTouchEvent: ACTION_MOVE
MyViewGroup: onInterceptTouchEvent: ACTION_MOVE
(拦截了, 但是不会去到MyViewGroup的onTouchEvent)
MyTextview: dispatchTouchEvent: ACTION_CANCEL
MyTextview: onTouchEvent: ACTION_CANCEL
MainActivity: onTouchEvent: ACTION_CANCEL
MainActivity: dispatchTouchEvent: ACTION_UP
MyViewGroup: dispatchTouchEvent: ACTION_UP
(不会走onInterceptTouchEvent)
MyViewGroup: onTouchEvent: ACTION_UP
MainActivity: onTouchEvent: ACTION_UP
- 一旦拦截onInterceptTouchEvent过,后续就不会走onInterceptTouchEvent
- VIewGroup 在 onInterceptTouchEvent 拦截半路事件如ACTION_MOVE,后续也不会走onInterceptTouchEvent,同时会转成一个ACTION_CANCEL事件传给之前处理事件的View的onTouchEvent,而且ACTION_MOVE事件是不会传到ViewGroup的onTouchEvent。只有后续的事件如ACTION_UP才会传递到 ViewGroup 的 onTouchEvent
如何处理滑动事件冲突
在《Android艺术开发》里面主要有两种方法:
- 外部拦截法
父容器需要则拦截,不需要则不拦截,在onInterceptTouchEvent() 控制,并且ACTION_DOWN不能拦截,在view的onTouchEvent返回true,否侧后续事件就不走过来了。
public boolean onInterceptTouchEvent(MotionEvent event) {
boolean intercepted = false;
int x = (int)event.getX();
int y = (int)event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
intercepted = false;
break;
}
case MotionEvent.ACTION_MOVE: {
if (满足父容器的拦截要求) {
intercepted = true;
} else {
intercepted = false;
}
break;
}
case MotionEvent.ACTION_UP: {
intercepted = false;
break;
}
default:
break;
}
mLastXIntercept = x;
mLastYIntercept = y;
return intercepted;
}
- 内部拦截法
父容器不拦截任何事件,将所有事件传递给View,在View的dispatchTouchEvent中控制, 如果需要则消耗掉,如果不需要则通过 requestDisallowInterceptTouchEvent 方法交给父容器处理
public boolean dispatchTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
parent.requestDisallowInterceptTouchEvent(true);
break;
}
case MotionEvent.ACTION_MOVE: {
int deltaX = x - mLastX;
int deltaY = y - mLastY;
if (父容器需要此类点击事件) {
parent.requestDisallowInterceptTouchEvent(false);
}
break;
}
case MotionEvent.ACTION_UP: {
break;
}
default:
break;
}
mLastX = x;
mLastY = y;
return super.dispatchTouchEvent(event);
}
父容器的onInterceptTouchEvent
public boolean onInterceptTouchEvent(MotionEvent event) {
int action = event.getAction();
if (action == MotionEvent.ACTION_DOWN) {
return false;
} else {
return true;
}
}
requestDisallowInterceptTouchEvent 控制父容器是否走onInterceptTouchEvent。
这也是为什么需要在父容器的onInterceptTouchEvent不拦截ACTION_DOWN,否则都没机会走到子View的dispatchTouchEvent
无论是哪种拦截法,都是必须在子View中处理消耗ACTION_DOWN事件,否则接受不到后续事件
参考:
云图网
原创文章,作者:奋斗,如若转载,请注明出处:https://blog.ytso.com/6272.html