Android事件分发机制

806 阅读4分钟

一、概述

事件分发的事件指的是MotionEvent,主要方法有dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent。整个事件的流向是从Activity-->ViewGroup-->View自上往下调用dispatchTouchEvent方法,一直到View节点的时候。再自下往上回溯,由View->ViewGroup->Activity调用onTouchEvent方法。

二、源码分析

包含Activity、ViewGroup、View事件分发机制。

  1. Activity事件分发源码,Activity.java中:
public boolean dispatchTouchEvent(MotionEvent ev) {
  if (ev.getAction() == MotionEvent.ACTION_DOWN) {
    onUserInteraction();
  }
  /**
   * 如果getWindow().superDispatchTouchEvent(ev)返回true,则表示这个事件被消费。
   * 详细参考分析1
   */
  if (getWindow().superDispatchTouchEvent(ev)) {
    return true;
  }
  /**
   * 当一个点击事件未被Activity下任何一个View处理时,调用该方法
   * 详细参考分析3
   */
  return onTouchEvent(ev);
}

(1) 分析1:PhoneWindow.java中:

a. Window类是一个抽象类,唯一的实现类是:PhoneWindow

b. PhoneWindow中可以了解到superDispatchTouchEvent是如何分发的。mDecor(DecorView)继承了FrameLayout(FrameLayout是ViewGroup的子类),DecorView就是窗口的顶层View。也就是说在Activity的根布局外面还包了一层DecorView,我们的标题栏就是显示在DecorView中。

// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;
...
  
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
    /**
     * mDecor(DecorView的实例对象)
     * 详见分析2
     */
    return mDecor.superDispatchTouchEvent(event);
}

(3) 分析2:DecorView.java中:

public boolean superDispatchTouchEvent(MotionEvent event) {
  /**
   * DecorView继承了FrameLayout,FrameLayout又是ViewGroup的子类,
   * 因此ViewGroup是DecorView的间接父类。
   * super.dispatchTouchEvent等于间接调用ViewGroup的dispatchTouchEvent()方法。
   */
  return super.dispatchTouchEvent(event);
}

(2) 分析3:Activity.java中:

public boolean onTouchEvent(MotionEvent event) {
    // shouldCloseOnTouch()方法是判断事件是否为边界外的点击事件、event的坐标是否在边界内
    if (mWindow.shouldCloseOnTouch(this, event)) {
        finish();
        // 说明事件在边界外,消费事件。
        return true;
    }
    // 在边界内,未消费
    return false;
}
  1. ViewGroup事件分发源码:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
  // 分析关键代码,省略部分代码
  ...
  // 为了安全政策筛选触摸事件
  if (onFilterTouchEventForSecurity(ev)) {
    final int action = ev.getAction();
    final int actionMasked = action & MotionEvent.ACTION_MASK;
​
    // 如果触摸的时间类型是ACTION_DOWN,清除之前的处理,重新开始新的触摸动作
    if (actionMasked == MotionEvent.ACTION_DOWN) {
        cancelAndClearTouchTargets(ev);
        resetTouchState();
    }
​
    final boolean intercepted;
    // 如果是ACTION_DOWN(按下)事件类型 || 已经在处理子View的触摸事件
    if (actionMasked == MotionEvent.ACTION_DOWN
            || mFirstTouchTarget != null) {
        // 检查是否不允许拦截事件(调用requestDisallowInterceptTouchEvent(true)的情况)
        final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
        if (!disallowIntercept) {
            // 调用onInterceptTouchEvent方法确定是否拦截事件
            intercepted = onInterceptTouchEvent(ev);
            ev.setAction(action); // restore action in case it was changed
        } else {
            // 不拦截
            intercepted = false;
        }
    } else {
        // 如果不是一个新的触摸事件(down),说明当前的 View处理该事件,因此拦截
        intercepted = true;
    }
    // 如果事件被当前的View拦截,或者已经有处理该事件的目标,则进行普通的分发
    if (intercepted || mFirstTouchTarget != null) {
        ev.setTargetAccessibilityFocus(false);
    }
    ...
}  

以上是判断是否被拦截的源码分析。

后面源码大致的逻辑:

(1)事件如果没有被拦截并且为初始事件,会找到可以处理事件的子View。

(2)分发事件后如果该子View处理了事件,则存入TouchTarget(名为newTouchTarget的对象)链表,再挺中止对子View的遍历,如果该子View没有处理该事件,则继续遍历寻找。

(3)如果事件被拦截,向TouchTarget中的子View发送cancel事件。

(4)将没有被(2)、(3)情况处理的事件分发给TouchTarget中的子View,如果TouchTarget为空,则交给父View的dispatchTouchEvent()方法处理。

  1. View事件分发源码:

如果一个触摸事件分发到一个非ViewGroup的View或则不再向下分发该事件(没有处理的目标或者被拦截),那么View类的dispatchTouchEvent()将会被调用,源码:

public boolean dispatchTouchEvent(MotionEvent event) {
    // 判断是否需要通过首个无障碍焦点处理事件
    if (event.isTargetAccessibilityFocus()) {
        // We don't have focus or no virtual descendant has it, do not handle the event.
        if (!isAccessibilityFocusedViewOrHost()) {
            return false;
        }
        // We have focus and got the event, then use normal event dispatch.
        event.setTargetAccessibilityFocus(false);
    }
    boolean result = false;
    // 一致性检验,检查事件是否被改变
    if (mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onTouchEvent(event, 0);
    }
​
    final int actionMasked = event.getActionMasked();
    if (actionMasked == MotionEvent.ACTION_DOWN) {
        // 停止滚动
        stopNestedScroll();
    }
​
    if (onFilterTouchEventForSecurity(event)) {
        if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
            result = true;
        }
​
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            result = true;
        }
        // 如果没有被注册的onTouchEvent方法消耗事件,则调用View本身的onTouchEvent方法。
        if (!result && onTouchEvent(event)) {
            result = true;
        }
    }
    // 检查时间是否被改变
    if (!result && mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
    }
​
    // 如果触摸事件是UP、cancel、或者,是初始事件并且此时还没有对该初始事件进行处理,就停止滚动
    if (actionMasked == MotionEvent.ACTION_UP ||
            actionMasked == MotionEvent.ACTION_CANCEL ||
            (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
        stopNestedScroll();
    }
​
    return result;
}

onTouchEvent()方法处理了一系列的触摸事件, 判断是否触发单击、长按等,并且提供了默认的按下、点击、长按的视觉反馈。