事件分发|Android开发系列

401 阅读3分钟

这是我参与8月更文挑战的第24天,活动详情查看:8月更文挑战

介绍

当我们点击、长按、滑动屏幕时,都会产生一个个事件,这些事件被封装为MotionEvent。分发机制就是将一个事件从屏幕传递给应用的各个View/ViewGroup,然后由其中的View来消费或者忽略这一个事件。

View的继承关系

image.png

该图源于网络
  1. 继承自View的只能处理事件 像ImageView它是继承自View的没有子View,当它遇到事件时,只有俩个选择处理这个事件和不处理这个事件。

  2. 继承自ViewGroup的才能进行分发事件 ViewGroup相当于一个容器包含子View可以进行分发事件,

分发对象

image.png 具体在传递事件的时候,是由以下三个方法来控制的:

  • dispatchTouchEvent : 分发事件
  • onInterceptTouchEvent : 拦截事件
  • onTouchEvent : 消费事件

事件分发的流程

事件分发机制使用的是责任链设计模式,从Activity如果传到最下层的View都没有组件处理该事件,该事件会依次回传到Activity

事件流程图

graph TD
A[Avtivity#diapthchTouchEvent] --> B[PhoneWindow#superDispatchTouchEvent]
    B --> C[DecorView#superDispatchTouchEvent]
    C --> D[ViewGroup#dispatchTouchEvent]
    D --> |处理事件| F[View#dispatchTouchEvent]
    F --> G[View#onTouchEvent]

事件分发源码分析

Acticity.java

这里优先响应子View,因为当父View决定不拦截子View后,就会调dispatchTouchEvent,ViewGroup的这个方法会先去遍历调用子View的dispatchTouchEvent,如果都返回false,该事件会依次回传到Activity。 先往下看待会回来理解这句话。

public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();//空实现,锁屏相关
        }
	//Window仅存在一个实例PhotoWindow,在Window里找不到代码去PhotoWindow里找
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }

Avtivity中调用Window.superDispatchTouchEvent(ev),但是在Window类中没有superDispatchTouchEvent()的方法,PhotoWindow作为Window仅存在的一个实例存在该方法。

PhoneWindow.java

public boolean superDispatchTouchEvent(MotionEvent event) {
    return mDecor.superDispatchTouchEvent(event);
}

PhoneWindow中调用了DecorView.superDispatchTouchEvent()

DecorView

public boolean superDispatchTouchEvent(MotionEvent event) {
    return super.dispatchTouchEvent(event);
}

image.png

DecorViewextendsFrameLayoutextendsViewGroup

dispatchTouchEvent真正的逻辑是存在于ViewGroup中的

ViewGroup.java image.png 上诉代码:ACTION_DOWN事件一定会交由ViewGroup处理,子View没办法拦截,因为resetTouchState()会把requestDisallowInterceptTouchEvent()所置的标志位重置

intercept表示是否拦截事件。

onInterceptTouchEvent()intercept进行初始化。大多数情况下,onInterceptTouchEvent()返回值为false,但我们可以重写onInterceptTouchEvent()来改变它的返回值。

image.png canceled检查是否取消,事件被上层是否拦截 image.png

image.png 可以看到,当intercept是false以及canceled,会通过for循环去遍历ViewGroupchild,然后调用dispatchTransformedTouchEvent(),如果dispatchTransformedTouchEvent()返回值是true,就去调用addTouchTarget()。 而当intercept是true时,就不会去遍历ViewGroupchild,也更不会调用childdispatchTransformedTouchEvent()了。

image.png 传入给dispatchTransformedTouchEvent()child不为null,所以调用的是子View的dispatchTouchEvent()
如果子View的dispatchTouchEvent()返回true,则调用addTouchTarget()

image.png 将指定子级的触摸事件添加到列表的开头。

当事件传递给ViewGroup,先去遍历调用child的dispatchTouchEvent(),如果有child的dispatchTouchEvent()返回了truemFirstTouchTarget就被赋值,否则mFirstTouchTarget就为null

image.png 如果mFirstTouchTargetnull,就去调用dispatchTransformedTouchEvent()

未完。。。