Android触摸滑动全解(二)——ViewGroup中触摸事件详解
VieGroup触摸事件概述
ViewGroup继承自View,很多方法都和View中一样,不同的是它重载了dispatchTouchEvent()
方法,并且它多了一个自身的方法onInterceptTouchEvent()
。
ViewGroup触摸事件调用原则
dispatchTouchEvent()
中,会对子View进行递归,将dispatchTouchEvent()
传递给它的每一个子View,如果某一个子View在ACTION_DOWN
的时候没有消费或者拦截事件,那么这个子View后续也将无法收到ACTIION_MOVE
和ACTION_UP
等事件。ViewGroup可以在
onInterceptTouchEvent()
中拦截事件,onInterceptTouchEvent()
在之后执行,如果返回true
,则会执行自身的onTouchEvent()
方法,但后续onInterceptTouchEvent()
和onTouchEvent()
将不会调用。ViewGroup如果
onInterceptTouchEvent()
返回false
,那么ViewGroup后续是否继续调用dispatchTouchEvent()
取决于子View是否消费触摸事件。ViewGroup如果返回在
dispatchTouchEvent()
中返回false
,那么后续所有的触摸操作都不会再执行!ViewGroup在
dispatchTouchEvent()
中return true
和return super.dispatchTouchEvent(ev)
会导致触摸结果不一样!
ViewGroup拦截触摸事件示例
自定义一个ViewGroup继承LinearLayout,展示ViewGroup对触摸事件的拦截。子View默认消费此事件。
1、正常状态(子View消费触摸事件)
此时:
ViewGroup:dispatchTouchEvent()
为super.dispatchTouchEvent(ev)
,onInterceptTouchEvent()
为false
,OnTouchEvent()
为false
。
子View:dispatchTouchEvent()
返回true
,OnTouchEvent()
返回true
。
Log打印:
zw: LinearLayout dispatchTouchEvent down
LinearLayout onInterceptTouchEvent down
View dispatchTouchEvent down
View onTouchEvent down
zw: LinearLayout dispatchTouchEvent move
LinearLayout onInterceptTouchEvent move
View dispatchTouchEvent move
View onTouchEvent move
zw: LinearLayout dispatchTouchEvent up
LinearLayout onInterceptTouchEvent up
View dispatchTouchEvent up
View onTouchEvent up
可以看见,子View将事件消费掉了,因此ViewGroup没有调用OnTouchEvent()
方法。
调用顺序:
ACTION_DOWN:
ViewGroup.dispatchTouchEvent(DOWN)
> ViewGroup.onInterceptTouchEvent(DOWN)
> View.dispatchTouchEvent(DOWN)
> View.OnTouchEvent(DOWN)
>
ACTION_MOVE:
ViewGroup.dispatchTouchEvent(MOVE)
> ViewGroup.onInterceptTouchEvent(MOVE)
> View.dispatchTouchEvent(MOVE)
> View.OnTouchEvent(MOVE)
>
ACTION_UP:
ViewGroup.dispatchTouchEvent(UP)
> ViewGroup.onInterceptTouchEvent(UP)
> View.dispatchTouchEvent(UP)
> View.OnTouchEvent(UP)
> 结束
2、正常状态(子View不消费触摸事件)
此时:
ViewGroup:dispatchTouchEvent()
为super.dispatchTouchEvent(ev)
,onInterceptTouchEvent()
为false
,OnTouchEvent()
为false
。
子View:dispatchTouchEvent()
返回false
,OnTouchEvent()
返回false
。
Log打印:
zw: LinearLayout dispatchTouchEvent down
LinearLayout onInterceptTouchEvent down
View dispatchTouchEvent down
View onTouchEvent down
LinearLayout onTouchEvent down
可以看见,子View没有将事件消费掉,因此ViewGroup调用OnTouchEvent()
方法。
调用顺序:
ACTION_DOWN:
ViewGroup.dispatchTouchEvent(DOWN)
> ViewGroup.onInterceptTouchEvent(DOWN)
> View.dispatchTouchEvent(DOWN)
> View.OnTouchEvent(DOWN)
> ViewGroup.onTouchEvent(DOWN)
> 结束
3、ViewGroup拦截但不消费触摸事件(子View消费触摸事件)
此时:
ViewGroup:dispatchTouchEvent()
为super.dispatchTouchEvent(ev)
,onInterceptTouchEvent()
为true
,OnTouchEvent()
为false
。
子View:dispatchTouchEvent()
返回true
,OnTouchEvent()
返回true
。
Log打印:
zw: LinearLayout dispatchTouchEvent down
LinearLayout onInterceptTouchEvent down
LinearLayout onTouchEvent down
可以看见,子View没有将事件消费掉,因为ViewGroup的onInterceptTouchEvent()
返回了true
,拦截了此次事件,但是它本身也没有消费,因此,ACTION_DOWN后面没有再继续执行了。
调用顺序:
ACTION_DOWN:
ViewGroup.dispatchTouchEvent(DOWN)
> ViewGroup.onInterceptTouchEvent(DOWN)
> ViewGroup.onTouchEvent(DOWN)
> 结束
4、ViewGroup拦截并消费触摸事件(子View消费触摸事件)
此时:
ViewGroup:dispatchTouchEvent()
为super.dispatchTouchEvent(ev)
,onInterceptTouchEvent()
为true
,OnTouchEvent()
为true
。
子View:dispatchTouchEvent()
返回true
,OnTouchEvent()
返回true
。
Log打印:
zw: LinearLayout dispatchTouchEvent down
LinearLayout onInterceptTouchEvent down
LinearLayout onTouchEvent down
zw: LinearLayout dispatchTouchEvent move
LinearLayout onTouchEvent move
zw: LinearLayout dispatchTouchEvent up
LinearLayout onTouchEvent up
可以看见,子View没有将事件消费掉,因为ViewGroup的onInterceptTouchEvent()
返回了true
,拦截了此次事件,但是它本身消费了此次事件,因此,只有ViewGroup自身在消费本次触摸事件。
调用顺序:
ACTION_DOWN:
ViewGroup.dispatchTouchEvent(DOWN)
> ViewGroup.onInterceptTouchEvent(DOWN)
> ViewGroup.onTouchEvent(DOWN)
>
ACTION_MOVE:
ViewGroup.dispatchTouchEvent(MOVE)
> ViewGroup.onTouchEvent(MOVE)
>
ACTION_UP:
ViewGroup.dispatchTouchEvent(UP)
> ViewGroup.onTouchEvent(UP)
>
5、ViewGroup没拦截但消费触摸事件(子View消费触摸事件)
此时:
ViewGroup:dispatchTouchEvent()
为super.dispatchTouchEvent(ev)
,onInterceptTouchEvent()
为false
,OnTouchEvent()
为true
。
子View:dispatchTouchEvent()
返回true
,OnTouchEvent()
返回true
。
Log打印:
zw: LinearLayout dispatchTouchEvent down
LinearLayout onInterceptTouchEvent down
View dispatchTouchEvent down
View onTouchEvent down
zw: LinearLayout dispatchTouchEvent move
LinearLayout onInterceptTouchEvent move
View dispatchTouchEvent move
View onTouchEvent move
zw: LinearLayout dispatchTouchEvent up
LinearLayout onInterceptTouchEvent up
View dispatchTouchEvent up
View onTouchEvent up
可以看见,如果ViewGroup的onInterceptTouchEvent()
没有返回true
而dispatchTouchEvent()
返回true
,那么即使OnTouchEvent()
返回的是true
,也不会调用OnTouchEvent()
方法,需要根据子View的消费情况来判断!
调用顺序:
ACTION_DOWN:
ViewGroup.dispatchTouchEvent(DOWN)
> ViewGroup.onInterceptTouchEvent(DOWN)
> View.dispatchTouchEvent(DOWN)
> View.onTouchEvent(DOWN)
>
ACTION_MOVE:
ViewGroup.dispatchTouchEvent(MOVE)
> ViewGroup.onInterceptTouchEvent(MOVE)
> View.dispatchTouchEvent(MOVE)
> View.onTouchEvent(MOVE)
>
ACTION_UP:
ViewGroup.dispatchTouchEvent(UP)
>ViewGroup.onInterceptTouchEvent(UP)
> View.dispatchTouchEvent(UP)
> View.onTouchEvent(UP)
>
子View强制消费触摸事件的方法
当ViewGroup在onInterceptTouchEvent()
返回true
消费事件后,子View则无法消费此触摸事件,此时,子View如果一定要消费此触摸事件的话,可以使用getParent().requestDisallowInterceptTouchEvent(true)
来让ViewGroup不拦截本次事件。
但是此方法需要满足一个条件,就是ViewGroup在onInterceptTouchEvent()
的ACTION_DOWN时不要拦截,如果拦截了,那么此方法失效。
正常情况
此时:
ViewGroup:dispatchTouchEvent()
为true
,onInterceptTouchEvent()
在ACITON_DOWN时为false
,其余返回true
,OnTouchEvent()
为true
。
子View:dispatchTouchEvent()
返回true
,OnTouchEvent()
返回true
。
Log打印:
zw: LinearLayout dispatchTouchEvent down
LinearLayout onInterceptTouchEvent down
View dispatchTouchEvent down
View onTouchEvent down
zw: LinearLayout dispatchTouchEvent move
LinearLayout onInterceptTouchEvent move
LinearLayout dispatchTouchEvent move
LinearLayout onTouchEvent move
zw: LinearLayout dispatchTouchEvent up
LinearLayout onTouchEvent up
可以看见,子View只有dispatchTouchEvent(DOWN)
和onTouchEvent(DOWN)
被调用了,其余的都被拦截了。(这就是ViewGroup的onInterceptTouchEvent()
必须在ACTION_DOWN时返回false
的原因,不然子View根本没有调用的机会)
调用顺序:
ACTION_DOWN:
ViewGroup.dispatchTouchEvent(DOWN)
> ViewGroup.onInterceptTouchEvent(DOWN)
> View.dispatchTouchEvent(DOWN)
> View.onTouchEvent(DOWN)
ACTION_MOVE:
ViewGroup.dispatchTouchEvent(MOVE)
> ViewGroup.onInterceptTouchEvent(MOVE)
> ViewGroup.dispatchTouchEvent(MOVE)
> ViewGroup.onTouchEvent(MOVE)
ACTION_UP:
ViewGroup.dispatchTouchEvent(UP)
> ViewGroup.onTouchEvent(UP)
> 结束
子View调用代码的情况
基本和上面一样,只是子View在onTouchEvent()
的ACTION_DOWN中调用代码:
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
getParent().requestDisallowInterceptTouchEvent(true);
Log.e("zw","onTouchEvent down");
break;
case MotionEvent.ACTION_MOVE:
Log.e("zw","onTouchEvent move");
break;
case MotionEvent.ACTION_UP:
Log.e("zw","onTouchEvent up");
break;
}
return true;
}
Log打印:
zw: LinearLayout dispatchTouchEvent down
LinearLayout onInterceptTouchEvent down
View dispatchTouchEvent down
View onTouchEvent down
zw: LinearLayout dispatchTouchEvent move
View dispatchTouchEvent move
View onTouchEvent move
zw: LinearLayout dispatchTouchEvent up
View dispatchTouchEvent up
View onTouchEvent up
可以看见,本次触摸事件被子View强制消费掉了。
调用顺序:
ACTION_DOWN:
ViewGroup.dispatchTouchEvent(DOWN)
> ViewGroup.onInterceptTouchEvent(DOWN)
> View.dispatchTouchEvent(DOWN)
> View.onTouchEvent(DOWN)
ACTION_MOVE:
ViewGroup.dispatchTouchEvent(MOVE)
> View.dispatchTouchEvent(MOVE)
> View.onTouchEvent(MOVE)
ACTION_UP:
ViewGroup.dispatchTouchEvent(UP)
> View.dispatchTouchEvent(UP)
>View.onTouchEvent(UP)
> 结束
总结
1、如果ViewGroup要消费触摸事件的话,一定要在onInterceptTouchEvent()
返回true
,否则它是否调用onTouchEvent()
方法取决于子View是否消费此事件!
2、ViewGroup的dispatchTouchEvent()
中尽量不要直接返回true
或false
,因为这样导致ViewGroup不会再去分发任何事件!最好返回super.dispatchTouchEvent(ev)
!
3、子View可以通过调用getParent().requestDisallowInterceptTouchEvent(true)
方法来强制消费事件(有限制条件)。