源码

Android中事件分发机制

2017-02-01  本文已影响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则把事件拦截了。

上一篇下一篇

猜你喜欢

热点阅读