事件处理和冲突
一、View
1. disPatchTouchEvent
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
//先处理onTouch
result = true;
}
在dispatchTouchEvent
方法中先调用这个View
的onTouchListener.onTouch
方法。如果onTouch
返回false
则执行下面的流程,否则就返回true
表示此事件已经被消费。
//从这里调用onclick
if (!result && onTouchEvent(event)) {
result = true;
}
在onTouchEvent
中方法判断如果当前事件为ACTION_UP
时就会调用onClick
的回调
//如果没能将这个调用放到Handler中,Handler退出了
if (!post(mPerformClick)) {
performClickInternal();
}
public boolean performClick() {
// We still need to call this method to handle the cases where performClick() was called
// externally, instead of through performClickInternal()
notifyAutofillManagerOnClick();
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
在这里就会调用到OnClickListener.onClick
方法。
总结:在View.dispatchTouchEvent
方法中会先执行OnTouchLitern.onTouch
方法,并且onTouch
可以响应所有的事件,如果返回false才会执行OnClickListener.onClick
方法,onClick
方法只能响应ACTION_UP
。如果既要执行onTouch
又要执行onClick
可以直接调用performClick
方法,来执行onClick
方法。
二 . ViewGroup
1. 形成消费链
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 (!child.canReceivePointerEvents()
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
//判断当前 的处理链中是否包含了这个View
newTouchTarget = getTouchTarget(child);
//当前的点击链中已经包含了这个View
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;
}
这里遍历 所有的子View
如果当前处理链中已经包含了某个子View,就直接退出循环,这个子View之后的View也就不会再收到通知。如果当前的处理链中不包含所有的子View,就调用dispatchTransformedTouchEvent
这个方法。
// Perform any necessary transformations and dispatch.
if (child == null) {
//父布局的View来处理
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());
}
//子View来处理
handled = child.dispatchTouchEvent(transformedEvent);
}
如果传入的子View为Null
则会调用当前这个ViewGroup
的父类(View.dispatchToucEvent
)方法,来处理onTouch和onClick
事件,如果传入的不为null这个方法又会递归的调用传入的子View
的dispatchTouchEvent
方法。如果这个方法最终返回true
即,有View消费了这个事件,就把这个View加入到处理链中。
newTouchTarget = addTouchTarget(child, idBitsToAssign);
//标记是否有新的子View加入到处理的链中
alreadyDispatchedToNewTouchTarget = true;
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
在addTouchTraget
方法中会创建一个TouchTarget
节点将,并把刚加入的View
作为链头,并设置mFirstTouchTraget
。
当遍历所有的子View
之并把处理当前事件的View加入处理链之后就会跳出循环(不再通知后面的View)。到这里其实这个事件已经被下面的View消费了,接下来就要标记这个处理的状态,用来告诉这个ViewGroup
的父View,它是否处消费过这个事件。
// Dispatch to touch targets.
//没有处理链
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
//没有子View处理这个事件,就调用onTouchEvent方法
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 {
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;
}
}
这个方法整体上分为两个部分:
-
没有处理链
ViewGroup
自己来处理这个事件 -
有处理链
这种情况又分为两种
- 有新加入的节点,直接将handle设为
true
(因为在前面这个节点已经处理过了,确认要消费才加入的) - 没有新的节点就遍历整条消费链,看是否有节点要消费,如果整条链上由节点消费了就返回
true
,否则就返回false
。这种情况下每一个处理链上的节点处理到收到这个事件,而不像之前遍历子View的。
- 有新加入的节点,直接将handle设为
2. ViewGroup
的二次选择机会
在整个的处理流程中当前的ViewGroup
有两次机会选择要不要处理这个事件。
- 开始的时候判断是否拦截这个事件
- 没有子
View
处理这个事件
第一种情况 即调用onInterceptTouchEvent
这个方法判断要不要拦截。
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//down事件由于重置一定为false
//子View通过调用requestDisallowInterceptTouchEvent,来告诉父View某个事件用不用通知子View
//如果为参数为true就告诉父View不要拦截,如果参数为false就让父View自己决定要不要拦截
//调用这个方法最主要的原因就是为了决定要不要走父View的onInterceptTouchEvent方法。不让它执行就默认不拦截
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;
}
}
如果ViewGroup
确定要拦截这个事件,就不会再询问所有的子View,而是判断当前处理链是否为空,如果为空就ViewGroup
自己处理,否则就给处理链上的节点依次处理。
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
//被拦截的时候回 把这个事件设置为Cancle
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
//询问当前View时
//调用onTouchEvent
handled = super.dispatchTouchEvent(event);
} else {
//询问子View时
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
ViewGroup
在确认拦截这个事件后会依次通知处理链上的每一个节点,并把一个CANCLE
事件传递给这些节点。
所以在被拦截之后子节点会收到一个Cancle
类型的事件。
//被拦截之后,会把链表置空,把表头置空
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
如果ViewGroup
拦截之后就会把mFirstTouchTarget
设置为null
,这样所有的子View再收到Cancle
事件之后就不能再 收到其他事件的通知了。
在
onInterceptTouchEvent
实现拦截是ViewGroup
的第一次选择机会
如果所有的子View都不处理这个Down
事件那么mFirstTouchTarget
就为null
,就会调用这个View
的disPatchTouchEvent
方法。
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
//没有子View处理这个事件,就调用onTouchEvent方法
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
如果所有的子View都不处理那么这个
ViewGroup
自己处理就是第二次选择机会
3.子View对父View的反向制约
从上面的分析中可以看出父View
如果拦截之后子View
就 无法再消费到事件。
[图片上传失败...(image-247a99-1593159300388)]
造成这种情况的原因是走进了if
条件中,如果子View
能够让if
条件不满足就能不调用父View
的的拦截方法。通过reqestDisallowInterceptTouchEvent()
方法能够设置mGroupFlag
的值,从而调整if
条件是否满足。
if (disallowIntercept) {
mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
} else {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
FLAG_DISALLOW_INTERCEPT
是一个非0的数,如果传入的参数为true
那么mGroupFlags&FLAG_DISALLOW_INTERCEPT
就不为0,if
条件就不满足,就默认不拦截;如果参数为false
时mGroupFlags&FLAG_DISALLOW_INTERCEPT
就一定 为0,if
条件就满足就会执行父View
的interceptTouchEvent
方法。
整型值的非运算
~7
-
先化成2进制
0000 0111 -
取反
1111 1000
-
计算补码
1000 0111 + 1 = 1000 1000
-8,首位为1表示负数。
网友评论