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这三种情况来讲。
而且,说到事件分发,有三个非常重要的方法也不得不提。
##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;
}
##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