本篇文章将从源码的角度解析事件分发机制的详细内容。关于上篇文章的那些情况迥异的分发处理过程,是如何在源码中实现的?本篇文章将逐一揭晓。
一、分发机制中三个方法的关系
上篇文章关于dispatchTouchEvent()方法,onInterceptTouchEvent()方法和onTouchEvent ()方法的流程进行了梳理。那么在源码实现中,三者之间的关系具体是什么样的?用一段伪代码来介绍。
/ 点击事件产生后,会直接调用dispatchTouchEvent()方法
public boolean dispatchTouchEvent(MotionEvent ev) {
//代表是否消耗事件
boolean consume = false;
if (onInterceptTouchEvent(ev)) {
//如果onInterceptTouchEvent()返回true则代表当前View拦截了点击事件
//则该点击事件则会交给当前View进行处理
//即调用onTouchEvent ()方法去处理点击事件
consume = onTouchEvent (ev) ;
} else {
//如果onInterceptTouchEvent()返回false则代表当前View不拦截点击事件
//则该点击事件则会继续传递给它的子元素
//子元素的dispatchTouchEvent()就会被调用,重复上述过程
//直到点击事件被最终处理为止
consume = child.dispatchTouchEvent (ev) ;
}
return consume;
}
上述伪代码清楚地描述了,事件分发从Activity->ViewGroup->View过程中,三大方法之间的调用关系。
二、Activity中的分发机制
上篇文章,我们介绍到,分发机制是从Activity开始的,当触摸屏幕时,Activity先感受到,并调用dispatchTouchEvent()方法进行分发。下面就来具体了解这一方法的内容。
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
//第一次按下操作时,用户希望能与设备进行交互,可通过实现该方法
onUserInteraction();
}
//获取当前Activity的顶层窗口是PhoneWindow,执行其superDispatchTouchEvent()方法
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
//当没有任何view处理时,交由activity的onTouchEvent处理
return onTouchEvent(ev);
}
可以看到,当Activity向下分发事件,最终没有任何控件进行处理后,将会交给Activity的onTouchEvent()方法处理。
继续看superDispatchTouchEvent()
方法
public boolean superDispatchTouchEvent(KeyEvent event) {
return mDecor.superDispatcTouchEvent(event);
}
PhoneWindow的最顶View是DecorView,再交由DecorView处理。而DecorView的父类的父类是ViewGroup,接着调用 ViewGroup.dispatchTouchEvent()方法。
所以Activity中的分发机制简述为:若不重写该方法,则调用根ViewGroup的dispatchTouchEvent()方法,进行分发,如果事件没有任何控件进行处理,则最后返回给Activity的onTouchEvent()方法进行处理。若重写该方法,不论返回值是false/true,都不会向下进行事件分发,也就是事件停止分发,已经在Activity中消费了。
三、ViewGroup中的分发机制
从Acivity中向下分发,就到达了ViewGroup中的dispatchTouchEvent()方法,下面来具体看看这个方法。
该方法比较复杂,篇幅有限,就截取几个重要的逻辑片段进行介绍,来解析整个分发流程。
// 发生ACTION_DOWN事件或者已经发生过ACTION_DOWN,并且将mFirstTouchTarget赋值,才进入此区域,主要功能是拦截器
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null) {
//disallowIntercept:是否禁用事件拦截的功能(默认是false),即不禁用
//可以在子View通过调用requestDisallowInterceptTouchEvent方法对这个值进行修改,不让该View拦截事件
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
//默认情况下会进入该方法
if (!disallowIntercept) {
//调用拦截方法
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action);
} else {
intercepted = false;
}
} else {
// 当没有触摸targets,且不是down事件时,开始持续拦截触摸。
intercepted = true;
}
这一段的内容主要是为判断是否拦截。如果当前事件的MotionEvent.ACTION_DOWN,则进入判断,调用ViewGroup onInterceptTouchEvent()方法的值,判断是否拦截。如果mFirstTouchTarget != null,即已经发生过MotionEvent.ACTION_DOWN,并且该事件已经有ViewGroup的子View进行处理了,那么也进入判断,调用ViewGroup onInterceptTouchEvent()方法的值,判断是否拦截。如果不是以上两种情况,即已经是MOVE或UP事件了,并且之间的事件没有对象进行处理,则设置成true,开始拦截接下来的所有事件。这也就解释了如果子View的onTouchEvent()方法返回false,那么接下来的一些列事件都不会交给他处理。其实这并不是onTouchEvent()方法傲娇,而是onInterceptTouchEvent()方法没给他机会,直接拦截了,不给子View机会。如果VieGroup的onInterceptTouchEvent()第一次执行为true,则mFirstTouchTarget = null,则也会使得接下来不会调用onInterceptTouchEvent(),直接将拦截设置为true。
当ViewGroup不拦截事件的时候,事件会向下分发交由它的子View或ViewGroup进行处理。
/* 从最底层的父视图开始遍历,
** 找寻newTouchTarget,即上面的mFirstTouchTarget
** 如果已经存在找寻newTouchTarget,说明正在接收触摸事件,则跳出循环。
*/
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = customOrder
? getChildDrawingOrder(childrenCount, i) : i;
final View child = (preorderedList == null)
? children[childIndex] : preorderedList.get(childIndex);
// 如果当前视图无法获取用户焦点,则跳过本次循环
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}
//如果view不可见,或者触摸的坐标点不在view的范围内,则跳过本次循环
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
newTouchTarget = getTouchTarget(child);
// 已经开始接收触摸事件,并退出整个循环。
if (newTouchTarget != null) {
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
//重置取消或抬起标志位
//如果触摸位置在child的区域内,则把事件分发给子View或ViewGroup
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// 获取TouchDown的时间点
mLastTouchDownTime = ev.getDownTime();
// 获取TouchDown的Index
if (preorderedList != null) {
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
//获取TouchDown的x,y坐标
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
//添加TouchTarget,则mFirstTouchTarget != null。
newTouchTarget = addTouchTarget(child, idBitsToAssign);
//表示以及分发给NewTouchTarget
alreadyDispatchedToNewTouchTarget = true;
break;
}
dispatchTransformedTouchEvent()
方法实际就是调用子元素的dispatchTouchEvent()
方法。
其中dispatchTransformedTouchEvent()
方法的重要逻辑如下:
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
由于其中传递的child不为空,所以就会调用子元素的dispatchTouchEvent()。
如果子元素的dispatchTouchEvent()方法返回true,那么mFirstTouchTarget就会被赋值,同时跳出for循环。
//添加TouchTarget,则mFirstTouchTarget != null。
newTouchTarget = addTouchTarget(child, idBitsToAssign);
//表示以及分发给NewTouchTarget
alreadyDispatchedToNewTouchTarget = true;
其中在addTouchTarget(child, idBitsToAssign);
内部完成mFirstTouchTarget被赋值。
如果mFirstTouchTarget为空,将会让ViewGroup默认拦截所有操作。
如果遍历所有子View或ViewGroup,都没有消费事件。ViewGroup会自己处理事件。
所以ViewGroup中的分发机制简述为:若子View或ViewGroup不处理MotionEvent.ACTION_DOWN事件,那么接下来的一些列事件都交由ViewGroup处理。若ViewGroup的onInterceptTouchEvent()执行为true,则接下来的所有事件都默认由该ViewGroup执行。若子View或ViewGroup处理MotionEvent.ACTION_DOWN事件,则接下来的事件处理交给谁要看onInterceptTouchEvent()的返回值,如果返回true,则第一个MOVE事件,会变成CANCEL事件,继续交由原来的子View或ViewGroup处理,接下来的一些列事件都交由ViewGroup执行。如果返回false,则继续交给原来的子View或ViewGroup处理。
四、View中的分发机制
View中的分发机制就比较简单了。上面ViewGroup中已经开始调用View.dispatchTouchEvent()方法,下面来具体看一下。
public boolean dispatchTouchEvent(MotionEvent event) {
...
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
//在Down事件之前,如果存在滚动操作则停止。不存在则不进行操作
stopNestedScroll();
}
if (onFilterTouchEventForSecurity(event)) {
ListenerInfo li = mListenerInfo;
//第一个条件默认为true
//第二个条件mOnTouchListener 不为空
//第三个条件该条件是判断当前点击的控件是否enable
//第四个条件onTouch返回值是true
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true; //满足上述四个条件,已经消费事件,则返回True
}
//如果OnTouch()返回false,或者没满足其他条件,没有消费Touch事件则调用OnTouchEvent()
if (!result && onTouchEvent(event)) {
result = true; //onTouchEvent(event)返回true,已经消费事件,则返回True
}
}
if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
// 处理取消或抬起操作
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}
return result;
}
针对上述四个条件的判断,很多View默认是(mViewFlags & ENABLED_MASK) == ENABLED
,通过设置OnTouchListener中的onTouch返回true,那么onTouchEvent()方法就不会调用,表明OnTouchListener的优先级高于onTouchEvent。
//手动调用设置
button.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
return false;
}
});
如果OnTouchListener中的onTouch返回false,那么会调用onTouchEvent()方法。
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
// 当View状态为DISABLED,如果可点击或可长按,则返回True,即消费事件
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
return (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
}
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
//当View状态为ENABLED,如果可点击或可长按,则返回True,即消费事件;
//与前面的的结合,可得出结论:只要view是可点击或可长按,则消费该事件.
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (prepressed) {
setPressed(true, x, y);
}
if (!mHasPerformedLongPress) {
//这是Tap操作,移除长按回调方法
removeLongPressCallback();
if (!focusTaken) {
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
//调用View.OnClickListener
if (!post(mPerformClick)) {
performClick();
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
mUnsetPressedState.run();
}
removeTapCallback();
}
break;
case MotionEvent.ACTION_DOWN:
mHasPerformedLongPress = false;
if (performButtonActionOnTouchDown(event)) {
break;
}
//获取是否处于可滚动的视图内
boolean isInScrollingContainer = isInScrollingContainer();
if (isInScrollingContainer) {
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
//当处于可滚动视图内,则延迟TAP_TIMEOUT,再反馈按压状态,用来判断用户是否想要滚动。默认延时为100ms
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
//当不再滚动视图内,则立刻反馈按压状态
setPressed(true, x, y);
checkForLongClick(0); //检测是否是长按
}
break;
case MotionEvent.ACTION_CANCEL:
setPressed(false);
removeTapCallback();
removeLongPressCallback();
break;
case MotionEvent.ACTION_MOVE:
drawableHotspotChanged(x, y);
if (!pointInView(x, y, mTouchSlop)) {
removeTapCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
removeLongPressCallback();
setPressed(false);
}
}
break;
}
return true;
}
return false;
}
只要View的CLICKABLE和LONG_CLICKABLE有一个为true,那么它就会消耗这个事件。
如果View设置了OnClickLisenter,那么performClick方法内部就会调用onClick方法。
public boolean performClick() {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
if (mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
mOnClickListener.onClick(this);
return true;
}
return false;
}
则表明onTouch()优先级高于onTouchEvent(),并高于onClick()。
所以View中的分发机制简述为:默认情况下,如果View的CLICKABLE和LONG_CLICKABLE有一个为true(默认LONG_CLICKABLE为false,一般可以点击的View中CLICKABLE为true。),则就会消费该事件,如果都为false,则不会消费该事件,dispatchTouchEvent()方法返回false,交由父控件的循环下一个子View进行同样操作。如果重写onTouchEvent()方法,返回false,dispatchTouchEvent()方法返回false,交由父控件的循环下一个子View进行同样操作。如果重写onTouchEvent()方法,返回true,则消费该事件。
以上就是源码解析事件分发的内容。
参考文章:
Android事件分发机制完全解析,带你从源码的角度彻底理解(上)
Android事件分发机制完全解析,带你从源码的角度彻底理解(下)
Android事件分发机制