Android事件分发机制

Android事件分发机制

为什么会有事件分发机制

android上面的view是树形结构的,view可能会重叠在一起,当我们点击的地方有过个view都可以响应的时候,这个点击事件应该交给谁来处理,就需要事件分发机制。

1.概述

事件分发的三个重要方法

public boolean dispatchTouchEvent(MotionEvent event)
public boolean onInterceptTouchEvent(MotionEvent event)
public boolean onTouchEvent(MotionEvent event)

MotionEvent取值如下:

    public static final int ACTION_DOWN             = 0;
    public static final int ACTION_UP               = 1;
    public static final int ACTION_MOVE             = 2;
    public static final int ACTION_CANCEL           = 3;

ACTION_DOWN 表示手指按下时发出的消息
ACTION_UP 表示手指抬起时发出的消息
ACTION_MOVE 表示手指移动时发出的消息
ACTION_CANCEL 表示结束事件时手指发出的消息

举个例子讲解事件分发机制:

布局为Activity嵌套两个ViewGroup,最顶层的ViewGroup里面套一个TextView

如下不拦截消息时,ACTION_DOWN消息的完整传递过程为:

image

注意:
对于Activity来说不需要 onInterceptTouchEvent 方法,因为拦截没有意义,拦截后整个view树都点击不了。
对于view来说,事件要么消费,要么回传,也没有拦截的必要。

因此在Activity,ViewGroup,TextView中都有disPatchTouchEvent方法和onTouchEvent方法。
对于ViewGroup来说,多了一个onInterceptTouchEvent方法。

先说不包含 onInterceptTouchEvent 方法时消息传递过程,再来看包含onInterceptTouchEvent方法时消息传递过程

2. 不包含 onInterceptTouchEvent 方法时 ACTION_DOWN 消息传递过程

什么是拦截?看下面这三个方法都有一个boolean类型的返回值。默认返回false表示没有拦截,即自己不做处理。返回true时,表示自己处理这个事件,就是拦截了。

public boolean dispatchTouchEvent(MotionEvent event)
public boolean onInterceptTouchEvent(MotionEvent event)
public boolean onTouchEvent(MotionEvent event)

2.1 dispatchTouchEvent 返回值简介

dispatchTouchEvent 方法对于返回值的处理比较特殊,默认的处理方法并不是返回false,而是直接调用super.dispatchTouchEvent.这种处理方式与直接返回false不同。因此对于dispatchTouchEvent,有三种返回值:

  • 默认的super.dispatchTouchEvent
  • 返回true
  • 返回false

2.2 在 dispatchTouchEvent 方法中返回 true 拦截消息

2.2.1 在 Activity 的 dispatchTouchEvent 方法中拦截消息

在 Activity 的 dispatchTouchEvent 方法中拦截消息后,消息会直接断掉,不会往任何地方传递,只有 Activity 的 dispatchTouchEvent 方法收到 ACTION_DOWN 消息。

2.2.2 在 ViewGroup 的 dispatchTouchEvent 方法中拦截消息

在 ViewGroup 的 dispatchTouchEvent 方法中拦截消息后,ACTION_DOWN 消息同样会直接停止传递,其他任何子控件都收不到消息。

2.2.2 在 View 的 dispatchTouchEvent 方法中拦截消息

在传递到 View 的 dispatchTouchEvent 方法后,消息就停止传递了。

因此,无论在哪个控件的 dispatchTouchEvent 方法中拦截消息,消息都会直接停止传递,后面的子控件都不会接收到这个消息。

2.3 在 dispatchTouchEvent 方法中返回 false 拦截消息

先说结论:在 dispatchTouchEvent 方法中返回 false 拦截消息后,消息不会直接停止传递,而是向父控件的 onTouchEvent 方法回传。

2.3.1 在 View 的 dispatchTouchEvent 方法中返回 false

2.3.2 在 ViewGroup2 的 dispatchTouchEvent 方法中返回 false

2.3.3 在 ViewGroup1 的 dispatchTouchEvent 方法中返回 false

2.3.4 在 Activity 的 dispatchTouchEvent 方法中返回 false

从上面这些消息传递过程中可以看出,在控件的 dispatchTouchEvent 方法中返回 false 后, ACTION_DOWN 消息会在 dispatchTouchEvent 这条线上停止传递,并向其父控件的 onTouchEvent 方法回传。

2.4 在 onTouchEvent 方法中拦截 ACTION_DOWN 消息

onTouchEvent 方法中,默认返回 false,表示不拦截; 当返回 true 时, 表示拦截。

先说结论:
无论哪个控件的 onTouchEvent 方法拦截 ACTION_DOWN 消息, 消息都会直接停止传递, 后面的父控件都不会接收到这个消息。

总结:

  • dispatchTouchEvent 和 onTouchEvent 方法一旦返回true拦截消息, ACTION_DOWN 消息就会停止传递,正常流程下的后续节点都不会收到 ACTION_DOWN 消息了。
  • 当 dispatchTouchEvent 方法,返回 false 时,首先会拦截 ACTION_DOWN 消息,消息不会继续传给子控件,而是传给父控件的 onTouchEvent,然后继续回传。

3. 在 onInterceptTouchEvent 方法中的 ACTION_DOWN 消息传递流程

3.1 onInterceptTouchEvent 方法的作用

首先,只有 ViewGroup 具有 onInterceptTouchEvent 方法, Activity 和 View 中都没有这个方法。

然后, onInterceptTouchEvent 方法其实就是一个拦截过滤器。每个 ViewGroup 每次在处理消息分发时,都会问一问拦截器要不要拦截(也就是这个消息要不要自己处理)。要处理就返回true,然后交给自己的 onTouchEvent 方法处理。如果不拦截,就继续往子控件传递。默认是不拦截的。

3.2 在 ViewGroup 的 onInterceptTouchEvent 方法中拦截 ACTION_DOWN 消息

3.2.1 仅在 ViewGroup2 的 onInterceptTouchEvent 方法中拦截 ACTION_DOWN 消息

3.2.2 仅在 ViewGroup1 的 onInterceptTouchEvent 方法中拦截 ACTION_DOWN 消息

3.2.3 在 ViewGroup2 的 onInterceptTouchEvent 和 onTouchEvent 方法中拦截 ACTION_DOWN 消息

总结:

在 ViewGroup 的 onInterceptTouchEvent 方法中拦截 ACTION_DOWN 消息,会改变 ACTION_DOWN 消息的流向,让它直接流向当前 ViewGroup 的 onTouchEvent 方法。
同样,如果 onTouchEvent 方法没有拦截继续拦截消息,则消息会继续传递。如果某个 onTouchEvent 方法中拦截消息的话, ACTION_DOWN 会直接停止传递。

4. 关于 ACTION_MOVE 和 ACTION_UP 消息传递流程

上面讲解的都是 ACTION_DOWN 消息的传递流程,我们知道 ACTION_DOWN 之后,是 ACTION_MOVE, 最后是 ACTION_UP 。
这两者与 ACTION_DOWN 的消息传递并不完全一样。

因为 ACTION_MOVE 和 ACTION_UP 的消息流向是完全一样的,所以只讲 ACTION_MOVE。

4.1 在 dispatchTouchEvent 方法中返回 true 的消息传递流程

结论:
在 dispatchTouchEvent 方法中返回 true 拦截消息后, ACTION_MOVE 消息的流向与 ACTION_DOWN 完全相同,消息会直接停止传递,后面的子控件都不会接收到这个消息。

4.1 在 onTouchEvent 方法中返回 true 的消息传递流程

这部分很复杂,稍后补充。

ACTION_MOVE 消息总结:

  • 在 dispatchTouchEvent 方法中返回 true,拦截消息后, ACTION_MOVE 消息的流向与 ACTION_DOWN 消息的完全相同,消息会直接停止传递,后面的子控件都不会接收到这个消息。

  • 无论 ACTION_DOWN 的最终流向如何,只要最终流到 onTouchEvent 方法中就行。假设控件 A 最终在 onTouchEvent 方法中消费了 ACTION_DOWN 消息,那么 ACTION_MOVE 消息的流向就是先流到控件 A 的 dispatchTouchEvent 方法中,最终直接流到控件 A 的 onTouchEvent 方法中。进而消息停止传递。

事件传递流程

Activity -> PhoneWindow -> DecorView -> ViewGroup -> ... -> view

典型的责任链模式

如果最后的view也没有消费事件,那么事件就会被依次向上回传直到Activity,如果Activity也没有处理该事件,那么事件就被抛弃。
既保证了事件的有序性,又非常的灵活。

参考文献

《Android自定义控件高级进阶与精彩实例》

posted @ 2022-07-08 18:24  cfdroid  阅读(114)  评论(0编辑  收藏  举报