你确定你对Android事件分发机制了然于胸了吗?如果能麻溜的回答出下面几个问题,还请大佬绕道,如果不能,请听我细细讲解:
1.View事件分发机制的传递过程是?onTouch,onTouchEvent,onClick的执行顺序?
2.DOWN事件分发机制的传递过程?遇到过滑动冲突吗?怎么解决的?
3.MOVE事件分发机制的传递过程?
4.事件分发机制流程及优缺点?
1. View事件分发机制的传递过程
1.1 View#dispatchTouchEvent:onTouch
public boolean dispatchTouchEvent(MotionEvent event) {
// 事件消费默认返回false
boolean result = false;
// 触摸事件安全过滤
if (onFilterTouchEventForSecurity(event)) {
// 判断有没有注册onTouchListener,onTouch事件是否被消费
// 如果事件被onTouch消费就不会继续往下执行;
// 如果没有被消费,继续执行onTouchEvent方法。
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
// 如果onTouchEvent事件被消费,则事件被消费;
// 如果onTouchEvent事件没有被消费,那么事件依然会继续往下传递。
if (!result && onTouchEvent(event)) {
result = true;
}
}
return result;
}
1.2 View#onTouchEvent:onTouchEvent
public boolean onTouchEvent(MotionEvent event) {
// 判断是否注册点击事件
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
// 有点击事件判断action
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
// ACTION_UP事件触发时,执行performClick方法。
performClick();
break;
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_CANCEL:
break;
case MotionEvent.ACTION_MOVE:
break;
}
return true;
}
return false;
}
1.3 View#performClick:onClick
public boolean performClick() {
final boolean result;
final ListenerInfo li = mListenerInfo;
// 如果设置了onClickListener,就会执行onClick方法,事件就会被消费。
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
return result;
}
小贴士:
View没有onInterceptTouchEvent方法,当有点击事件传递给它时,就会执行onTouchEvent方法。
onTouchEvent方法:默认消耗事件,但是如果View不可点击就不会消耗事件。
onClick事件会发生的前提:当前View可点击,并且收到了down和up事件。
方法执行顺序:onTouch-onTouchEvent-onClick
思考:
一个需求:onTouch返回true要执行onClick。
解决办法:在onTouch方法里ACTION_DOWN里加v.performClick();
场景:上层popupwindow挡住了下层view的点击事件
上层popupwindow设置isTouchable=false
2.DOWN事件分发机制的传递过程:
2.1 Activity#dispatchTouchEvent:
当我们点击屏幕时,触发MotionEvent.ACTION_DOWN事件,此时屏幕处于Activity界面,Activity#dispatchTouchEvent方法进行事件分发:
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
// 执行和用户交互的操作
// 例如聪明的管理状态栏通知,帮助activity在正确的时间取消通知
onUserInteraction();
}
// 将事件传递给Window,Window#superDispatchTouchEvent进行事件分发
if (getWindow().superDispatchTouchEvent(ev)) {
// Window消费了该事件,直接return退出该方法
return true;
}
4
// Window没有消费该事件,继续由Activity#onTouchEvent方法来处理
return onTouchEvent(ev);
}
小贴士:
return的用法:
结束该方法,继续执行方法后面的语句。
2.2 Window#superDispatchTouchEvent:
public abstract boolean superDispatchTouchEvent(MotionEvent event);
Window类:抽象类,superDispatchTouchEvent是抽象方法。
看Window类的介绍,这个抽象类的唯一存在实现是PhoneWindow。
双击shift,搜索PhoneWindow类。
PhoneWindow#superDispatchTouchEvent:
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
// PhoneWindow将事件传递给了DecorView
return mDecor.superDispatchTouchEvent(event);
}
2.3 DecorView#superDispatchTouchEvent:
public boolean superDispatchTouchEvent(MotionEvent event) {
// DecorView将事件传递给了ViewGroup
return super.dispatchTouchEvent(event);
}
2.4 ViewGroup#dispatchTouchEvent:
@Override
// 事件分发:dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
// 注释1
// 初始化的原因:一个完整的事件序列是以DOWN开始,以UP结束的。
// 如果是DOWN事件,说明这是一个新的事件序列,所以需要初始化之前的状态。
if (actionMasked == MotionEvent.ACTION_DOWN) {
// 清除触摸目标
cancelAndClearTouchTargets(ev);
// 重置触摸状态
// mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT代码意思是:将FLAG_DISALLOW_INTERCEPT标志位设置为false。
resetTouchState();
}
// 注释2
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
// 因为初始化,所以该值为false
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
// 事件拦截:onInterceptTouchEvent
// onInterceptTouchEvent返回true,intercepted为true;
// onInterceptTouchEvent返回false,intercepted为false。
intercepted = onInterceptTouchEvent(ev);
} else {
intercepted = false;
}
} else {
intercepted = true;
}
// 注释3
final boolean canceled = resetCancelNextUpFlag(this) || actionMasked == MotionEvent.ACTION_CANCEL;
// 注释4
TouchTarget newTouchTarget = null;
if (!canceled && !intercepted) {
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
for (int i = childrenCount - 1; i >= 0; i--) {
// 注释5
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
// 注释6
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
// 注释7
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
// 注释8
resetCancelNextUpFlag(child);
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
if (preorderedList != null) {
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
ev.setTargetAccessibilityFocus(false);
}
if (preorderedList != null) preorderedList.clear();
}
if (newTouchTarget == null && mFirstTouchTarget != null) {
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
// 注释9:
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;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
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;
}
}
// 注释10
if (canceled
|| actionMasked == MotionEvent.ACTION_UP
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
resetTouchState();
} else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
final int actionIndex = ev.getActionIndex();
final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
removePointersFromTouchTargets(idBitsToRemove);
}
}
return handled;
}
Down事件:
注释1:见代码。
注释2:
当onInterceptTouchEvent返回true,intercepted为true,执行注释9。
mFirstTouchTarget为空,没有进行任何赋值,执行dispatchTransformedTouchEvent方法。
dispatchTransformedTouchEvent:
canceled值:看注释3#resetCancelNextUpFlag默认为false,且没有执行ACTION_CANCEL事件,所以值为false。
private static boolean resetCancelNextUpFlag(@NonNull View view) {
// 如果if条件boolean类型为true,条件置为false,该方法返回true
if ((view.mPrivateFlags & PFLAG_CANCEL_NEXT_UP_EVENT) != 0) {
view.mPrivateFlags &= ~PFLAG_CANCEL_NEXT_UP_EVENT;
return true;
}
// 该方法默认返回false
return false;
}
dispatchTransformedTouchEvent:cancel为false,child为null,执行注释2:
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
final int oldAction = event.getAction();
// 注释1
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;
}
// 注释2
if (child == null) {
// 执行View#dispatchTouchEvent
// super关键字:用来调用超类里的方法,ViewGroup继承View。
// 事件交给父容器来处理。
handled = super.dispatchTouchEvent(transformedEvent);
} else {
handled = child.dispatchTouchEvent(transformedEvent);
}
return handled;
}
当onInterceptTouchEvent返回false,intercepted为false,假设canceled依然为false,执行注释4。
执行DOWN事件,newTouchTarget为空,mChildrenCount为ViewGroup中所有子View的数量不为0,执行buildTouchDispatchChildList方法:
public ArrayList<View> buildTouchDispatchChildList() {
// 对事件进行排序
return buildOrderedChildList();
}
buildOrderedChildList:将子视图从ViewGroup到View(比如Button)从前往后依次有序添加到list集合中,Z轴不断增大。
ArrayList<View> buildOrderedChildList() {
if (mPreSortedChildren == null) {
mPreSortedChildren = new ArrayList<>(childrenCount);
} else {
mPreSortedChildren.clear();
mPreSortedChildren.ensureCapacity(childrenCount);
}
for (int i = 0; i < childrenCount; i++) {
final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
final View nextChild = mChildren[childIndex];
final float currentZ = nextChild.getZ();
int insertIndex = i;
while (insertIndex > 0 && mPreSortedChildren.get(insertIndex - 1).getZ() > currentZ) {
insertIndex--;
}
mPreSortedChildren.add(insertIndex, nextChild);
}
return mPreSortedChildren;
}
回到注释4:执行for循环。
注释5:从后往前第一个得到的View为Button。
注释6:判断View是否能收到点击事件和点是否在它的坐标里。如果不能收到点击事件或者不在坐标里,继续。
注释7:getTouchTarget为空值,执行注释8。
注释8:
执行dispatchTransformedTouchEvent方法,child不为空,执行child#dispatchTouchEvent方法即View#dispatchTouchEvent方法。即Button在处理事件。如果Button处理了事件,执行注释8的if里的内容。先拿到所有点击事件的索引,然后用addTouchTarget方法将child赋值给mFirstTouchTarget,newTouchTarget为mFirstTouchTarget,alreadyDispatchedToNewTouchTarget赋值为true。最后执行break退出整个for循环。
注意:
如果注释8的View没有把事件消费掉,那么剩余的Activity,Window,ViewGroup就会一直调用dispatchTransformTouchEvent处理事件直到把事件消费为止。只要子View处理了事件,父View就再也不会接收到事件。
注释9:
mFirstTouchTarget不为空,target.next为空,handled为true。
小贴士:
二进制和按位操作符:& | ~
break:退出for循环。
2.5 ViewGroup#requestDisallowInterceptedTouchEvent:
定义:子View通过调用父View的requestDisallowInterceptTouchEvent方法来要求父View不拦截事件。
即:子View调用getParent().requestDisallowInterceptTouchEvent();
requestDisallowInterceptTouchEvent传的值true,disallowIntercept为true。
当为DOWN事件时,ViewGroup#dispatchTouchEvent中注释2处intercepted为false,会执行注释4,向子View传递。
@Override
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
return;
}
if (disallowIntercept) {
mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
} else {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
if (mParent != null) {
mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
}
}
标志位FLAG_DISALLOW_INTERCEPT的作用:
要求父View不拦截事件,向子View传递。
DOWN事件除外。因为如果是ACTION_DOWN事件,每次都会在注释1处重置手势状态。
小结:
DOWN事件的流程:
事件分发从父容器向子容器分发。
事件分发:dispatchTouchEvent
事件拦截:onInterceptTouchEvent。
为true不执行注释4,事件由父容器处理;
为false执行注释4,向子View传递,发生到ViewGroup时,先问Button处不处理该事件,不处理就交给ViewGroup,再根据for循环一层一层向上处理。
ViewGroup想要拦截事件,可以重写onInterceptTouchEvent方法返回true。
事件排序:buildTouchDispatchChildList,是为了从下往上排序处理。
事件处理:dispatchTransformedTouchEvent
事件消费:onTouchEvent
流程图:
[图片上传失败...(image-39be25-1692928698844)])
问题:
父View#onInterceptedTouchEvent设置为true,
DOWN事件requestDisallowInterceptedTouchEvent为true不起作用,还是会被父View拦截。
原因:
DOWN事件会在注释1处进行初始化,清除状态,导致disallowIntercept为false执行onInterceptTouchEvent方法为true被父View拦截。
解决:(重点)
父View#onInterceptedTouchEvent:
当点击事件为ACTION_DOWN时,return false,向子View传递。
如果onInterceptedTouchEvent是重写的方法,还要写方法super.onInterceptTouchEvent。
3.MOVE事件分发机制的传递过程:
执行完DOWN事件就开始执行MOVE事件。
3.1 ViewGroup#dispatchTouchEvent:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
// 注释1:不执行
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// 注释2:if 语句第二个条件mFirstTouchTarget不为空满足
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
// 事件拦截:onInterceptTouchEvent
intercepted = onInterceptTouchEvent(ev);
} else {
intercepted = false;
}
} else {
intercepted = true;
}
// 注释3
final boolean canceled = resetCancelNextUpFlag(this) || actionMasked == MotionEvent.ACTION_CANCEL;
// 注释4
TouchTarget newTouchTarget = null;
// 第一个 if 语句
if (!canceled && !intercepted) {
// 第二个 if 语句
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
for (int i = childrenCount - 1; i >= 0; i--) {
// 注释5
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
// 注释6
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
// 注释7
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
// 注释8
resetCancelNextUpFlag(child);
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
if (preorderedList != null) {
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
ev.setTargetAccessibilityFocus(false);
}
if (preorderedList != null)
preorderedList.clear();
}
if (newTouchTarget == null && mFirstTouchTarget != null) {
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
// 注释9:
// 第一个 if-else 语句
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;
// 第二个 if-else 语句
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
// 第三个 if 语句
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
// 第四个 if 语句
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
// 注释10
if (canceled
|| actionMasked == MotionEvent.ACTION_UP
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
resetTouchState();
} else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
final int actionIndex = ev.getActionIndex();
final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
removePointersFromTouchTargets(idBitsToRemove);
}
}
return handled;
}
默认:
注释2:onInterceptTouchEvent默认返回false,执行注释4。
注释4:第一个 if 语句满足,第二个 if 语句 不满足,执行注释9。
注释9:
第一个 if-else 语句,mFirstTouchTarget是全局属性,在DOWN事件后不为空,执行else。
第二个 if-else 语句,newTouchTarget是局部属性,没有执行注释4里的语句,所以为空,执行else。
第三个 if 语句,执行dispatchTransformedTouchEvent方法,cancel为false,child不为空,执行方法里注释2的else语句即child.dispatchTouchEvent方法。
第一次MOVE,父View拦截子View:
注释2:假设onInterceptTouchEvent返回true,不执行注释4,执行注释9。
注释9:
第一个 if-else 语句,同上。
第二个 if-else 语句,同上。
第三个 if 语句:
执行dispatchTransformedTouchEvent方法,cancel为true,child不为空,执行方法里的注释1。
MotionEvent事件设置为了ACTION_CANCEL,调用child.dispatchTouchEvent方法,但是最后事件还是oldAction即MOVE事件。
重点:此处child取消了事件。
第四个 if 语句:将mFirstTouchTarget置为了空。
第二次MOVE:
MOVE事件会多次触发,再从执行MOVE事件:
注释2:执行 else 语句 intercepted = true 部分。
注释4:不执行
注释9:执行第一个 if-else 语句的 if 语句,即 dispatchTransformedTouchEvent方法,cancel为false,child为空,执行方法里注释2的else语句super.dispatchTouchEvent即父View它自己的dispatchTouchEvent。
流程图:
[图片上传失败...(image-f7701e-1692928698844)])
MotionEvent中的四个事件:
ACTION_DOWN:手指初次接触到屏幕时触发
ACTION_MOVE:手指在屏幕上滑动时触发,会多次触发
ACTION_UP:手指离开屏幕时触发
ACTION_CANCEL:事件被上层拦截时触发
4.事件分发机制流程及优缺点:
事件分发机制其实是一个责任链模式,上级交给下级,下级完成不了再交给上级。但是这样做的缺点是层级太多,不够扁平化。
[图片上传失败...(image-5d1707-1692928698844)])