美文网首页源码
Android中事件分发机制

Android中事件分发机制

作者: 圈圈猫 | 来源:发表于2017-02-01 15:38 被阅读32次

    首先了解下Android中View的结构:

    Android 中View是树形结构的,

    可以看到view顶层出现两个吧:

            简单来说,Window是一个抽象类,是所有视图的最顶层容器,视图的外观和行为都归他管,不论是背景显示,标题栏还是事件处理都是他管理的范畴,它其实就像是View界的太上皇(虽然能管的事情看似很多,但是没实权,因为抽象类不能直接使用)。而 PhoneWindow 作为 Window 的唯一亲儿子(唯一实现类),自然就是 View 界的皇帝了,PhoneWindow 的权利可是非常大大,不过对于我们来说用处并不大,因为皇帝平时都是躲在深宫里面的,虽然偶尔用特殊方法能见上一面,但想要完全指挥 PhoneWindow 为你工作是很困难的。而 DecorView 是 PhoneWindow 的一个内部类,其职位相当于小太监,就是跟在 PhoneWindow 身边专业为 PhoneWindow 服务的,除了自己要干活之外,也负责消息的传递,PhoneWindow 的指示通过 DecorView 传递给下面的 View,而下面 View 的信息也通过 DecorView 回传给 PhoneWindow。

    触摸事件的相关方法:

    一、ViewGroup:

              ①、dispatchTouchEvent(MotionEvent)用于分发Touch事件

              ②、onInterceptTouchEvent( MotionEvent ) 用于是否中断touch事件

              ③、onTouchEvent( MotionEvent )   用于处理Touch事件

    二、View 和 Activity:

               ①、dispatchTouchEvent(MotionEvent)   事件的分发

                ②、onTouchEvent(MotionEvent)  onTouch的处理

    接下来用代码诠释下问题,至于源码的有大神写的更详细:

    郭大神的:android事件分发机制完全解析(上)

    Android事件分发机制完全解析(下)

    本试验有5个类:

          Utils用于获取MotionEvent的事件名称

          MainActivity用于显示界面

          ViewGroupA继承于LinearLayout

          ViewGroupB继承于LinearLayout

          ViewC继承于TextView

    Utils:

    2、ViewGroupA.java 代码如下:

    3、ViewGroupB.java代码与ViewGroupA.java类似,只是类名不同,还有输出Log的时候是“ViewGroupB"的Log

    4、ViewC.java 代码与ViewGroupA.java类似,只是类名不同,还有输出Log的时候是“ViewC"的Log,还有ViewC是直接继承于TextView,它没有onInterceptTouchEvent(MotionEvent)方法

    5、MainActivity.java代码与ViewGroupA.java类似,只是类名不同,还有输出Log的时候在分行方法之前先打印一行分隔线,还有Log是“Activity"的Log,还有MainActivity是直接继承于Activity,它没有onInterceptTouchEvent(MotionEvent)方法

    6、activity_main.xml 代码如下:

    运行代码,在蓝色的View上进行按下、移动、抬起,输出的Log如下:

    ---------------------------------------------

    DOWN , Activity.dispatch

    DOWN ,ViewGroupA.dispatch

    DOWN ,ViewGroupA.intercept = false

    DOWN ,ViewGroupB.dispatch

    DOWN ,ViewGroupB.intercept = false

    DOWN ,ViewC.dispatch

    DOWN ,ViewC.touch = false

    DOWN ,ViewC.dispatch = false

    DOWN ,ViewGroupB.touch = false

    DOWN ,ViewGroupB.dispatch = false

    DOWN ,ViewGroupA.touch = false

    DOWN ,ViewGroupA.dispatch = false

    DOWN ,Activity.touch = false

    DOWN ,Activity.dispatch = false

    ---------------------------------------------

    MOVE ,Activity.dispatch

    MOVE ,Activity.touch = false

    MOVE ,Activity.dispatch = false

    ---------------------------------------------

    UP   ,Activity.dispatch

    UP   ,Activity.touch = false

    UP   ,Activity.dispatch = false

    ---------------------------------------------

    从上面的Log可分析出:

         ①、触摸事件最先是由Activity获得,然后是ViewGroupA、ViewGroupB、ViewC

          ②、如果Down事件没有人处理,则事件丢失,后续的事件(如Move、Up)不再传递,直接由Activity进行处理

           ③、所有的事件分发都是调用super.dispatchTouchEvent(ev)完成的,所以如果不调用这句代码则事件中止传递。但是要中止事件传递一般不会这么做,一般是在onInterceptTouchEvent(MotionEvent) 方法中处理,如果该方法返回true则中止。既然dispatchTouchEvent(ev)方法可以中止事件传递,为什么还要设计一个onInterceptTouchEvent(MotionEvent) 方法呢? 因为子View可以请求父View不要拦截事件,如ListView是可以上下滑动的,当处于滑动状态时候就会请求禁止父View的拦截触摸事件方法,让ListView可以一直获取到touch事件进行滚动。假设这个时候父View又想响应触摸事件怎么办?可以写到dispatchTouchEvent方法中,因为事件是先传到这个方法,然后再传递给ListView的。

            ④、既然Activity最先获得事件,则可在Activity的dispatchTouchEvent方法不调用super.dispatchTouchEvent(ev);代码,则事件就不会进行分发了,可在此方法中调用onTouchEvent(ev)让它去处理。

            ⑤、应用:假如在Activity中要一定要响应一些触摸事件,又怕事件传递后被消费了,也是相同道理,直接在Activity的dispatchTouchEvent方法中处理即可)

    三、修改ViewGroupA的onInterceptTouchEvent让其返回true,代表拦截事件。

    运行,然后按下、移动、抬起,Log如下:

    ---------------------------------------------

    DOWN , Activity.dispatch

    DOWN , ViewGroupA.dispatch

    DOWN , ViewGroupA.intercept = true

    DOWN , ViewGroupA.touch = false

    DOWN , ViewGroupA.dispatch = false

    DOWN , Activity.touch = false

    DOWN , Activity.dispatch = false

    ---------------------------------------------

    MOVE , Activity.dispatch

    MOVE , Activity.touch = false

    MOVE , Activity.dispatch = false

    ---------------------------------------------

    UP   , Activity.dispatch

    UP   , Activity.touch = false

    UP   , Activity.dispatch = false

    从上面的Log可分析出:

          ①、ViewGroupA在Down事件的时候中断事件传递后直接把Touch事件交由自己的onTouchEvent方法去处理

          ②、ViewGroupA中断触摸事件后只是子View(ViewGroupB和ViewC)不再获得touch事件,而父View(Activity)可以。

          ③、虽然ViewGroupA中断触摸事件传递,由于它的onTouchEvent方法也没有处理Down事件,所以事件也丢失了,后续事件(如Move、Up)不再传给ViewGroupA,由Activity中行处理。所以,如果你想要获取后续事件,在处理了Down事件后一定要记得返回true。这里说的后续事件是指一次整体的操作,一般是由一个Down和多个Move和一个Up组成。按下然后移动然后弹起,这样的操作称为一次整体的操作。

    修改ViewGroupA的onInterceptTouchEvent让其返回false,代表不拦截事件

    修改ViewGroupB的onInterceptTouchEvent让其返回true,代表拦截事件,并修改onTouchEvent方法如下:

    运行,然后按下、移动、抬起,Log如下:

    ---------------------------------------------

    DOWN ,Activity.dispatch

    DOWN , ViewGroupA.dispatch

    DOWN , ViewGroupA.intercept = false

    DOWN , ViewGroupB.dispatch

    DOWN , ViewGroupB.intercept = true

    DOWN , ViewGroupB.touch = true

    DOWN , ViewGroupB.dispatch = true

    DOWN , ViewGroupA.dispatch = true

    DOWN , Activity.dispatch = true

    ---------------------------------------------

    MOVE , Activity.dispatch

    MOVE , ViewGroupA.dispatch

    MOVE , ViewGroupA.intercept = false

    MOVE , ViewGroupB.dispatch

    MOVE , ViewGroupB.touch = false

    MOVE , ViewGroupB.dispatch = false

    MOVE , ViewGroupA.dispatch = false

    MOVE , Activity.touch = false

    MOVE , Activity.dispatch = false

    ---------------------------------------------

    MOVE , Activity.dispatch

    MOVE , ViewGroupA.dispatch

    MOVE , ViewGroupA.intercept = false

    MOVE , ViewGroupB.dispatch

    MOVE , ViewGroupB.touch = false

    MOVE , ViewGroupB.dispatch = false

    MOVE , ViewGroupA.dispatch = false

    MOVE , Activity.touch = false

    MOVE , Activity.dispatch = false

    ---------------------------------------------

    UP   , Activity.dispatch

    UP   , ViewGroupA.dispatch

    UP   , ViewGroupA.intercept = false

    UP   , ViewGroupB.dispatch

    UP   , ViewGroupB.touch = false

    UP   , ViewGroupB.dispatch = false

    UP   , ViewGroupA.dispatch = false

    UP   , Activity.touch = false

    UP   , Activity.dispatch = false

    从上面的Log可分析出:

            ①、ViewGroupB中断了触摸事件的传递,并把事件交给自己的onTouchEvent方法处理。它的孩子ViewC就接收不到touch事件了

            ②、ViewGroupB中消费了Down事件(返回true),所以可以接收后续的Move、Up事件,dispatch方法接收到Move事件后并没有去调用onInterceptTouchEvent方法了,因为在接收到Down事件时候已经成为了目标View,非目标View不能接收后续事件,所以后续事件(Move、Up)就不需要再调用了onInterceptTouchEvent,因为不需要再传递了,所以直接把事件交给自己的onTouchEvent方法进行处理。

           ③、虽然ViewGroupB在处理Move事件时返回了false,但是父View(ViewGroupA)的onTouchEvent方法并没有执行,这说明,谁消费了Down事件(返回true)谁就能接收后续事件(Move、Up),没有消费Down事件的其它View则接收不到(Activity例外)。

           ④、虽然ViewGroupB在处理Move事件时返回了false,但是还是依旧可以接收后续事件的(Move、Up),那在处理Move和UP时返回false或返回true有什么区别吗?答:返回true则消费此事件,Activity的onTouchEvent方法就不会接收到了。

    通过这个Demo可以模拟任何的不同情况,如处理Down事件返回true,Move事件返回false,事件拦截,请求禁止拦截等等。

    总结:

            ①、touch事件传递其实就是一连串的方法调用,由Activity的dispatchTouchEvent方法开始调用,当这个方法调用结束时,这个touch事件就结束了。

            ②、 一个整体的touch事件由1个Donw和0 ~ n个Move和1个Up事件组成

           ③、 消费了Down事件的View称为目标View,目标View可接收后续事件(非目标View不接收后续事件)

           ④、 Down事件如果没有任何View消费,则后续事件不再传递,直接由Activity的onTouchEvent方法处理,所以,想要处理其它事件首先要消费Down事件,也就是在接收到Down事件的时候返回true。

    二、拦截:

          ①、如果在Down事件拦截,则把当前事件交自己的onTouchEvnet方法处理,如果此方法不消费Down事件,则事件丢失,后续事件由Activity的onTouchEvent方法处理

          ②、如果在其它事件拦截,则不处理当前事件,且传一个Cancel事件给目标View,后续的事件就会交给自己的onTouchEvent方法处理(此时拦截了事件的View变成了目标View,虽然它没有消费Down事件)

          ③、如果在Down事件不拦截,在事件分发返回后,如果Down事件没被消费,则会把事件交给自己的onTouchEvent方法处理。

          ④、如果在其它事件不拦截(能接收其它事件说明有目标View),在事件分发返回后,不会把事件交给自己的onTouchEvent,即使目标View在处理(Move、Up)事件时返回false。

          ⑤、Activity不管有无目标View,也不管是Down还是Move、Up事件,只要分发调用返回时,如果事件没被消费,则交给自己的onTouchEvent方法处理

          ⑥、可以调用getParent().requestDisallowInterceptTouchEvent(true)方法请求父View禁止拦截事件,这个方法会递归的请求所有的父View禁止拦截事件。

    三、注意:

           ①。如果想要获取到一个整体的touch事件,一定要消费Down事件,如果在Down事件的时候只是请求父View禁止拦截并不消费Down事件,虽然父View不再拦截了,但后续事件也接收不到了,哪个父View消费了Down事件哪个父View就可以接收到后续事件。

           ②、有两个View并没有包含关系,但是有重叠,则上面的View先拿到事件,如果消费了,则事件不会传给另一个View。

           ③、容器类一般都是调用ViewGroup的dispatchTouchEvent方法进行事件分发,其它类一般是调用View类的dispatchTouchEvent方法进行事件分发;通常默认的onInterceptTouchEvent、onTouchEvent方法都是返回false。

    应用技巧:

           ①、父类的onTouchEvent方法要想执行,要么是等所有的子View都不消费Down事件,要么是父View把事件拦截。

           ②、如果子类消费了Down事件,而父View又不想拦截但是又想处理这个事件,则父View可以在onInterceptTouchEvent或dispatchTouchEvent方法处理touch事件

           ③、如果子View请求了禁止父View拦截,且父View还想要拦截的话,可在父View的dispatchTouchEvent方法中不调用super.dispatchTouchEvent则把事件拦截了。

    相关文章

      网友评论

        本文标题:Android中事件分发机制

        本文链接:https://www.haomeiwen.com/subject/thkevttx.html