前言
Android 事件分发非常复杂,所以这篇文章会从事件的类型、事件的入口、事件分发的核心方法、事件的处理、ACTION_DOWN、ACTION_MOVE、ACTION_UP事件的分发多个角度将复杂的逻辑拆分各个维度来帮助大家分析事件分发的机制。
事件类型
事件主要包括以下这几种类型。
事件 | 简介 |
---|---|
ACTION_DOWN | 手指 初次接触到屏幕 时触发 |
ACTION_MOVE | 手指 在屏幕上滑动时触发,会多次触发 |
ACTION_UP | 手指 离开屏幕 时触发 |
ACTION_CANCEL | 事件 被上层拦截 时触发 |
这些事件被封装MotionEvent对象。按下、滑动、抬起、取消这几种事件组成一个事件流。事件流以按下为开始,中间可能有 若干次滑动,以抬起或取消作为结束。
在安卓对事件分发的处理过程中,主要是对按下事件作分发,进而找到能够处理按下事件的组件。对于事件流中后续的事件(如滑动、抬起等),则直接分发给能够处理按下事件的组件。
这篇文章不会过多讨论多点触碰事件类型,因为他的事件分发的流程跟单点触碰是一样的。他的正常事件流程是 ACTION_DOWN --> (ACTION_MOVE) --> ACTION_POINTER_DOWN --> (ACTION_MOVE) --> ACTION_POINTER_UP --> (ACTION_MOVE) --> ACTION_UP
事件的入口
Android的事件是自上而下流转的,从Activity -> ViewGroup -> View
所有事件的入口Activity#dispatchTouchEvent()
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
//1.mPhoneWindow superDispatchTouchEvent方法
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
//2.activity自己处理事件
return onTouchEvent(ev);
}
- 第一步getWindow() 其实调用的是
PhoneWindow#superDispatchTouchEvent
- 如果Activity的子View不消费事件,那么Activity将自己处理事件,调用
Activity#onTouchEvent()
Activity事件的分发PhoneWindow#superDispatchTouchEvent
// PhoneWindow.java
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
// DecorView.java
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
上面的代码可以看出PhoneWindow 还是通过DecorView来进行事件的分发,DecorView是ViewGroup的派生类所以还是通过ViewGroup进行分发。
小结
- 所有的事件入口都是要经过Activity进行分发,而真正执行分发的是DecorView也就是ViewGroup的
dispatchTouchEvent
处理事件分发逻辑。 - DecorView是ViewGroup的派生类,所以也会有自己特定的拦截逻辑和消费逻辑(有兴趣的同学可以自行看源码)
- 所有子类包含DecorView如果不消费事件,那么就会执行到
Activity#onTouchEvent
方法中
事件分发的核心方法
负责对事件进行分发的方法主要有三个,分别是:
组件 | dispatchTouchEvent | onTouchEvent | onInterceptTouchEvent |
---|---|---|---|
Activity | 存在 | 存在 | 无 |
ViewGroup | 存在 | 存在 | 存在 |
View | 存在 | 存在 | 无 |
这里要引入一个概念ViewGroup的dispatchTouchEvent方法是事件的分发,View的dispatchTouchEvent是事件的处理。为什么这么说呢?首先我们要知道的是ViewGroup派生自View,ViewGroup重载了View的dispatchTouchEvent方法,所以View和ViewGroup的dispatchTouchEvent有着两套不同的逻辑,在事件分发处理流程中有着不同的作用。
事件的处理
我们先从View的dispatchTouchEvent是事件的处理流程拿出来单独讲,这是在逻辑上比较独立、简单的流程模块。
dispatchTouchEvent
//View.java
public boolean dispatchTouchEvent(MotionEvent event) {
....
boolean result = false;
....
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Defensive cleanup for new gesture
// 当ACTION_DOWN手势时,停止之前的滑动
stopNestedScroll();
}
//这个判断方法是进行事件安全性筛选,我们不需要太过在意,一般情况都能执行到if语句里
if (onFilterTouchEventForSecurity(event)) {
//处理ScrollBar拖拽的逻辑,不用关注
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
//1.如果我们给View设置OnTouchListener,会优先执行OnTouchListener的onTouch方法
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
//2.如果事件没被上面消费掉,则执行onTouchEvent
if (!result && onTouchEvent(event)) {
result = true;
}
}
if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
// Clean up after nested scrolls if this is the end of a gesture;
// also cancel it if we tried an ACTION_DOWN but we didn't want the rest
// of the gesture.
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}
//返回是否消费事件的结果
return result;
}
所以事件处理的流程是先执行onTouch方法(如果设置了OnTouchListener),然后才是onTouchEvent方法
onTouchEvent
//View.java
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
//1.是否可点击标志,默认的false, 一般情况是在setOnclickListener 时候更新的标志位
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
// 如果是DISABLED 的View,直接返回clickable
return clickable;
}
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
//2.如果没设置过点击事件,直接返回false, 否则true
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
if ((viewFlags & TOOLTIP) == TOOLTIP) {
handleTooltipUp();
}
if (!clickable) {
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
break;
}
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
// take focus if we don't have it already and we should in
// touch mode.
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (prepressed) {
// The button is being released before we actually
// showed it as pressed. Make it show the pressed
// state now (before scheduling the click) to ensure
// the user sees it.
setPressed(true, x, y);
}
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// This is a tap, so remove the longpress check
removeLongPressCallback();
//3.手势抬起事件的时候执行PerformClick,然后回掉OnClickListener#onClick方法
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClickInternal();
}
}
}
....
}
mIgnoreNextUpEvent = false;
break;
case MotionEvent.ACTION_DOWN:
...
break;
case MotionEvent.ACTION_CANCEL:
....
break;
case MotionEvent.ACTION_MOVE:
....
break;
}
return true;
}
//4.不消耗事件
return false;
}
onTouchEvent方法里面会对View的各个事件响应, ACTION_UP事件的时候会处理onClick的点击事件,ACTION_DOWN处理onLongClick逻辑
事件处理流程图
事件的分发
这里会将事件的分发按照流程不同和先后执行顺序来进行拆分分析同一块代码,方便大家理解
TouchTarget
在ViewGroup中有个全局变量mFirstTouchTarget,这里存放处理ACTION_DOWN后续事件的View。
TouchTarget中有两个重要的成员变量。一个child变量是处理事件的View的引用,另一个是next指向下一个处理事件的TouchTarget(多点事件会存在多个TouchTarget)
ACTION_DOWN的事件分发
ACTION_DOWN是整个事件的起点,也是整个事件流闭环的开始,没有ACTION_DOWN也就没有后续的其他事件。
ViewGroup#dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent ev) {
.... 前面的代码不需要管
boolean handled = false;
//这个判断方法是进行事件安全性筛选,我们不需要太过在意,一般情况都能执行到if语句里
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Handle an initial down.
//1.当捕获到ACTION_DOWN(起始事件)时,清空ViewGroup所有与事件相关数据
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Throw away all previous state when starting a new touch gesture.
// The framework may have dropped the up or cancel event for the previous gesture
// due to an app switch, ANR, or some other state change.
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// Check for interception.
//事件拦截标志位
final boolean intercepted;
//这个时候 actionMasked == ACTION_DOWN ,mFirstTouchTarget =null(在第一步的时候clear)
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//disallowIntercept默认时false(在第一步的时候clear)
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
//2.执行onInterceptTouchEvent方法,标记是否拦截
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
//不会执行
intercepted = false;
}
} else {
//不会执行
intercepted = true;
}
....
//检查是否取消,默认时false。在事件分发ACTION_DOWN之后的流程才会有机会设置true
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
// Update list of touch targets for pointer down, if needed.
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
if (!canceled && !intercepted) {
....
//ACTION_DOWN事件一定执行下面if语句,以及多点触碰
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int actionIndex = ev.getActionIndex(); // always 0 for down
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
// Clean up earlier touch targets for this pointer id in case they
// have become out of sync.
removePointersFromTouchTargets(idBitsToAssign);
final int childrenCount = mChildrenCount;
//newTouchTarget还未赋值一定为空,判断有多少个子View
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
// Find a child that can receive the event.
// Scan children from front to back.
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
//遍历子View
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
// If there is a view that has accessibility focus we want it
// to get the event first and if not handled we will perform a
// normal dispatch. We may do a double iteration but this is
// safer given the timeframe.
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}
//判断点击事件是否能够接受多点事件或者点击事件是否在该子View上
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
//查找触摸的目标,是否已缓存到 mFirstTouchTarget 中,一般情况ACTION_DOWN newTouchTarget为null
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
// Child is already receiving touch within its bounds.
// Give it the new pointer in addition to the ones it is handling.
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
//3.如果child是ViewGroup,执行ViewGroup#dispatchTouchEvent,如果child是View则执行View#dispatchTouchEvent
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
// childIndex points into presorted list, find original index
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
//4.将要处理事件的View缓存到ViewGroup的全局mFirstTouchTarget变量中
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
// The accessibility focus didn't handle the event, so clear
// the flag and do a normal dispatch to all children.
ev.setTargetAccessibilityFocus(false);
}
if (preorderedList != null) preorderedList.clear();
}
....
}
}
//5.mFirstTouchTarget == null说明ViewGroup拦截ACTION_DOWN事件,或者ACTION_DOWN事件子View不消费,或者找不到可以处理事件的子View,自己处理事件
if (mFirstTouchTarget == null) {
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
//6.被子View消费ACTION_DOWN事件,handled直接标记true,看第3、4步已经消费了事件
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
//ACTION_DOWN 不会执行到这里
....
}
predecessor = target;
target = next;
}
}
....
}
....
return handled;
}
ViewGroup#dispatchTransformedTouchEvent
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
final MotionEvent transformedEvent;
if (newPointerIdBits == oldPointerIdBits) {
if (child == null || child.hasIdentityMatrix()) {
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
event.offsetLocation(offsetX, offsetY);
handled = child.dispatchTouchEvent(event);
event.offsetLocation(-offsetX, -offsetY);
}
return handled;
}
transformedEvent = MotionEvent.obtain(event);
} else {
transformedEvent = event.split(newPointerIdBits);
}
// Perform any necessary transformations and dispatch.
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
transformedEvent.offsetLocation(offsetX, offsetY);
if (! child.hasIdentityMatrix()) {
transformedEvent.transform(child.getInverseMatrix());
}
handled = child.dispatchTouchEvent(transformedEvent);
}
// Done.
transformedEvent.recycle();
return handled;
}
dispatchTransformedTouchEvent代码逻辑非常简单如果child == null || child 不是ViewGroup 调用View#dispatchTouchEvent
事件处理流程,否则调用ViewGroup#dispatchTouchEvent
进入事件分发流程,进入另一个事件分发循环
ACTION_MOVE 、 ACTION_UP 事件分发流程
ACTION_DOWN事件流走完之后会有3种情况
- 子View消费ACTION_DOWN事件,那么ViewGroup中mFirstTouchTarget成员变量标记子View
- ViewGroup自己拦截消费ACTION_DOWN事件,那么ViewGroup中mFirstTouchTarget为null
- ViewGroup和子View都不消费ACTION_DOWN事件,ViewGroup中mFirstTouchTarget依然为null
基于上面三种情况我们接下来进入事件分发的流程分析,上代码
public boolean dispatchTouchEvent(MotionEvent ev) {
....
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
.....
// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//1.1第一种情况会进入这里
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
//2.1正常流程调用ViewGroup的onInterceptTouchEvent
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
//2.2子View禁止父View拦截,通过#requestDisallowInterceptTouchEvent方法设置
intercepted = false;
}
} else {
//1.2第二、三种情况进入这里
intercepted = true;
}
.....
// Check for cancelation.
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
// Update list of touch targets for pointer down, if needed.
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
if (!canceled && !intercepted) {
.....
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
//不执行,除非多点触碰事件
......
}
}
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
//3.1第二、三种情况会执行这里
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
// Dispatch to touch targets, excluding the new touch target if we already
// dispatched to it. Cancel touch targets if necessary.
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
//不执行
handled = true;
} else {
//如果被ViewGroup 拦截,会给child分发cancel事件
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
//3.2第一种子View消费ACTION_DOWN事件之后走dispatchTransformedTouchEvent
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
....
}
....
return handled;
}
当子View消费事件时,如果不希望父View执行onInterceptTouchEvent
方法,可以通过调用requestDisallowInterceptTouchEvent(true)
。
总结
希望通过这篇对事件分发的分析,能够让大家更加清晰的了解事件分发的机制,以及自定义View能够写出更加稳定的自定义控件