Android触摸滑动全解(二)——ViewGroup中触摸事件详解

Android触摸滑动全解(二)——ViewGroup中触摸事件详解

VieGroup触摸事件概述

ViewGroup继承自View,很多方法都和View中一样,不同的是它重载了dispatchTouchEvent()方法,并且它多了一个自身的方法onInterceptTouchEvent()

ViewGroup触摸事件调用原则

  • dispatchTouchEvent()中,会对子View进行递归,将dispatchTouchEvent()传递给它的每一个子View,如果某一个子View在ACTION_DOWN的时候没有消费或者拦截事件,那么这个子View后续也将无法收到ACTIION_MOVEACTION_UP等事件。

  • ViewGroup可以在onInterceptTouchEvent()中拦截事件,onInterceptTouchEvent()在之后执行,如果返回true,则会执行自身的onTouchEvent()方法,但后续onInterceptTouchEvent()onTouchEvent()将不会调用。

  • ViewGroup如果onInterceptTouchEvent()返回false,那么ViewGroup后续是否继续调用dispatchTouchEvent()取决于子View是否消费触摸事件。

  • ViewGroup如果返回在dispatchTouchEvent()中返回false,那么后续所有的触摸操作都不会再执行!

  • ViewGroup在dispatchTouchEvent()return truereturn super.dispatchTouchEvent(ev)会导致触摸结果不一样!

ViewGroup拦截触摸事件示例

自定义一个ViewGroup继承LinearLayout,展示ViewGroup对触摸事件的拦截。子View默认消费此事件。

1、正常状态(子View消费触摸事件)

此时:
ViewGroup:dispatchTouchEvent()super.dispatchTouchEvent(ev)onInterceptTouchEvent()falseOnTouchEvent()false
子View:dispatchTouchEvent()返回trueOnTouchEvent()返回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()falseOnTouchEvent()false
子View:dispatchTouchEvent()返回falseOnTouchEvent()返回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()trueOnTouchEvent()false
子View:dispatchTouchEvent()返回trueOnTouchEvent()返回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()trueOnTouchEvent()true
子View:dispatchTouchEvent()返回trueOnTouchEvent()返回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()falseOnTouchEvent()true
子View:dispatchTouchEvent()返回trueOnTouchEvent()返回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()没有返回truedispatchTouchEvent()返回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()trueonInterceptTouchEvent()在ACITON_DOWN时为false,其余返回trueOnTouchEvent()true
子View:dispatchTouchEvent()返回trueOnTouchEvent()返回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()中尽量不要直接返回truefalse,因为这样导致ViewGroup不会再去分发任何事件!最好返回super.dispatchTouchEvent(ev)
3、子View可以通过调用getParent().requestDisallowInterceptTouchEvent(true)方法来强制消费事件(有限制条件)。

推荐阅读更多精彩内容