Android 事件体系全面总结+实践分析

作者 : 开心源码 本文共18333个字,预计阅读时间需要46分钟 发布时间: 2022-05-13 共234人阅读

在这之前看了很多相关文章,有一个整体认识以后,就要开始动手体验一下了。动手之前要明确事件分发机制要研究的是什么:事件序列在ViewGroup/View之间的传递规则。
注意几点:

  • 研究的是事件序列而不是单个事件
  • 至少要考虑到一个ViewGroup和一个View
  • 传递或者者说分发包括父View向子View传递事件(事件下发过程)和子View向父View传递事件(事件消费过程)。

举例说明一下为什么要考虑上面这几点,假如事件传到了一个没有子View的View里面,这时view的onTouchEvent()会被回调,我们可以通过重写onTouchEvent()的返回值来决定能否消费这个事件。假如这个事件是down,而onTouchEvent()返回false不消费它,那么事件序列后面的事件都不再分发给这个View,假如消费了down事件,那么后续事件会继续分发给这个View,这时,假如不消费后续事件的某个move事件,那么这个move事件后面的事件仍然会分发给这个View。
由此可见,对事件序列里某个事件的消费情况是会影响后续事件的分发的。
另一方面,假如这个View没消费down事件,那么会一层层往上回调父View的onTouchEvent()将down事件传递给父View,所以事件的消费也是一个传递事件的过程。

上面的例子是通过编写代码验证的一个事实,至于起因,会在文中详细分析,这里只为说明需要考虑上面的几个注意点。

尽管事件体系很复杂,但是是有规律可循的,我们实践的目的就是寻觅这个规律。根据我的了解,画了一张图作为Android事件机制的一个总结:

由图可以看到将整个流程分为了4种情况,下面将通过实践详细分析这4种情况,包括为什么这样划分,其余更多的情况如何归类排除等。

首先实现下图界面,界面每一层View的dispatchTouchEvent(),onInterceptTouchEvent(),onTouchEvent()三个方法中加上相应的log并返回默认的super,也可以使用这份代码:示例代码。图中,我们可以把Activity看作是顶级父View。

而后研究 Android 事件分发流程图中的4种情况:

  1. 默认情况,一律返回super,默认情况是不阻拦不消费事件的。
  2. View的onTouchEvent()消费down事件,其余默认。
  3. ViewGroup2的onTouchEvent()消费down事件,其余默认。
  4. ViewGroup2的onInterceptTouchEvent()阻拦down之后的事件。

消费事件指onTouchEvent()返回true,阻拦事件指onInterceptTouchEvent()返回true。从4种情况可以看出,将一个事件序列的ACTION_DOWN和ACTION_DOWN之后的事件分开考虑了,分析完源码之后就会明白为什么这样做。

下面开始结合log分析4种情况,为了方便查看和形容,我会把log用空行分为三部分,第一部分是down事件,第二部分是move事件,第三部分是up事件。

情况1:默认情况,一律返回super,默认情况是不阻拦不消费事件的。

( 1955): MainActivity->dispatchTouchEvent( 1955): MyViewGroup1-->dispatchTouchEvent( 1955): MyViewGroup1-->onInterceptTouchEvent( 1955): MyViewGroup2--->dispatchTouchEvent( 1955): MyViewGroup2--->onInterceptTouchEvent( 1955): MyView--------------->dispatchTouchEvent    //down事件下发过程结束( 1955): MyView--------------->onTouchEvent    // down事件消费过程开始( 1955): MyViewGroup2--->onTouchEvent( 1955): MyViewGroup1-->onTouchEvent( 1955): MainActivity->onTouchEvent( 1955): MainActivity->dispatchTouchEvent  //move1事件( 1955): MainActivity->onTouchEvent( 1955): MainActivity->dispatchTouchEvent //move2事件( 1955): MainActivity->onTouchEvent( 1955): MainActivity->dispatchTouchEvent   //up事件( 1955): MainActivity->onTouchEvent

上面的log我手动加了几个注释,默认情况下Activity/ViewGroup/View不阻拦不消费事件,由log很容易看出,down事件经历了一下一上的过程,下是分发过程,上是消费过程。假如下发无人阻拦,事件会一直向下传递到子View,假如子View不消费事件,会传给父View去消费,即依次回调父View的onTouchEvent。
而后就是move和up事件,log很简单,由于down事件子View们无人消费,那么事件序列里面的后续事件也就不再下发了,直接顶级Activity(DecorView)自己来解决。
为什么后续事件不再下发给子View,答案在源码里。接下来开始分析源码。


下面是ViewGroup的dispatchTouchEvent()方法,只保留了关键代码,其中if语句都是完整的,可以通过if语句在完整源码中定位代码。数字编号的注释是 ACTION_DOWN事件的下发过程,注意是down事件下发过程。

public boolean dispatchTouchEvent(MotionEvent ev) {    boolean handled = false;    if (onFilterTouchEventForSecurity(ev)) {        //1.首先假如是down事件,reset 少量状态,其中包括将mFirstTouchTarget置空        if (actionMasked == MotionEvent.ACTION_DOWN) {            cancelAndClearTouchTargets(ev);            resetTouchState();        }        //2.能否阻拦事件,用布尔变量intercepted表示        final boolean intercepted;        if (actionMasked == MotionEvent.ACTION_DOWN                || mFirstTouchTarget != null) {            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;            if (!disallowIntercept) {                //这里调用onInterceptTouchEvent()看能否阻拦事件                intercepted = onInterceptTouchEvent(ev);                ev.setAction(action);            } else {                intercepted = false;            }        } else {//假如mFirstTouchTarget是空,而且当前事件不是ACTION_DOWN,就阻拦事件                //注意此处直接给intercepted赋值而没有调用onInterceptTouchEvent()方法            intercepted = true;        }        // 3.得到intercepted之后,假如不阻拦,进入这个if        if (!canceled && !intercepted) {            //4.假如是down事件,进入这个if            if (actionMasked == MotionEvent.ACTION_DOWN                    || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)                    || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {                //5.newTouchTarget是这个方法里面定义的,默认是空,进入这个if                if (newTouchTarget == null && childrenCount != 0) {                    //6.循环,寻觅解决事件的子View                    for (int i = childrenCount - 1; i >= 0; i--) {                        //7.注意这个if里面的dispatchTransformedTouchEvent方法,里面调用了子View的dispatchTouchEvent方法                        //  调用子View的dispatchTouchEvent方法即把事件传给了子View去解决,                        //  这时先走一遍子View里的dispatchTouchEvent逻辑并返回一个布尔值表示能否消费掉了事件,                        //  假如子View的dispatchTouchEvent返回true说明子View消费事件,进入这个if,否则没消费不进入此if                        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {                            //8.假如子View消费了事件,那么给mFirstTouchTarget赋值                            //  给mFirstTouchTarget赋值的操作在addTouchTarget()方法里                            newTouchTarget = addTouchTarget(child, idBitsToAssign);                            break;                        }                    }                }            }        }        //在编号7的if条件里面已经把事件下发到了子View,并得到了子View返回的结果        //至此,默认情况下发事件的逻辑结束        //下面消费事件相关的代码,省略    }    //最后返回结果,此方法结束    return handled;    //至此,假如编号8的代码没执行,也就是子View的dispatchTouchEvent没消费事件,那么mFirstTouchTarget的值是空    //结合编号3的条件,可以得出结论:当ViewGroup不阻拦事件且它的子View消费事件的时候,mFirstTouchTarget不为空,否则mFirstTouchTarget是空。}

代码中注释很详细,最终可以得到一个结论:当ViewGroup不阻拦down事件且它的子View消费down事件的时候,mFirstTouchTarget不为空,否则mFirstTouchTarget是空。

下面带着这个结论重新看源码,不过这次分析的不是down事件,而是down之后的事件。只看关键部分,上面代码的编号2部分如下:

///2.能否阻拦事件,用布尔变量intercepted表示final boolean intercepted;if (actionMasked == MotionEvent.ACTION_DOWN        || mFirstTouchTarget != null) {    final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;    if (!disallowIntercept) {        //这里调用onInterceptTouchEvent()看能否阻拦事件        intercepted = onInterceptTouchEvent(ev);        ev.setAction(action);    } else {        intercepted = false;    }} else {//假如mFirstTouchTarget是空,而且当前事件不是ACTION_DOWN,就阻拦事件        //注意此处直接给intercepted赋值而没有调用onInterceptTouchEvent()方法    intercepted = true;}

首先if里面的actionMasked == MotionEvent.ACTION_DOWN一定是不成立了,看mFirstTouchTarget != null,假如mFirstTouchTarget不是空,那么说明子View消费了down事件,会执行到intercepted = onInterceptTouchEvent(ev);这一行代码。假如mFirstTouchTarget是空,说明子View没消费down事件,直接else里面intercepted = true;阻拦事件。而后看默认情况下的log:

( 1955): MainActivity->dispatchTouchEvent( 1955): MyViewGroup1-->dispatchTouchEvent( 1955): MyViewGroup1-->onInterceptTouchEvent( 1955): MyViewGroup2--->dispatchTouchEvent( 1955): MyViewGroup2--->onInterceptTouchEvent( 1955): MyView--------------->dispatchTouchEvent    //down事件下发过程结束( 1955): MyView--------------->onTouchEvent    // down事件消费过程开始( 1955): MyViewGroup2--->onTouchEvent( 1955): MyViewGroup1-->onTouchEvent( 1955): MainActivity->onTouchEvent( 1955): MainActivity->dispatchTouchEvent  //move1事件( 1955): MainActivity->onTouchEvent( 1955): MainActivity->dispatchTouchEvent //move2事件( 1955): MainActivity->onTouchEvent( 1955): MainActivity->dispatchTouchEvent   //up事件( 1955): MainActivity->onTouchEvent

这里顶级的ViewGroup是MainActivity(DecorView),首先down事件下发到子View,而后子View没消费它,又一层层交给父View消费,最终无人消费传回了MainActivity,down事件结束。由上面的源码分析可知,这时的mFirstTouchTarget是空,假如move事件来了,那么直接执行源码编号2部分的else阻拦事件,所以后续事件的log就是上面这样不再下发(同时也没有onInterceptTouchEvent()方法的log由于没调用到它)。假如子View消费down事件,mFirstTouchTarget就不是空,后续事件的流程就与down类似了,读者可以修改MyView的onTouchEvent()消费掉down事件试一下消费down事件的情况。

对于后续事件,无非就是阻拦不阻拦,决定权还是在编号2部分的代码。决定的结果是能否进入编号3的if,进入的话,假如不是down事件的就直接跳出编号3的if了。

扩展:由上面的分析可知,dispatchTouchEvent()方法是由父View调用的,子View是通过这个方法的返回值来告诉父View能否消费事件了,这个大家都知道。但是,考虑一个问题,Activity -> ViewGroup1 -> ViewGroup2 -> View,假如事件按这个顺序下发,最终View消费了down事件,那么Activity如何知道View能否消费事件呢。过程必然是这样
View.dispatchTouchEvent() 返回 true ->
ViewGroup2.dispatchTouchEvent() 返回 true ->
ViewGroup1.dispatchTouchEvent() 返回 true ->
Activity得到ViewGroup1.dispatchTouchEvent() 返回 true后就是上面分析的源码流程了。

这个过程只是猜测,我没有深入分析源码,不过我打了个log,结果是一系列的dispatchTouchEvent()的确是返回了true。也就是说ViewGroup1和ViewGroup2并没有消费事件,dispatchTouchEvent()却返回了true,那么网上的一种说法:dispatchTouchEvent()返回true就是消费事件。这种说法或者许并不完全精确,具体还需去源码找答案,这里先不分析了,只是说明一个问题:不要轻易重写dispatchTouchEvent()。就算重写,也要尽量保证调用到super方法并且返回值与super结果一致。其实,源码已经提供了两个接口来间接的重写它,就是onInterceptTouchEvent()和onTouchEvent(),通过源码可以知道它们最终都是dispatchTouchEvent()来调用的。而且用onTouchEvent()的返回值来形容能否消费事件是没有问题的(不消费事件的View根本调用不到onTouchEvent())。

针对默认情况下的log的源码分析到此结束,了解了默认情况,后面3种情况就简单了。


情况2:View的onTouchEvent()消费down事件,其余默认

(这里说的View是界面图里的最小的子View,不是ViewGroup1或者ViewGroup2)
先分析一下,由上面的默认情况来看,对于事件机制只研究单个事件是不能说明问题的,需要看整个事件序列里每个事件是如何解决的。所以研究事件消费需要考虑以下几种情况:

说明一下上图,对于图中第1点不消费down事件,其实就是情况1(默认情况),由情况1的log可以看出,不消费down事件的时候,后续事件不再分发给该View,所以图中分支1对于View来说不用再考虑后续事件。
而后看图中2,3,4,5分支,通过前面的Android事件分发流程图可以看出,它们可以得出同一个结论,所以它们可以看成是一种情况。
分析至此,只有情况1和情况2两种情况。对于2,3,4,5分支得出结论?这里拿图中分支5来举例,修改MyView的onTouchEvent()的代码如下:

int x = 0;@Overridepublic boolean onTouchEvent(MotionEvent event) {    if (event.getAction() == MotionEvent.ACTION_DOWN) {        x = 0;        Log.d(TAG, "MyView--------------->onTouchEvent  x=" + x);        return true;    }    x++;    Log.d(TAG, "MyView--------------->onTouchEvent  x=" + x);    if (x == 3) {        return true;    }    if (x == 5) {        return true;    }    return super.onTouchEvent(event);}

代码不难了解,实现了MyView消费事件序列里的down事件和第3个,第5个事件(从0开始计数),其余事件都不消费。同时,为了方便查看,在log中打印出了x的值。log如下(每个事件的log用空行分开了):

( 1888): MainActivity->dispatchTouchEvent( 1888): MyViewGroup1-->dispatchTouchEvent( 1888): MyViewGroup1-->onInterceptTouchEvent( 1888): MyViewGroup2--->dispatchTouchEvent( 1888): MyViewGroup2--->onInterceptTouchEvent( 1888): MyView--------------->dispatchTouchEvent   // 下发过程结束( 1888): MyView--------------->onTouchEvent  x=0    //  消费过程开始( 1888): MainActivity->dispatchTouchEvent( 1888): MyViewGroup1-->dispatchTouchEvent( 1888): MyViewGroup1-->onInterceptTouchEvent( 1888): MyViewGroup2--->dispatchTouchEvent( 1888): MyViewGroup2--->onInterceptTouchEvent( 1888): MyView--------------->dispatchTouchEvent   // 下发过程结束( 1888): MyView--------------->onTouchEvent  x=1    //  消费过程开始( 1888): MainActivity->onTouchEvent( 1888): MainActivity->dispatchTouchEvent( 1888): MyViewGroup1-->dispatchTouchEvent( 1888): MyViewGroup1-->onInterceptTouchEvent( 1888): MyViewGroup2--->dispatchTouchEvent( 1888): MyViewGroup2--->onInterceptTouchEvent( 1888): MyView--------------->dispatchTouchEvent   // 下发过程结束( 1888): MyView--------------->onTouchEvent  x=2    //  消费过程开始( 1888): MainActivity->onTouchEvent( 1888): MainActivity->dispatchTouchEvent( 1888): MyViewGroup1-->dispatchTouchEvent( 1888): MyViewGroup1-->onInterceptTouchEvent( 1888): MyViewGroup2--->dispatchTouchEvent( 1888): MyViewGroup2--->onInterceptTouchEvent( 1888): MyView--------------->dispatchTouchEvent   // 下发过程结束( 1888): MyView--------------->onTouchEvent  x=3    //  消费过程开始( 1888): MainActivity->dispatchTouchEvent( 1888): MyViewGroup1-->dispatchTouchEvent( 1888): MyViewGroup1-->onInterceptTouchEvent( 1888): MyViewGroup2--->dispatchTouchEvent( 1888): MyViewGroup2--->onInterceptTouchEvent( 1888): MyView--------------->dispatchTouchEvent   // 下发过程结束( 1888): MyView--------------->onTouchEvent  x=4    //  消费过程开始( 1888): MainActivity->onTouchEvent( 1888): MainActivity->dispatchTouchEvent( 1888): MyViewGroup1-->dispatchTouchEvent( 1888): MyViewGroup1-->onInterceptTouchEvent( 1888): MyViewGroup2--->dispatchTouchEvent( 1888): MyViewGroup2--->onInterceptTouchEvent( 1888): MyView--------------->dispatchTouchEvent   // 下发过程结束( 1888): MyView--------------->onTouchEvent  x=5    //  消费过程开始( 1888): MainActivity->dispatchTouchEvent( 1888): MyViewGroup1-->dispatchTouchEvent( 1888): MyViewGroup1-->onInterceptTouchEvent( 1888): MyViewGroup2--->dispatchTouchEvent( 1888): MyViewGroup2--->onInterceptTouchEvent( 1888): MyView--------------->dispatchTouchEvent   // 下发过程结束( 1888): MyView--------------->onTouchEvent  x=6    //  消费过程开始( 1888): MainActivity->onTouchEvent

上面log一共7个事件,尽管log很长,其实很简单,主要分为两类:MyView消费的和MyView没消费的。其中x=0,3,5是消费的,其余未消费。下发过程 log 中的所有事件一律一样(和默认情况下的down事件也一样),为什么每次都会调用onInterceptTouchEvent(),通过源码分析也已经很清楚了。消费过程的规律也很显著,x=0,3,5的事件消费掉就没了,其余没消费的事件直接传给顶级父View而不是一层层传回去。

情况3:ViewGroup2的onTouchEvent()消费down事件

首先,由情况1的log可以看出(假如了解了默认情况,这时候是没必要回去翻log的,脑中自然形成),要想出现当前的情况3,首先得让View的onTouchEvent()不消费down事件(假如消费就是情况2了,已经分析完),同时,因为View的onTouchEvent()不消费down事件,那么后续事件都不再传给View,也就是说没View什么事了,所以界面相当于变成了下图这样:

看图和标题发现了什么,变成情况2了,那就简单了,直接看log,下面是ViewGroup2的onTouchEvent()消费down事件,后续事件都不消费的log(相当于情况2那个图的分支2,可以顺便验证上面分支5得出的结论):

( 2008): MainActivity->dispatchTouchEvent( 2008): MyViewGroup1-->dispatchTouchEvent( 2008): MyViewGroup1-->onInterceptTouchEvent( 2008): MyViewGroup2--->dispatchTouchEvent( 2008): MyViewGroup2--->onInterceptTouchEvent( 2008): MyView--------------->dispatchTouchEvent( 2008): MyView--------------->onTouchEvent( 2008): MyViewGroup2--->onTouchEvent( 2008): MainActivity->dispatchTouchEvent( 2008): MyViewGroup1-->dispatchTouchEvent( 2008): MyViewGroup1-->onInterceptTouchEvent( 2008): MyViewGroup2--->dispatchTouchEvent( 2008): MyViewGroup2--->onTouchEvent( 2008): MainActivity->onTouchEvent( 2008): MainActivity->dispatchTouchEvent( 2008): MyViewGroup1-->dispatchTouchEvent( 2008): MyViewGroup1-->onInterceptTouchEvent( 2008): MyViewGroup2--->dispatchTouchEvent( 2008): MyViewGroup2--->onTouchEvent( 2008): MainActivity->onTouchEvent( 2008): MainActivity->dispatchTouchEvent( 2008): MyViewGroup1-->dispatchTouchEvent( 2008): MyViewGroup1-->onInterceptTouchEvent( 2008): MyViewGroup2--->dispatchTouchEvent( 2008): MyViewGroup2--->onTouchEvent( 2008): MainActivity->onTouchEvent

log中需要注意down事件经历了一次MyView的onTouchEvent()方法,down之后的事件不再执行ViewGroup2的onInterceptTouchEvent()方法。起因在源码分析中已经给出,结论在前两种情况已经得出,没什么好解释的了。

由上面分析可知,本情况是可以算成情况2的,只是感觉单独把它分出来逻辑更清楚正当少量,分析情况4的时候也会解释少量起因,同时,这也是对前两种情况的一个运用,假如了解了情况1和情况2,其余很多本文没提到的情况都能解释通的。

情况4:ViewGroup2的onInterceptTouchEvent()阻拦down之后的事件

前面研究的都是onTouchEvent()消费相关的情况,这里研究onInterceptTouchEvent()事件下发阻拦。

首先仔细看标题,为什么不考虑阻拦down事件而只考虑阻拦down之后的事件。由于阻拦down事件之后,事件直接交给自身的onTouchEvent()解决,不再经过子View,也就变成了情况3的界面(注意这里是变成情况3的界面而不是变成情况3,内部流程与情况3是有区别的,后面解释)。变成了情况3的界面之后,回调到自身的onTouchEvent()又分为不消费down和消费down:不消费down就是情况1;消费down就是情况2。

这里解释为什么 “是变成情况3的界面而不是变成情况3”,这里的情况是ViewGroup2的onInterceptTouchEvent()阻拦down之后,down直接交给ViewGroup2的onTouchEvent()去消费。而情况3是没有阻拦,down事件先去子View溜了一圈,发现子View不消费它,而后才传递给父亲ViewGroup2去消费。
说明了有两种方式可以让ViewGroup2消费到down事件。这也是把情况3从情况2分离出来的一个起因。

上面的分析说明阻拦down后必然出现前面的三种情况之一,所以这里不再考虑阻拦down。下面看看阻拦down之后的事件会有什么不同。
修改ViewGroup2的onInterceptTouchEvent()代码如下:

int x = 0;@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {    Log.d(TAG, "MyViewGroup2--->onInterceptTouchEvent x="+x);    if (x==3) {        x++;        return true;    }    x++;    return super.onInterceptTouchEvent(ev);}

代码很简单,当x==3的时候阻拦事件,也就是阻拦事件序列里的第4个事件,log如下:

( 1888): MainActivity->dispatchTouchEvent( 1888): MyViewGroup1-->dispatchTouchEvent( 1888): MyViewGroup1-->onInterceptTouchEvent( 1888): MyViewGroup2--->dispatchTouchEvent( 1888): MyViewGroup2--->onInterceptTouchEvent x=0( 1888): MyView--------------->dispatchTouchEvent( 1888): MyView--------------->onTouchEvent( 1888): MainActivity->dispatchTouchEvent( 1888): MyViewGroup1-->dispatchTouchEvent( 1888): MyViewGroup1-->onInterceptTouchEvent( 1888): MyViewGroup2--->dispatchTouchEvent( 1888): MyViewGroup2--->onInterceptTouchEvent x=1( 1888): MyView--------------->dispatchTouchEvent( 1888): MyView--------------->onTouchEvent( 1888): MainActivity->dispatchTouchEvent( 1888): MyViewGroup1-->dispatchTouchEvent( 1888): MyViewGroup1-->onInterceptTouchEvent( 1888): MyViewGroup2--->dispatchTouchEvent( 1888): MyViewGroup2--->onInterceptTouchEvent x=2( 1888): MyView--------------->dispatchTouchEvent( 1888): MyView--------------->onTouchEvent( 1888): MainActivity->dispatchTouchEvent( 1888): MyViewGroup1-->dispatchTouchEvent( 1888): MyViewGroup1-->onInterceptTouchEvent( 1888): MyViewGroup2--->dispatchTouchEvent( 1888): MyViewGroup2--->onInterceptTouchEvent x=3( 1888): MyView--------------->dispatchTouchEvent( 1888): MyView--------------->onTouchEvent( 1888): MainActivity->dispatchTouchEvent( 1888): MyViewGroup1-->dispatchTouchEvent( 1888): MyViewGroup1-->onInterceptTouchEvent( 1888): MyViewGroup2--->dispatchTouchEvent( 1888): MyViewGroup2--->onTouchEvent( 1888): MainActivity->onTouchEvent( 1888): MainActivity->dispatchTouchEvent( 1888): MyViewGroup1-->dispatchTouchEvent( 1888): MyViewGroup1-->onInterceptTouchEvent( 1888): MyViewGroup2--->dispatchTouchEvent( 1888): MyViewGroup2--->onTouchEvent( 1888): MainActivity->onTouchEvent

log需要分析3个地方:

1. x=3之前的事件,很好了解,就是情况2。

2. x=3的时候的事件,x=3的时候ViewGroup2已经阻拦事件了,为什么MyView还是收到了一个事件?假如用log把MyView收到的这个事件的action值打印出来的话发现它是ACTION_CANCEL这个事件,它表示非人为起因结束本次事件。对于ACTION_CANCEL事件我举一个例子,一般我们解决事件的时候会在down事件记录少量东西,而后在up事件清掉记录。那么假如在ListView中有一个Button,我们按住Button的时候按钮收到down事件,这时手指开始滑动,事件就会被ListView阻拦解决了,这时候按钮就再也收不到up事件了,但是ListView阻拦事件的时候按钮会收到cancel事件表示非人为起因结束本次事件,所以这时可以把up事件要做的事情让cancel来做。

3. x=3之后的事件,代码中只改了onInterceptTouchEvent(),而onTouchEvent()是默认返回,也就是说ViewGroup2阻拦事件之后并没有消费,从log也可以看出,每个事件都传回了Activity。但是,ViewGroup2不消费事件为什么后续事件还是不断的分发给ViewGroup2,而且执行到它的onTouchEvent()方法?(看似如同违反了默认情况的规则),这是一个问题,也是需要注意的一点。
首先,事件为什么分发到ViewGroup2?其实源码分析中已经解释了,当有子View消费掉down事件后,(MainActivity的)mFirstTouchTarget就不再是空,后续事件都会被下发到子View(递归),除非父View的onInterceptTouchEvent()主动阻拦事件,而例子中ViewGroup2的父View和Actvity并没有阻拦事件。
而后是为什么总是调到ViewGroup2的onTouchEvent()方法,起因是ViewGroup2没有解决事件的子View了,所以会调用到ViewGroup2的super.dispatchTouchEvent(),也就是调用父类View.dispatchTouchEvent()方法,所以假如没有设置onTouchListener就必然会调用到onTouchEvent()了。

结束语

文章到这里就分析完了,这时候再看Android事件分发流程图,此图就是根据上面4种情况画出来的,假如了解了那4种情况(至少要做到给出一种阻拦消费方式,能想到每个事件的log是什么样的),那么看懂这张图应该是不难的。

关于结论,这里就不给出纯文字形容了(即便形容出来也是很复杂的,一堆if),《Android开发艺术探究》中给了11条结论,有兴趣的可以看一下。但是,上图中有少量文字形容,可以当作结论,主要是绿色箭头对应的两部分,形容的是各个事件最终的去向,图中的各个分支箭头,就是各种情况下的条件。

最后,文中有没考虑到的情况或者不对的地方欢迎留言探讨。另外,对于源码,文中只给出了下发过程的分析。消费过程就不再给出详细分析了,提供一个思路,文中的源码分析里有一行注释 //下面消费事件相关的代码,省略,读者可以从这里开始看消费相关的代码,注意这是ViewGroup类里的dispatchTouchEvent(),这行注释下面会有相关的逻辑调用到dispatchTransformedTouchEvent()这个方法(下发过程也调到了这个方法),这个方法里会有super.dispatchTouchEvent(event)这样的代码调用到ViewGroup类的父类View里面的dispatchTouchEvent(),而后看View里面的dispatchTouchEvent(),就会发现onTouchEvent()被调用了,同时也会发现少量其余的东西,比方onTouchListener()和onTouchEvent()的优先级。
关于dispatchTransformedTouchEvent()再解释一下,里面有两种代码:super.dispatchTouchEvent(event)child.dispatchTouchEvent(event)。简单了解,前者就是调用ViewGroup自身的onTouchEvent()去消费,后者是将事件分发给(界面上的)子View去解决。假如子View是ViewGroup,那么子View解决事件的流程和父View相似,从ViewGroup的dispatchTouchEvent()开始执行;假如子View不是ViewGroup,那么直接执行View的dispatchTouchEvent()逻辑。而View的dispatchTouchEvent()里并没有将事件传给父View去消费的代码,其实消费过程事件不是传递的,而是根据子View的dispatchTouchEvent()返回值和少量ViewGroup自身的记录来决定能否调用super.dispatchTouchEvent(event)来调用自身的onTouchEvent()。好了,思路就提供到这里。

解释一个问题:

问题:情况2分析中最后一段话“消费过程的规律也很显著,x=0,3,5的事件消费掉就没了,其余没消费的事件直接传给顶级父View而不是一层层传回去。” 这里是为什么呢?

先看下Activity里面的dispatchTouchEvent()源码

public boolean dispatchTouchEvent(MotionEvent ev) {    if (ev.getAction() == MotionEvent.ACTION_DOWN) {        onUserInteraction();    }    if (getWindow().superDispatchTouchEvent(ev)) {        return true;    }    return onTouchEvent(ev);}

不难看出activity的onTouchEvent()是否被调用取决于if条件getWindow().superDispatchTouchEvent(ev)。
从前面的源码分析那可以知道一点:dispatchTouchEvent()方法是由父View调用的,子View是通过这个方法的返回值来告诉父View能否消费事件了(具体看源码分析编号7那里,还有情况1末尾的扩展那一部分)。
所以在这个问题中,对于不消费的事件,每一层View/ViewGroup的dispatchTouchEvent()返回值如下:
MyView.dispatchTouchEvent() 返回 false ->
ViewGroup2.dispatchTouchEvent() 返回 false ->
ViewGroup1.dispatchTouchEvent() 返回 false ->
……
最终根View,DecorView.dispatchTouchEvent() 返回 false

这个过程因为每个View都不消费事件,它们的onTouchEvent()都没被调用到,具体的代码就不分析了。

而后看Activity的dispatchTouchEvent()源码,getWindow().superDispatchTouchEvent(ev),这个方法是Window笼统类类的方法,大家都知道Window的实现类是PhoneWindow,所以直接看PhoneWindow的superDispatchTouchEvent()方法:

public boolean superDispatchTouchEvent(MotionEvent event) {    return mDecor.superDispatchTouchEvent(event);}

ctrl+左键跳到DecorView.java的superDispatchTouchEvent()方法

public boolean superDispatchTouchEvent(MotionEvent event) {    return super.dispatchTouchEvent(event);}

ctrl+左键再跳就是ViewGroup的dispatchTouchEvent()方法。上面说了,对于不消费的事件,这一系列的方法都返回false,所以最终Activity的dispatchTouchEvent()方法里面,if(getWindow().superDispatchTouchEvent(ev))条件是false,执行return onTouchEvent(ev);

最后

假如你觉得文章写得不错就给个赞呗?假如你觉得那里值得改进的,请给我留言。肯定会认真查询,修正不足。谢谢。

希望读到这的您能转发分享和关注一下我,以后还会升级技术干货,谢谢您的支持!

转发+点赞+关注,第一时间获取最新知识点

Android架构师之路很漫长,一起共勉吧!


以下墙裂推荐阅读!!!

  • Android学习笔记参考(敲黑板!!)
  • “寒冬未过”,阿里P9架构分享Android必备技术点,让你offer拿到手软!
  • 毕业3年,我是如何从年薪10W的拖拽工程师成为30W资深Android开发者!
  • 腾讯T3大牛带你理解 2019 Android开发趋势及必备技术点!
  • 八年Android开发,从码农到架构师分享我的技术成长之路,共勉!

最后祝大家生活愉快~

说明
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » Android 事件体系全面总结+实践分析

发表回复