背景


前几天开始了对Android里事件分发相关源码的阅读,事件分发由三个对象Activity、ViewGroup、View依次向下传递,其间由三个方法dispatchTouchEvent()、onInterceptTouchEvent()(此方法只在ViewGroup里有)和onTouchEvent贯穿之中。

昨天晚上读完了第一个方法dispatchTouchEvent(),今天做此记录




Activity

Activty中的源码如下
public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() ==
MotionEvent.ACTION_DOWN) { onUserInteraction(); // 空方法 } if
(getWindow().superDispatchTouchEvent(ev)) { /* PhoneWindow类中实现: @Override
public boolean superDispatchTouchEvent(MotionEvent event) { return
mDecor.superDispatchTouchEvent(event); } DecorView里的实现: public boolean
superDispatchTouchEvent(MotionEvent event) { return
super.dispatchTouchEvent(event); } 调用的是父类FrameLayout的方法 */ return true; }
return onTouchEvent(ev); }

上来是调用了Window类的superDispatchTouchEvent(),然后是走到了DecorView的superDispatchTouchEvent(),DecorView则调用了父类FrameLayout的dispatchTouchEvent()方法,而FrameLayout没有覆写之,所以走的是ViewGroup的dispatchTouchEvent()。

而且ViewGroup的dispatchTouchEvent()返回false,才会调用Activity自己的onTouchEvent()




ViewGroup

源码如下,比较长,做了精简
@Override public boolean dispatchTouchEvent(MotionEvent ev) { .. //
event连续性判断和无障碍处理 boolean handled = false; if
(onFilterTouchEventForSecurity(ev)) { // 事件安全性过滤,正常都是true final int action =
ev.getAction(); final int actionMasked = action & MotionEvent.ACTION_MASK; //
Handle an initial down. if (actionMasked == MotionEvent.ACTION_DOWN) { //
ACTION_DOWN的时候清空touchTarget链表,因为按下事件是事件序列的开头 cancelAndClearTouchTargets(ev);
resetTouchState(); // 这个方法里,mFirstTouchTarget被赋值为null } // Check for
interception. final boolean intercepted; if (actionMasked ==
MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { //
ACTION_DOWN或者此次事件流里之前的事件有子view处理 final boolean disallowIntercept = (mGroupFlags
& FLAG_DISALLOW_INTERCEPT) != 0; if (!disallowIntercept) { // 正常是走这儿
intercepted = onInterceptTouchEvent(ev); //
调用ViewGroup.onInterceptTouchEvent()方法 ev.setAction(action); // restore action
in case it was changed } else { intercepted = false; } } else { //
没有点到这个ViewGroup的子view(说白了就是空白位置),那这次事件流的剩余事件全部被ViewGroup拦截 intercepted = true;
} .. //无障碍处理 // Check for cancelation. final boolean canceled =
resetCancelNextUpFlag(this) // 当前ViewGroup的cancel_next_up标志位清零 || actionMasked
== MotionEvent.ACTION_CANCEL; // 当前view是否被暂时移除,或者收到ACTION_CANCEL事件 // Update
list of touch targets for pointer down, if needed. final boolean split =
(mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0; //
标志位判断,split为true的话表示down事件分割给多个子view,一般为false 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) { // 只处理了Action_Down,因为这是事件的起点 final int
actionIndex = ev.getActionIndex(); // always 0 for down final int
idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex) :
TouchTarget.ALL_POINTER_IDS; // 一般取后者 .. // 清理上一次事件流的id final int childrenCount
= mChildrenCount; if (newTouchTarget == null && childrenCount != 0) { //
第一个判断是废话... final float x = ev.getX(actionIndex); // x坐标 final float y =
ev.getY(actionIndex); // y坐标 // Find a child that can receive the event. //
Scan children from front to back. final ArrayList<View> preorderedList =
buildTouchDispatchChildList(); final boolean customOrder = preorderedList ==
null // preorderedList绝不为null,但顺序也一般和mChildren顺序一致 &&
isChildrenDrawingOrderEnabled(); // 后一个判断分支一般为false final View[] children =
mChildren; for (int i = childrenCount - 1; i >= 0; i--) { // 从后往前遍历 final int
childIndex = getAndVerifyPreorderedIndex( childrenCount, i, customOrder); //
可以当成childIndex = i final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex); // 可以当成child = children[childIndex]; ..
// 无障碍处理 // 触点合法性判断 if (!canViewReceivePointerEvents(child) //
child是visible或者是在执行动画,canViewReceivePointerEvents()返回true ||
!isTransformedTouchPointInView(x, y, child, null)) { // 点击坐标是不是在当前子view内 .. //
无障碍处理 continue; } newTouchTarget = getTouchTarget(child); //
寻找之前确定过的处理这次事件流的子view的target if (newTouchTarget != null) { //
新的ACTION_DOWN事件的话,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); // 子view的cancel_next_up标志位清理 if
(dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { //
调用子view的dispatchTouchEvent(),若返回为true,则记录newTouchTarget,表示已经有子view处理事件了,退出循环 //
Child wants to receive touch within its bounds. mLastTouchDownTime =
ev.getDownTime(); if (preorderedList != null) { // preoderedList不可能是null //
childIndex points into presorted list, find original index for (int j = 0; j <
childrenCount; j++) { if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j; // 由于外面的遍历是从后往前,这次记录touchDown索引,就要从前往后遍历寻找 //
为毛不直接mLastTouchDownIndex = childrenCount - i? break; } } } else {
mLastTouchDownIndex = childIndex; } mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY(); newTouchTarget = addTouchTarget(child,
idBitsToAssign); // 将当前子view的touchTarget,此时mFirstTouchTarget被赋值,不再为null
alreadyDispatchedToNewTouchTarget = true; break; } .. // 无障碍处理 } if
(preorderedList != null) preorderedList.clear(); } //
没有子view处理这次事件,但是有子view处理这次事件流,但这个判断似乎永远不可能成立,因为这里只有ACTION_DOWN才能进来,但ACTION_DOWN的时候如果newTouchEventnull,mFirstTouchEvent也一定为null
if (newTouchTarget == null && mFirstTouchTarget != null) { // Did not find a
child to receive the event. // Assign the pointer to the least recently added
target. newTouchTarget = mFirstTouchTarget; while (newTouchTarget.next != null)
{ newTouchTarget = newTouchTarget.next; } newTouchTarget.pointerIdBits |=
idBitsToAssign; // 把id位设置给上一个事件流里最后一个事件的target } } } // 分发给touchTarget if
(mFirstTouchTarget == null) { //
没有子view处理这次事件流,就调用自己的(父类View)的dispatchTouchEvent(),分发给自己 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) { // 这个循环似乎只会循环一次,因为mFirstTouchTarget是按下事件的target,它的后继似乎是null final
TouchTarget next = target.next; if (alreadyDispatchedToNewTouchTarget && target
== newTouchTarget) { // 如果已经有子view处理事件并且找到了,方法结果是true handled = true; } else {
// 如果事件被拦截,会走这里 //
还没有找到相应的子view,就依次调用每个touchTarget的子view或viewGroup父类(View)的dispatchTouchEvent()
// 一旦有一个的dispatchTouchEvent()返回true,整体就返回true // 顺便如果事件被拦截,就销毁target链表 final
boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted; //
主要由intercepted决定cancelChild是不是true if (dispatchTransformedTouchEvent(ev,
cancelChild, target.child, target.pointerIdBits)) { //
如果cancelChild为true,也就是事件被拦截,似乎所有事件都被当成了Action_Cancel处理分发,故而此时子view只会收到cancel事件
handled = true; } if (cancelChild) { // 非down的事件被拦截 if (predecessor == null) {
// predecessor为null mFirstTouchTarget = next; //
之前的mFirstTouchTarget的后继为null,现在mFirstTouchTarget自己变成了null,所以之后的事件,只能走上面的分发给viewGroup自己了
} else { predecessor.next = next; } target.recycle(); target = next; continue;
} } predecessor = target; target = next; } } // 更新标志位 .. } // 检查event的连续性 ..
return handled; }

代码不短,只看跟触摸事件有关而且常见的。首先如果是down事件,就情况touchTarget链表,因为down事件是触摸事件流的起点,在resetTouchState()方法里面,也将mFirstTouchTarget置为null


mFirstTouchTarget表示处理down事件的子view的target,不为null,则表示有子view处理这个事件流,为null,就交给ViewGroup自己处理


说明一下,处理down事件也就是处理这个事件流,不处理down事件就被认为不处理这个事件流,不过即便处理down事件,子view也不见得能接收所有事件,因为ViewGroup可以拦截后续事件


而后进行判断,如果是down事件或者有子view处理这个事件流,就调用ViewGroup.onInterceptTouchEvent()方法判断ViewGroup是否拦截事件


不拦截还则罢了,拦截的话,如果拦截的是down事件,mFirstTouchTarget为null,整个事件流归ViewGroup处理;拦截的不是down事件,比如move事件,此时mFirstTouchTarget不是null,当前事件归子view管,但子view接收到的却变成了cancel事件,这次事件流之后的事件全归了ViewGroup


然后在不是cancel事件也没有拦截的情况下,只处理down事件,根据坐标找到被点击的子view后,先尝试找到之前确定过的,处理这次事件流的子view的target(但在down事件的情况下,这里似乎一般都是null),然后调用子view的dispatchTouchEvent()方法确定子view是不是要处理down,是的话,找到目标,调用addTouchTarget()方法给mFirstTouchTarget赋值,并把返回值赋给newTouchTarget,此刻,两者才不是null

所以在down的时候,newTouchTarget和mFirstTouchTarget要空都空,要不空都不是空

找到目标后,进行分发。


这里分发的就不一定是down事件了,如果mFirstTouchTarget是null,就只能分发给ViewGroup自己;如果不是null,就分发给mFirstTouchTarget对应的子view


如果ViewGroup拦截了down之后的事件,会导致cancelChild为true,但处理当前事件时,mFirstTouchTarget不是null,所以还是会分发给target.child,只不过这次分发的成了cancel事件。然后由于predecessor是null,导致mFirstTouchTarget被赋值成了后继(null),所以待到下一个事件来的时候,直接走了上一段说的判断分支,也就是mFirstTouchTarget是null的情况,不会调用自己的onInterceptTouchEvent()(两个条件down事件和mFirstTouchTarget为null都不成立),然后分发给自己,而不是子view

关于TouchTarget,可以参见我的文章安卓事件分发学习之TouchTarget源码阅读
<https://blog.csdn.net/qq_37475168/article/details/80486956>

这整个过程里,调用了一个关键方法dispatchTransformedTouchEvent()




ViewGroup#dispatchTransformedTouchEvent

源码如下
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean
cancel, View child, int desiredPointerIdBits) { final boolean handled; //
单独处理ACTION_CANCEL事件 final int oldAction = event.getAction(); if (cancel ||
oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL); if (child == null) { handled =
super.dispatchTouchEvent(event); } else { handled =
child.dispatchTouchEvent(event); } event.setAction(oldAction); return handled;
} // Calculate the number of pointers to deliver. final int oldPointerIdBits =
event.getPointerIdBits(); final int newPointerIdBits = oldPointerIdBits &
desiredPointerIdBits; // 一般情况下,old和new是一样的,而且都是ALL_POINTER_IDS // If for some
reason we ended up in an inconsistent state where it looks like we // might
produce a motion event with no pointers in it, then drop the event. if
(newPointerIdBits == 0) { // 此判断一般不成立 return false; } final MotionEvent
transformedEvent; if (newPointerIdBits == oldPointerIdBits) { // 走这里 if (child
== null || child.hasIdentityMatrix()) { // 一般情况下后一个判断为false if (child == null)
{ // 子view为null,调用父类View的dispatchEvent()方法 handled =
super.dispatchTouchEvent(event); } else { //
子view不为null,调整事件在view中的相对位置后再调用子view的dispatchTouchEvent()方法 //
但一般这儿走不到,分发给子view是走后面的逻辑 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 { // 在这里分发给子view 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; }
这里代码简单一些,分发给ViewGroup自己就是调用父类View的dispatchTouchEvent()




View#dispatchTouchEvent

代码如下
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
(onFilterTouchEventForSecurity(event)) { if ((mViewFlags & ENABLED_MASK) ==
ENABLED && handleScrollBarDragging(event)) { //
handleScrollBarDragging()用来处理鼠标拖动滚动条,直接是false,所以可以忽略这个判断分支 result = true; }
//noinspection SimplifiableIfStatement ListenerInfo li = mListenerInfo; if (li
!= null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) ==
ENABLED && li.mOnTouchListener.onTouch(this, event)) { //
先调用我们给View设置的onTouchListener方法的onTouch() result = true; } // result =
false才会调用View.onTouchEvent //
所以自定义的onTouchListener返回true,view的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. //
在ACTION_UP或ACTION_CANCEL时再次停止滚动(ACTION_MOVE时可能在滚动) if (actionMasked ==
MotionEvent.ACTION_UP || actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) { stopNestedScroll(); }
return result; }
代码也不复杂,可以得出结论onTouchListener的onTouch()比onTouchEvent()优先级要高




结语

在安卓事件分发学习之onInterceptTouchEvent方法
<https://blog.csdn.net/qq_37475168/article/details/80556748>
中,我会记录下一个方法--onInterceptTouchEvent的源码阅读

友情链接
ioDraw流程图
API参考文档
OK工具箱
云服务器优惠
阿里云优惠券
腾讯云优惠券
华为云优惠券
站点信息
问题反馈
邮箱:ixiaoyang8@qq.com
QQ群:637538335
关注微信