Android事件分发机制详解

概述

  众所周知,Android事件分发机制是Android知识体系中的重点也是难点。说白了,要学好Android,事件分发机制是无论如何也绕不过去的。

  也许你会问,Android事件分发机制那么重要,我怎么没用过呢?
  当你被不同item的侧滑删除冲突问题所困扰时就会后悔没有真正理解事件分发机制。
  当你的针对多层级的ViewGroup做相应的触摸事件的处理的时候,如何进行事件分发的截断,也会使你幡然醒悟,下定决心学好事件分发机制

网上关于事件分发机制的讲解有很多,大体研究下来自认为存在下述不足之处。

1.单纯的理论描述,没有相应的流程图示,让读者很难静下心来研读。
2.有流程图示,但是没有紧紧结合和图示进行讲解,耦合性较低,不便于理解。
3.有些文章上来直接就源码分析,使得在阅读过程中更加晦涩难懂,尤其是初学者。
4.有些文章叙述的很详细,相对冗余,没有突出重点。这样就容易造成两级分化,那些对Android事件分发机制有一定了解的同学可能看完之后,感觉不错,受益匪浅。而对于一些对事件分发机制不太熟悉的同学,感觉看完之后更加懵逼了。

  毫不隐瞒的说,楼主在事件分发机制的学习过程中,也经历了以下比较难熬的过程。
在这里插入图片描述
以为看懂了—>懵逼---->茅塞顿开---->再次懵逼---->这下理解应该正确了---->我槽,这说不通啊---->哦,原来是这样。

  也正是基于此,我想记录下在Android 事件分发机制中遇到的各种坑坑。方便老铁门更快更高效的学习Android事件分发机制。

预备知识

  稍微了解过Android事件分发机制的同学,Android事件分发机制主要围绕Acticity–>ViewGroup—>View这三层来展开。因此在正式开始研究事件分发机制之前,希望大家能对Activity和View等的层级关系的知识点有个基本认知(当然了,这只是个建议,如果你不看的话,也不怎么影响本篇文章的阅读)。

推荐链接Activity、View、Window的理解一篇文章就够了

进入正文

先从网上盗张图:
说明: 网上给出的示意图呢,大都大同小异。因此给大家找了一个相对比较全面的的示意图。

在这里插入图片描述
下面对照上述示意图,大家先梳理一下事件分发机制的流程。

1 , 事件分发:public boolean dispatchTouchEvent(MotionEvent ev)

  Touch事件发生时Activity的dispatchTouchEvent(MotionEvent ev)方法会将事件传递给最外层View(Activity的Touch事件事实上是调用它内部的ViewGroup的Touch事件,可以直接当成ViewGroup处理。)的dispatchTouchEvent(MotionEvent ev)方法,该方法对事件进行分发。分发逻辑按照View的角色如下:

如果return true,事件会由当前View的dispatchTouchEvent方法进行消费,同时事件会停止向下传递;

如果return false,事件分发分为两种情况:

    (1).如果当前 View 获取的事件直接来自 Activity,则会将事件返回给Activity的onTouchEvent进行消费;
    (2).如果当前 View 获取的事件来自外层父控件,则会将事件返回给父View的onTouchEvent进行消费。

如果return super.dispatchTouchEvent(ev),事件分发分为两种情况:

 (1).如果当前View是ViewGroup,则事件会分发给onInterceptTouchEvent方法进行处理;
 (2).如果当前View是普通View,则事件直接交给onTouchEvent方法进行处理

2, 事件拦截:public boolean onInterceptTouchEvent(MotionEvent ev)

  此方法只有ViewGroup才有, Activity与普通View没有。 上面已经提到,如果当前ViewGroup的dispatchTouchEvent(事件分发)返回super.dispatchTouchEvent(ev), 那么事件会传递到传递到onInterceptTouchEvent方法, 该方法对事件进行拦截。拦截逻辑如下:

如果return true,则表示拦截该事件,并将事件交给当前View的onTouchEvent方法;

如果return false,则表示不拦截该事件,并将该事件交由子View的dispatchTouchEvent方法进行事件分发,重复上述过程

如果return super.onInterceptTouchEvent(ev), 事件拦截分两种情况:

(1).如果该View(ViewGroup)存在子View且点击到了该子View, 则不拦截, 继续分发给子View 处理, 此时相当于return false。

(2).如果该View(ViewGroup)没有子View或者有子View但是没有点击中子View(此时ViewGroup相当于普通View), 则交由该View的onTouchEvent响应,此时相当于return true。 

友情提示:上述中的(2)尤其重要,是对下图中标注的红色线路的合理解释
如图:
在这里插入图片描述

  一般的LinearLayout、 RelativeLayout、FrameLayout等ViewGroup默认不拦截, 而ScrollView、ListView等ViewGroup则可能拦截,得看具体情况。

3, 事件响应:public boolean onTouchEvent(MotionEvent ev)

  上面已经提到,在dispatchTouchEvent(事件分发)返回super.dispatchTouchEvent(ev)并且onInterceptTouchEvent进行拦截(事件拦截返回true)的情况下,那么事件会传递到onTouchEvent方法,该方法对事件进行响应。响应逻辑如下:

如果return true,则表示响应并消费该事件;

如果return fasle,则表示不响应事件,那么该事件将会不断向上层View的onTouchEvent方法传递,直到某个View的onTouchEvent方法返回true,如果到了最顶层View还是返回false,那么认为该事件不消耗,则在同一个事件系列中,当前View无法再次接收到事 件,该事件会交由Activity的onTouchEvent进行处理;

如果return super.onTouchEvent(e),事件处理分为两种情况:

(1).如果该View是clickable或者longclickable的,则会返回true, 表示消费了该事件, 与返回true一样;
(2).如果该View不是clickable或者longclickable的,则会返回false, 表示不消费该事件,将会向上传递,与返回false一样.

日志分析

示意图和理论知识讲述完毕,下面带领大家跟谁日志简单看下事件分发流程。
界面如下,

在这里插入图片描述

其对应的布局文件如下

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <com.avatarmind.toucheventdemo.CustomViewGroup
        android:id="@+id/vg_ll"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:background="@color/colorAccent"
        android:orientation="vertical">

        <com.avatarmind.toucheventdemo.CustomButton
            android:id="@+id/btn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:text="按钮" />

    </com.avatarmind.toucheventdemo.CustomViewGroup>


</LinearLayout>


  CustomViewGroup和CustomButton,顾名思义,是用了打印日志用的。CustomViewGroup中仅仅是重写了dispatchTouchEvent()/onInterceptTouchEvent()/onTouchEvent()方法,CustomButton也仅仅是重写了dispatchTouchEvent()/onTouchEvent()方法而已。顺便说一句,上述的重写的方法的返回值都是默认值

return  super.xxx();

大家也可以自己通过更改dispatchTouchEvent()/onInterceptTouchEvent()/onTouchEvent()扥等的返回值,来不断熟悉事件分发流程。

点击ViewGroupB

说明: 由于ViewGroupA是LinearLayout布局,我没有对其采用自定义ViewGroup的形式对其进行重写,所以无法对其追踪ViewGroup层面的dispatchTouchEvent()/onInterceptTouchEvent()/onTouchEvent()的打印结果。

ViewGroupB日志信息:
在这里插入图片描述

日志解读:(log和和上图中完全一样,毋庸置疑)

 activity::::-----------dispatchTouchEvent-------------down
 
 viewGroup::::::-----------dispatchTouchEvent-------------down
 viewGroup::::::-----------onInterceptTouchEvent-------------down
 viewGroup::-------------onInterceptTouchEvent----------super
 
 //由于没有点击子View(子View没有消费事件),所以onTouchEvent()开始回溯到ViewGroup中的onTouchEvent()
 viewGroup::::-----------onTouchEvent-------------down
 viewGroup::-------------onTouchEvent----------super
 
 //ViewGroup.dispatchTouchEvent()执行完毕(回溯)
 viewGroup::-------------dispatchTouchEvent----------super
 
 activity::::-----------onTouchEvent-------------down
 activity::::-----------onTouchEvent---------------super
 
 //activity.dispatchTouchEvent()执行完毕
 activity::::-----------dispatchTouchEvent---------------super

//由于ViewGroup/View都没有对事件进行消费,因此Action_up事件直接交由activity.onTouchEvent()方法。前面的Action_down相当于一个探路者,谁如果要对事件进行消费,则后续的Action_up/Action_move事件才会跟过来
 activity::::-----------dispatchTouchEvent-------------up
 activity::::-----------onTouchEvent-------------up
 activity::::-----------onTouchEvent---------------super
 activity::::-----------dispatchTouchEvent---------------super
//1.ViewGroup.onInterceptTouchEvent()的默认返回return super.onInterceptTouchEvent(ev);(默认不拦截,返回值为false,事件继续分发至子view)
//当点击了ViewGroup时,而没有点击子View时,在没有拦截的情况下,事件还是会向子View分发的。(由于子View没有被点击,所以log打印不出来,无法在log中进行查看)

View(点击Button)

日志解读:(大家自行分析,如果有疑问,可留言提出来)

activity::::-----------dispatchTouchEvent-------------down
viewGroup::::::-----------dispatchTouchEvent-------------down
viewGroup::::::-----------onInterceptTouchEvent-------------down
viewGroup::-------------onInterceptTouchEvent----------super
view:::-----------dispatchTouchEvent-------------down
view::::-----------onTouchEvent-------------down
view::-------------onTouchEvent----------super
view::-------------dispatchTouchEvent---------super
viewGroup::-------------dispatchTouchEvent----------super
activity::::-----------dispatchTouchEvent---------------super

activity::::-----------dispatchTouchEvent-------------move
viewGroup::::::-----------dispatchTouchEvent-------------move
viewGroup::::::-----------onInterceptTouchEvent-------------move
viewGroup::-------------onInterceptTouchEvent----------super
view:::-----------dispatchTouchEvent-------------move
view::::-----------onTouchEvent-------------move
view::-------------onTouchEvent----------super
view::-------------dispatchTouchEvent---------super
viewGroup::-------------dispatchTouchEvent----------super
activity::::-----------dispatchTouchEvent---------------super

activity::::-----------dispatchTouchEvent-------------up
viewGroup::::::-----------dispatchTouchEvent-------------up
viewGroup::::::-----------onInterceptTouchEvent-------------up
viewGroup::-------------onInterceptTouchEvent----------super
view:::-----------dispatchTouchEvent-------------up
view::::-----------onTouchEvent-------------up
view::-------------onTouchEvent----------super
view::-------------dispatchTouchEvent---------super
viewGroup::-------------dispatchTouchEvent----------super
activity::::-----------dispatchTouchEvent---------------super

顺便提一嘴,
1.当我们点击ViewGroup时,如果ViewGroup中不包含子View或者包含子View但是没有点击子View的时候,不管ViewGroup的dispatchTouchEvent()和dispatchTouchEvent()方法是否拦截,事件都不会向子View传递。此时的ViewGroup就是一个普通的View。

2.上面讲解的都是针对ACTION_DOWN的事件传递,ACTION_MOVE和ACTION_UP在传递的过程中并不是和ACTION_DOWN 一样,你在执行ACTION_DOWN的时候返回了false,后面一系列其它的action就不会再得到执行了。简单的说,就是当dispatchTouchEvent在进行事件分发的时候,只有前一个事件(如ACTION_DOWN)返回true,才会收到ACTION_MOVE和ACTION_UP的事件。通俗点说ACTION_DOWN就是个侦查员,看到前面没有敌情可以安营扎寨(被消费),就通知大部队过来(ACTION_MOVE/ACTION_UP等等)

  好了,至此完结。不知道大家是否对Android事件分发机制流程有了一个基本的认识,如果还不是太清楚的话,给大家推荐几篇自认为讲解比较通俗易懂的文章。




推荐链接:
Android:30分钟弄明白Touch事件分发机制
通俗理解Android事件分发与消费机制

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值