本文主要是介绍Android中的事件分发浅析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
说在前面
Android在启动一个activity时,会实例化一个PhoneWindow,而PhoneWindow对象持有一个Decorview的引用,DecorView中有个id为content的ViewGrou,而我们平常在xml写的布局,就是加载在这个ViewGroup中的,如下图所示(图片引用自网络,侵删)。
事件分发
一般开发过程中,我们需要处理的事件,也就是我们的手指在ContentView上的触摸事件,在我们眼中,我们用手指触摸、点击一个个按钮、图片等,而无论是按钮、图片 等等,都无非是以下两种的分支:
1、View
拥有dispatchTouchEvent、onTouchEvent两个方法,分管事件分发、事件消费
2、ViewGroup
拥有dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent三个方法,分管事件分发、事件拦截、事件消费
预先准备
我们先准备一个View以及两个ViewGroup,他们分别是ChildView(黑色)、MotherLayout(红色)、FatherLayout(蓝色),Child盖在Mother上,Mother盖在Father上,所以Father是三者的最高层级如下图所示:
我们分别重写三个类的dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent方法并打上log,具体源码为:
FatherLayout.kt
class FatherLayout(context: Context, attrs: AttributeSet?) : FrameLayout(context, attrs){private val TAG="FatherLayout"companion object{fun getActionName(action:Int?):String{return when(action){0->{"按下"}1->{"抬起"}2->{"滑动"}else->{"其他"}}}}override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {Log.e(TAG,"调用了父亲的dispatch方法,事件为:${getActionName(ev?.action)}")return super.dispatchTouchEvent(ev)}override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {Log.e(TAG,"调用了父亲的onIntercept方法,事件为:${getActionName(ev?.action)}")return super.onInterceptTouchEvent(ev)}override fun onTouchEvent(ev: MotionEvent?): Boolean {Log.e(TAG,"调用了父亲的onTouchEvent方法,事件为:${getActionName(ev?.action)}")return super.onTouchEvent(ev)}
}
MotherLayout.kt
class MotherLayout(context: Context, attrs: AttributeSet?) : FrameLayout(context, attrs){private val TAG="MotherLayout"override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {Log.e(TAG,"调用了母亲的dispatch方法,事件为:${FatherLayout.getActionName(ev?.action)}")return super.dispatchTouchEvent(ev)}override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {Log.e(TAG,"调用了母亲的onIntercept方法,事件为:${FatherLayout.getActionName(ev?.action)}")return super.onInterceptTouchEvent(ev)}override fun onTouchEvent(ev: MotionEvent?): Boolean {Log.e(TAG,"调用了母亲的onTouchEvent方法,事件为:${FatherLayout.getActionName(ev?.action)}")return super.onTouchEvent(ev)}}
ChildView.kt
class ChildView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {private val TAG = "ChildView"override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {Log.e(TAG,"调用了孩子的dispatch方法,事件为:${FatherLayout.getActionName(ev?.action)}")return super.dispatchTouchEvent(ev)}override fun onTouchEvent(ev: MotionEvent?): Boolean {Log.e(TAG,"调用了孩子的onTouchEvent方法,事件为:${FatherLayout.getActionName(ev?.action)}")return super.onTouchEvent(ev)}
}
xml布局
<?xml version="1.0" encoding="utf-8"?>
<com.wp.facetest.eventHandle.FatherLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="450dp"android:layout_gravity="center"android:layout_height="450dp"android:background="#3a7dff"><com.wp.facetest.eventHandle.MotherLayoutandroid:layout_width="300dp"android:layout_gravity="center"android:layout_height="300dp"android:background="#ff3a7d"><com.wp.facetest.eventHandle.ChildViewandroid:layout_width="150dp"android:layout_gravity="center"android:layout_height="150dp"android:background="#333" /></com.wp.facetest.eventHandle.MotherLayout>
</com.wp.facetest.eventHandle.FatherLayout>
开始测试
我们先后单击Father、Mother、Child,得出如下日志及流程图:
PS:dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent方法默认返回false
单击Father
单击Mother
单击Child
以上可以看出,默认情况下,点击一个布局后,onDown(按下)事件首先会传导到最上层的父布局(FatherLayout)的dispatchTouchEvent方法,紧接着onInterceptTouchEvent、onTouchEvent,然后传到次一层的ViewGroup或View(MotherLayout或ChildView),重复以上传导,如果当前布局无子布局,则会在当前布局的onTouchEvent调用后,依次向上调用对应层次父布局的onTouchEvent,然后本次点击事件结束。
正常来说,一次单击动作,是包含Down和Up两个事件的,那么为什么以上只发现了Down事件呢?
原因是从父到子,无一个敢于站出来承担这次的Down事件,那么上级领导自然没必要再将Up事件下放下来,只能自己默默把Down和Up事件一起消化掉~
那么,我们让父、母、子勇敢一点,会发生什么呢?
针对dispatchTouchEvent
1、将FatherLayout的dispatchTouchEvent删除super.dispatchTouchEvent(ev),并且分别返回true和false,随后点击FatherLayout、MotherLayout、ChildView任何一个,代码、日志分别如下:
返回true
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {Log.e(TAG,"调用了父亲的dispatch方法,事件为:${getActionName(ev?.action)}")return true}
返回false
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {Log.e(TAG,"调用了父亲的dispatch方法,事件为:${getActionName(ev?.action)}")return false}
当dispatchTouchEvent删除super.dispatchTouchEvent(ev)时,无论返回值为true还是false,事件都不会往下传递,也不会传递到本身的onInterceptTouchEvent和onTouchEvent,只不过,如果返回值为true,则可以接收到所有的事件,如果返回false,只能收到第一个事件。
2、将FatherLayout的dispatchTouchEvent保留super.dispatchTouchEvent(ev),并且返回true(无需试验false,前面默认的就是false),随后点击ChildView,代码、日志分别如下:
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {Log.e(TAG,"调用了父亲的dispatch方法,事件为:${getActionName(ev?.action)}")super.dispatchTouchEvent(ev)return true}
可以看出,第一次的Down事件,在传递到MotherLayout、ChildView后,发现没有人处理,所以和我们上面发的流程图一样,通过onTouchEvent自下而上传回,而随后的Up事件也不会继续往下传递,只到FatherLayout这里截止。
接下来,我们在MotherLayout中也针对dispatchTouchEvent做类似处理:
3、将FatherLayout和MotherLayout的dispatchTouchEvent都保留super.dispatchTouchEvent(ev),并且都返回true,随后点击ChildView,日志如下:
我们发现Down事件从ChildView往上传递,到了MotherLayout这里截止了,而Up事件也只传到了MotherLayout这里,没有继续往下传递
4、将FatherLayout和MotherLayout的dispatchTouchEvent都保留super.dispatchTouchEvent(ev),并且FatherLayout返回true、MotherLayout返回false,随后点击ChildView,日志如下:
我们发现Down事件从ChildView往上传递,还是到了MotherLayout这里截止了,但是后续的Up事件却不见了,原因是FatherLayout的dispatchTouchEvent返回了false,FatherLayout的上级领导看FatherLayout不作为,肯定不会再下放Up事件了!
所以,我们对dispatchTouchEvent得出结论:
1、如果删除super.dispatchTouchEvent(ev),则无论返回true还是false都无意义,因为这个时候只有dispatchTouchEvent可以收到事件,而onInterceptTouchEvent和onTouchEvent都收不到事件,所以我们平时处理事件分发不建议这样写。
2、保留super.dispatchTouchEvent(ev),返回 true,如果下级布局的dispatchTouchEvent都没有返回true,则此布局为第一责任人,下层的Down事件传到此后不会再往上传递,而后续的Up事件也只会从上传到此,不会往下传递。
3、保留super.dispatchTouchEvent(ev),返回 false,那么这就又回到无人敢承担责任这一说了,第一次的Down事件会传递到最下层(ChildView),随后再层层往上传递,然后第二次的Up事件则不会再传下来。
4、如果一个布局想要处理事件,首先自己的dispatchTouchEvent要返回true,其次他的所有父布局的dispatchTouchEvent也要返回true,正所谓,你想干成事,首先要争取到领导的支持,你的上级领导是个不作为的人,你做起事来,肯定是难度很大的。
针对onInterceptTouchEvent
咱们先看一下ViewGroup里onInterceptTouchEvent的源码:
public boolean onInterceptTouchEvent(MotionEvent ev) {if (ev.isFromSource(InputDevice.SOURCE_MOUSE)&& ev.getAction() == MotionEvent.ACTION_DOWN&& ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)&& isOnScrollbarThumb(ev.getX(), ev.getY())) {return true;}return false;}
根据一些逻辑,判断该返回true还是false,那这一次我们不管super.onInterceptTouchEvent,自己定规则!考虑到
1、将FatherLayout的onInterceptTouchEvent返回true,随后点击ChildView,日志如下:
我们发现事件被FatherLayout拦截了,没有往下传递。
2、将FatherLayout的onInterceptTouchEvent返回false、MotherLayout的onInterceptTouchEvent返回true,随后点击ChildView,日志如下
我们发现事件被MotherLayout拦截了,但是我们发现,Down事件还是会往上传递!
所以,我们对onInterceptTouchEvent得出结论:
1、onInterceptTouchEvent返回true,事件被拦截,不会往下传递,但是还是会往上传递,且后续的Up事件不会传递下来,简单点说:接了活,但是没有能力干,也不分给下属,最后任务被上级领导收回了。
针对onTouchEvent
观察View的onTouchEvent源码得知,此方法处理一些点击事件等,所以我们不调用super.onTouchEvent,简单粗暴的返回true和false。
1、将FatherLayout的onTouchEvent返回true,随后点击ChildView,代码、日志如下:
override fun onTouchEvent(ev: MotionEvent?): Boolean {Log.e(TAG, "调用了父亲的onTouchEvent方法,事件为:${getActionName(ev?.action)}")return true}
我们看出,Down事件看不出什么,但是Up事件到FatherView的onTouchEvent后,没有继续传递
2、将FatherLayout的onTouchEvent返回false,将MotherLayout的onTouchEvent返回false,随后点击ChildView,代码、日志如下:
override fun onTouchEvent(ev: MotionEvent?): Boolean {Log.e(TAG, "调用了母亲的onTouchEvent方法,事件为:${FatherLayout.getActionName(ev?.action)}")return true}
我们发现,由于ChildView没有处理Down事件,然后传给了MotherLayout,但是母亲处理了Down,所以没有继续向上传递,而Up事件,也是在母亲的onTouchEvent这里截止了。
感觉onTouchEvent返回true和dispatchTouchEvent返回true的效果很类似啊,那是因为在View的dispatchTouchEvent中,有一行代码如下:
if (onFilterTouchEventForSecurity(event)) {if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {result = true;}//noinspection SimplifiableIfStatementListenerInfo li = mListenerInfo;if (li != null && li.mOnTouchListener != null&& (mViewFlags & ENABLED_MASK) == ENABLED&& li.mOnTouchListener.onTouch(this, event)) {result = true;}if (!result && onTouchEvent(event)) {result = true;}}
result在最后会根据onTouchEvent的返回值来定,所以,如果dispatchTouchEvent 返回super.dispatchTouchEvent,且onTouchEvent返回true,那么dispatchTouchEvent像相当于返回了true,所以现象会和上面一致。
那么,我们将dispatchTouchEvent返回false,且onTouchEvent返回true,看一下效果:
3、将MotherLayout的dispatchTouchEvent返回false,onTouchEvent返回true,然后点击ChildView,代码和日志如下:
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {Log.e(TAG, "调用了母亲的dispatch方法,事件为:${FatherLayout.getActionName(ev?.action)}")super.dispatchTouchEvent(ev)return false}override fun onTouchEvent(ev: MotionEvent?): Boolean {Log.e(TAG, "调用了母亲的onTouchEvent方法,事件为:${FatherLayout.getActionName(ev?.action)}")return true}
可以得出:只要dispatchTouchEvent返回false,onTouchEvent返回true也是没有用的。你没有接这个任务,但是背地里偷偷去处理这个任务,最终,上级领导也是不买账的,因为你不是责任人!
接下来,整理最终结论:
1、dispatchTouchEvent管调度,这个不返回true,其他都免谈。
2、onInterceptTouchEvent管拦截,返回true就代表下层布局没资格处理任何事件。
3、onTouchEvent管处理,真正处理事件的地方。
本博讲述的,也只是三个方法比较浅显的理解,真正深入的理解,还是要进入系统类的源码中寻找~
这篇关于Android中的事件分发浅析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!