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

本文详细分析了Android中事件分发的完整流程,从down事件开始,探讨了当View的onTouchEvent()消费事件、ViewGroup2的onTouchEvent()消费事件、ViewGroup2的onInterceptTouchEvent()拦截后续事件等不同情况下的事件处理逻辑。通过源码解析和实际操作,揭示了事件消费、拦截和传递的细节,有助于深入理解Android事件处理机制。

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)
先分析一下,由上面的默认情况来看,对于事件机制只研究单个事件是不能说明问题的,需要看整个事件序列里每个事件是如何处理的。所以研究事件消费需要考虑以下几种情况:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RGJ6BDpd-1650983211040)(//upload-images.jianshu.io/upload_images/7923646-88510f5cf9c5d804.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/826/format/webp)]

说明一下上图,对于图中第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;
@Override
public 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): 《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》无偿开源 徽信搜索公众号【编程进阶路】 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什么事了,所以界面相当于变成了下图这样:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wUZOJqrC-1650983211041)(//upload-images.jianshu.io/upload_images/7923646-8a656217c413a571.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/375/format/webp)]

看图和标题发现了什么,变成情况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;
@Override
public 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个地方:

MainActivity->onTouchEvent

( 1888): MainActivity->dispatchTouchEvent
( 1888): MyViewGroup1–>dispatchTouchEvent
( 1888): MyViewGroup1–>onInterceptTouchEvent
( 1888): MyViewGroup2—>dispatchTouchEvent
( 1888): MyViewGroup2—>onTouchEvent
( 1888): MainActivity->onTouchEvent

log需要分析3个地方:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值