言简意赅的View分发机制
页面的布局是由ViewGroup或View叠加的显示的。当一个MotionEvent产生以后,系统需要一个具体的View去响应这个事件,也就是把这个事件传递给一个View去处理,这个传递过程就是View的分发。
从宏观的角度看,他的传递过程应该是Activity->PhoneWindow->DecorView->ViewGroup->View…,这是一个整体的分发过程,但是从微观的实现角度讲,绍复杂一些。下面对这个过程进行一个详尽的描述。
一、基础概念
在说分发流程前先说两个常见的概念,View树和Touch事件,接下来简要描述一下。
1、View树
事件分发中只有两个主角ViewGroup和View,ViewGroup和View的关系可以描述成一个树状结构。根节点为Activity布局中包含的一个ViewGroup,往下延申有若干个ViewGroup或View节点,每个ViewGroup节点下又可延伸若干个ViewGroup或View节点,以此类推。事件分发机制的核心可以说就是View树的一个遍历过程。
2、Touch事件
Touch事件由Action_Down、Action_Move和Action_Up组成,具体的含义不说了,从字面意思应该就能理解。Down和Up事件都只有一个,Move有若干个,也可以是0个,Touch事件可被封装成MotionEvent对象,在onTouchEvent回调中传递给对应的Activity。
二、分发流程
View的分发流程中有三个重要的方法,onInterceptTouchEvent、dispatchTouchEvent、onTouchEvent,其中ViewGroup中包含onInterceptTouchEvent、dispatchTouchEvent、onTouchEvent三个相关事件,View包含dispatchTouchEvent、onTouchEvent两个相关事件。其中,dispatchTouchEvent用于进行事件的分发,onInterceptTouchEvent用于判断是否拦截某个事件,onTouchEvent在dispatchTouchEvent方法中调用,用来处理点击事件。下面从源码的角度来分部来分析下具体的分发流程。
1、Activity#dispatchTouchEvent源码
当一个点击操作发生时,事件肯定最先传递给当前的Activity,然后调用Activity的dispatchTouchEvent来进行事件分发。
// Activity#dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
//该方法是个空实现
onUserInteraction();
}
//事件的分发是由window的superDispatchTouchEvent方法完成的
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
// Window#superDispatchTouchEvent
// 可以看到该方法是个抽象方法,然而Window只有一个实现类PhoneWindow,下面来看看PhoneWindow内该方法的实现
public abstract boolean superDispatchTouchEvent(MotionEvent event);
// PhoneWindow#superDispatchTouchEvent
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
// 应该很清晰了,PhoneWindow将事件传递给了DecorView
return mDecor.superDispatchTouchEvent(event);
}
总结:当点击事件来临的时候,首先会调用Activity的dispatchTouchEvent,而实际具体的工作是由Activity内部的Window来完成的。Window又是个抽象类,其具体实现再PhoneWindow中,PhoneWindow又将这个事件传递给了DecorView来处理。这个DecorView在之前的View绘制流程的文章中说过,也就是我们setContentView设置的View,它是所有View的顶层View,一般来说是一个ViewGroup。
2、ViewGroup#dispatchTouchEvent源码
注:代码中需要知道的地方均以Focus开头标记出来,在代码后分别分析。由于ViewGroup#dispatchTouchEvent太长,现按步骤分别来看。
步骤一:判断当前ViewGroup是否要拦截事件
ViewGroup#dispatchTouchEvent
// Check for interception.
final boolean intercepted;
// Focus one
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
// Focus two
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
// Focus three
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action