前言
难得有点时间,那就把前面学习的知识点总结一下下。
分发流程
当你动动手指头点了一下界面,点击事件就开始传递了,底层处理我们就不考虑了,首先从Activity到Window,再到我们熟悉的View。事件来到了根ViewGroup的dispatchTouchEvent方法,如果ViewGroup拦截了,那么会调用View的dispatchTouchEvent进行处理;如果 ViewGroup 不拦截了,将传递给子 View 中的 dispatchTouchEvent。以此类推,事件就这样传递下去了。如果子 View 不处理,那么 ViewGroup 就自己处理了,如果 ViewGroup 自己都不处理了,那么 Activity 的 onTouchEvent 会被调用。View 的dispatchTouchEvent方法 会处理一些监听事件优先级,如果设置了setOnTouchListener,那么onTouch会被调用,如果onTouch 返回false或者没有设置OnTouchListener,那么onTouchEvent被调用,如果设置了setOnClickListener那么onClick将会被回调。
这是简短的流程总结,当然这里有一些不足的地方,比如ViewGroup拦截不单单与onInterceptTouchEvent有关,还与事件类型,是否设置FLAG_DISALLOW_INTERCEPT标志位有关。
源码分析
#Activity
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
事件产生,传递给了当前的Activity,在上面的方法中进行了分发。交给了window的superDispatchTouchEvent方法处理,接下来看下实现类PhoneWindow。
#PhoneWindow
1828 @Override
1829 public boolean superDispatchTouchEvent(MotionEvent event) {
1830 return mDecor.superDispatchTouchEvent(event);
1831 }
mDecor就是DecorView,继续追踪。
#DecorView
439 public boolean superDispatchTouchEvent(MotionEvent event) {
440 return super.dispatchTouchEvent(event);
441 }
这里调用的是父类的dispatchTouchEvent方法,接下来看下ViewGroup的dispatchTouchEvent。
#ViewGroup --->dispatchTouchEvent
先看下拦截这部分的源码
2567 // Check for interception.
2568 final boolean intercepted;
2569 if (actionMasked == MotionEvent.ACTION_DOWN
2570 || mFirstTouchTarget != null) {
2571 final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
2572 if (!disallowIntercept) {
2573 intercepted = onInterceptTouchEvent(ev);
2574 ev.setAction(action); // restore action in case it was changed
2575 } else {
2576 intercepted = false;
2577 }
2578 } else {
2579 // There are no touch targets and this action is not an initial down
2580 // so this view group continues to intercept touches.
2581 intercepted = true;
2582 }
当事件类型为MotionEvent.ACTION_DOWN或者 mFirstTouchTarget != null时,如果没有设置FLAG_DISALLOW_INTERCEPT标志位,那么是否拦截由onInterceptTouchEvent(ev)返回值决定,如果设置了标志位,则不拦截。如果事件类型不为DOWN并且mFirstTouchTarget 为空,那么表示拦截此次事件。mFirstTouchTarget 是在什么时候不为空呢?当事件由ViewGroup的子View成功处理后,那么mFirstTouchTarget 会被赋值并且跟该子View相关联。
当ViewGroup不拦截此次事件时,ViewGroup会将事件继续分发到他的孩子。
if (!canceled && !intercepted) {
....
if (newTouchTarget == null && childrenCount != 0) {
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
//判断子View是否能够接受事件,View是否可见动画是否为空,点击事件位置是否落在View的范围内。
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
//过滤完条件后,会调用此方法,进行分发到子View
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
...
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
}
}
如果ViewGroup不拦截,满足条件!canceled && !intercepted,那么将会遍历子view,当然会进行过滤,如果子View满足条件,那么事件将分发到子View身上了,dispatchTransformedTouchEvent方法会被调用,需要注意这里有布尔类型的返回值,还有参数chlid,返回值表示是否处理此次事件,接着看一下这个方法大概做了什么。
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
...
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
...
}
重点大概就是这两句代码了,我们前面传进来的child不为空,即调用的是孩子的dispatchTouchEvent方法,这样事件就到了孩子身上了,注意孩子可能也是ViewGroup。
如果子View处理了这次事件,那么mFirstTouchTarget会被赋值,看下addTouchTarget方法。
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
如果子View不处理,那么会交给ViewGroup,即mFirstTouchTarget 为空,下面的代码将会被调用。
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
}
很清楚,又回到了dispatchTransformedTouchEvent方法,这次child参数传的是null,回到上面的代码,这里将调用的是super.dispatchTouchEvent(event)。如果ViewGroup不处理此次事件,那么事件将交给上级(Activity)去处理。这里就完成了一次事件的传递。从Activity到ViewGroup,再进行分发到子View。
下面再补充一些,如果事件类型为ACTION_DOWN,还会进行重置FLAG_DISALLOW_INTERCEPT标志位。
// Handle an initial down.
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Throw away all previous state when starting a new touch gesture.
// The framework may have dropped the up or cancel event for the previous gesture
// due to an app switch, ANR, or some other state change.
cancelAndClearTouchTargets(ev);
resetTouchState();
}
#View --->dispatchTouchEvent
接下来分析当ViewGroup调用了super.dispatchTouchEvent(event),或者是事件来到了直接继承于View的情况。这里是事件的处理过程,没有事件的分发,所以比较简单。
public boolean dispatchTouchEvent(MotionEvent event) {
...
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
//先判断上面的result,然后在调用onTouchEvent,
if (!result && onTouchEvent(event)) {
result = true;
}
}
由上面的代码可以看出,如果设置了OnTouchListener,那么onTouch将会被回调,如果返回结果为false,onTouchEvent才会被调用。由此可见,onTouchEvent的优先级比OnTouchListener低。
当View是DISABLED的,但是是可点击的,那么此次事件也会被消耗。
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return clickable;
}
如果事件类型为ACTION_UP并且View是可点击的,performClick()会被调用。如果设置了OnClickListener那么onClick会被调用。
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;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
notifyEnterOrExitForAutoFillIfNeeded(true);
return result;
}
到这里,View的事件分发就大概讲完了,接下来回顾一下三个重要的方法。
public boolean dispatchTouchEvent(MotionEvent ev)
用于事件的分发,返回结果表示是否消耗此事件,返回结果受当前super.dispatchTouchEvent和子View的dispatchTouchEvent方法影响。
public boolean onInterceptTouchEvent(MotionEvent ev)
ViewGroup中的方法,表示是否拦截此次事件。
public boolean onTouchEvent(MotionEvent event)
此方法是用于处理事件,表示是否消耗此次事件,在dispatchTouchEvent中被调用
到此,大概流程就讲完了,以此记录学习。