一、概述
事件分发的事件指的是MotionEvent
,主要方法有dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent。整个事件的流向是从Activity-->ViewGroup-->View自上往下调用dispatchTouchEvent方法,一直到View节点的时候。再自下往上回溯,由View->ViewGroup->Activity调用onTouchEvent方法。
二、源码分析
包含Activity、ViewGroup、View事件分发机制。
- 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;
}
- 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()方法处理。
- 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()方法处理了一系列的触摸事件, 判断是否触发单击、长按等,并且提供了默认的按下、点击、长按的视觉反馈。