Android 事件分发机制
2019-09-25 本文已影响0人
pj0579
事件分发的分析文章网上很多,自己看来比较好的文章
美团开发人员的
https://www.jianshu.com/p/e99b5e8bd67b#
郭神的
https://blog.csdn.net/guolin_blog/article/details/9153747
结合自己的理解说下
总结就是 最底部的dispatchTouchEvent
一旦return true(View可点击时的dispatchTouchEvent
肯定返回ture),事件就开始一层层往回传了, 最底部的view肯定有你自己写的onClick之类或者View可点击促使返回true消耗了这次事件,默认回传的时候父View不会执行自己的点击事件直接向上层返回true。
OnTouchEvent
首先View
的dispatchTouchEvent
方法默认是这样的
public boolean dispatchTouchEvent(MotionEvent event) {
// 主体是这些代码
boolean result = false;
.......
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
// 默认返回 false
// 在一个普通的view触摸事件执行时 需要这些条件才会返回true,onTouch先于onTouchEvent执行
result = true;
}
// onTouchEvent 默认返回false
if (!result && onTouchEvent(event)) {
result = true;
}
......
return result;
}
当 dispatchTouchEvent
返回true
的时候 开始一级级往上传递true,当没有子 view返回true的时候,则会去执行自己的super.dispatchTouchEvent
。
然后子view的dispatchTouchEvent
方法是他的父ViewGroup
调用的。
看下ViewGroup
是怎么分发事件的 代码很长 部分精简 主要看设置handled
相关的办法
public boolean dispatchTouchEvent(MotionEvent ev) {
...
// 是否拦截 true的话 不会向子View传递事件
final boolean intercepted;
boolean handled = false;
// 如果子View ACTION_DOWN 没有返回true ,mFirstTouchTarget 为空 直接拦截 不会到子View里去
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
intercepted = true;
}
if (!canceled && !intercepted) {
// 开始对子view进行事件分发
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
// 找到触摸到的子View
///////处理事件返回true的时候才会执行
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();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
}
}
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// 没有对应的view则自己处理
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
// 分发事件给子view, 如果有必要取消之前的
// 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 {
// MOVE事件被拦截的话 会传递CANCEL事件给子View
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
// 判断执行子view的c还是自己的super.dispatchTransformedTouchEvent()
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
// 子view返回true
handled = true;
}
}
return handled;
}
除了这个正常的事件分发,还有一些特殊的情况没有展示,比如DOWN事件子View返回true,
MOVE事件被父View拦截的话 会造成第一个MOVE事件作为一个CANCEL事件传递给子View,之后的事件都在这个父View上被消费,等等情况还有很多。
ViewGroup的dispatch也相应返回true 直到最顶层的View的 dispatchTouchEvent。