【Android基础】讲讲Android的事件分发机制
Android的事件分发机制涉及的知识点很多,为了方便记忆和知识总结,小编决定专门开一篇文章,记录相关知识。
话不多说,我们直入主题。
##面试场景
今天找到了几个自己之前校招时记录的关于事件分发的几个面试题。
讲讲Android的事件分发机制
基本会遵从Activity => ViewGroup => View的顺序进行事件分发,然后通过调用onTouchEvent()方法进行事件的处理。我们在项目中一般会对
MotionEvent.ACTION_DOWN,MotionEvent.ACTION_UP, MotionEvent.ACTION_MOVE分情况进行操作。
有去查看源码中的事件拦截方法吗?了解过相关源码吗?
事件拦截分为内部拦截法和外部拦截法,对于外部拦截法,我们可以重写ViewGroup的onInterceptEvent();对于内部拦截法,我们可以通过
requestDisallowInterceptTouchEvent()来控制父容器是否拦截。
在一个列表中,同时对父View和子View设置点击事件,优先响应哪个?为什么会这样?
优先响应子View,因为当父View决定不拦截子View后,就会调dispatchTouchEvent,ViewGroup的这个方法会先去遍历调用子View的dispatchTouchEvent,如果都返回false,才会走到父View的onTouchEvent。
这三个问题只是简单的热身,要想了解Android的事件分发机制,还是得熟读源码。关于事件分发,应该分Activity、ViewGroup、View这三种情况来讲。
而且,说到事件分发,有三个非常重要的方法也不得不提。
dispatchTouchEvent()
onTouchEvent()
onInterceptTouchEvent()
##Activity的事件分发机制
顾名思义,dispatchTouchEvent()是负责事件分发的。当点击事件产生后,事件会传递给当前Activity,这时会调用Activity的dispatchTouchEvent(),我们来看源码。
这里写图片描述
在上面这段代码中,我们来看下getWindow().superDispatchTouchEvent(),getWindow()明显是获取Window,这个就是我们很熟的PhoneWindow了。我们直接看看PhoneWindow的superDispatchTouchEvent()到底做了什么操作。
这里写图片描述
直接调用了DecorView的superDispatchTouchEvent()方法。DecorView继承于FrameLayout,作为顶层View,是所有界面的父类。而FrameLayout作为ViewGroup的子类,所以直接调用了ViewGroup的dispatchTouchEvent()。
##ViewGroup的事件分发机制
我们通过查看ViewGroup的dispatchTouchEvent()可以发现。
我们分段来看dispatchTouchEvent()。
这里写图片描述
注:本文中源码都经过了小编删减,只展示与事件分发相关逻辑。
我们来看如上代码中的红框部分:ACTION_DOWN事件一定会交由ViewGroup处理,子View没办法拦截,因为resetTouchState()会把requestDisallowInterceptTouchEvent()所置的标志位重置。
我们再来看红框外的部分:首先定义了一个变量intercept来表示是否拦截事件。
其中采用了onInterceptTouchEvent()对intercept进行赋值。大多数情况下,onInterceptTouchEvent()返回值为false,但我们可以重写onInterceptTouchEvent()来改变它的返回值,我们往下看后面这个intercept是如何被用到的。
这里写图片描述
可以看到,当intercept是false时,会通过for循环去遍历ViewGroup的child,然后调用dispatchTransformedTouchEvent(),如果dispatchTransformedTouchEvent()返回值是true,就去调用addTouchTarget()。
而当intercept是true时,就不会去遍历ViewGroup的child,也更不会调用child的
dispatchTransformedTouchEvent()了。
这里我们还需要看下dispatchTransformedTouchEvent()和addTouchTarget()的源码是如何实现的。
这里写图片描述
在for循环里,传入给dispatchTransformedTouchEvent()的child不为null,所以调用的是子View的dispatchTouchEvent()。
如果子View的dispatchTouchEvent()返回true,则调用addTouchTarget()。
这里写图片描述
这个方法里,我们只需要注意一点:给mFirstTouchTarget赋值。
我们来整理一下思路:
当事件传递给ViewGroup,先去遍历调用child的dispatchTouchEvent(),如果有child的dispatchTouchEvent()返回了true,mFirstTouchTarget就被赋值,否则mFirstTouchTarget就为null。
至此,dispatchTouchEvent()就快看完了,我们来看下mFirstTouchTarget是如何被使用到的。
这里写图片描述
如上,如果mFirstTouchTarget为null,就去调用super.dispatchTouchEvent()。
我们知道ViewGroup的super是View,所以,当ViewGroup的所有child的dispatchTouchEvent()都返回false,就调用父容器的dispatchTouchEvent()。
总结一下,ViewGroup的事件传递过程可以通过如下伪代码表示:
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean consume = false;
if (onInterceptTouchEvent(ev)) {
consume = onTouchEvent(ev);
} else {
consume = child.dispatchTouchEvent(ev);
}
return consume;
}
1
2
3
4
5
6
7
8
9
##View的事件分发机制
上节我们讲到,当ViewGroup的所有child都没有消费事件,就调用super.dispatchTouchEvent(),而ViewGroup的super是View,所以我们来看下View的dispatchTouchEvent()。
这里写图片描述
View的dispatchTouchEvent()已经说的很清楚了:onTouchListener的优先级大于onTouchEvent。如果onTouchListener返回true了,就不调用onTouchEvent了。
我们下面再看看onTouchEvent()的源码。
这里写图片描述
在手指抬起的时候都会调用performClick。如果设置了onClickListener,就会调用onClickListener.onClick。
##总结
1、Android事件分发总是遵循Activity => ViewGroup => View的传递顺序
2、onTouchListener的优先级大于onTouchEvent。
##参考
https://blog.csdn.net/jiangwei0910410003/article/details/17504315
https://www.cnblogs.com/qlky/p/6675882.html
https://blog.csdn.net/jaysong2012/article/details/45535521
https://www.jianshu.com/p/d3758eef1f72
---------------------
作者:Colin_Mindset
来源:CSDN
原文:https://blog.csdn.net/colinandroid/article/details/81202045
版权声明:本文为博主原创文章,转载请附上博文链接!

浙公网安备 33010602011771号