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)方法来强制消费事件(有限制条件)。