View的事件分发机制
前言
今天看了好久的View的事件分发机制,感觉懵懵懂懂,最后干脆自己来看看源码吧,然后总结了分发的过程。
View的分发机制(非ViewGroup):
当一个MotionEvent
事件到来的时候,一定是先调用View
的dispatchTouchEvent
方法,此方法是用来分发的,返回true
代表事件被消耗,返回false
则代表没有消耗。
dispatchTouchEvent()
精华源码:
public boolean dispatchTouchEvent(MotionEvent event) {
...
...
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
//在这个条件条件中 进行了onTouch事件的回调
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
//如果上面onTouch事件返回了true,那么这里的onTouchEvent便不会被执行了
//因为result为真
if (!result && onTouchEvent(event)) {
result = true;
}
}
...
...
return result;
}
onTouch
方法是通过setOnTouchListener
设置的。
所以通过阅读上面的源码,可以得出结论:onTouch
返回 true
,那么onClick
无法被执行,因为onClick
事件是在onTouchEvent
方法里面触发的。
onTouchEvent
的部分源码:
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
//view处于disable状态
//如果是clickable或者设置了长按事件 返回true
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return (((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
}
...
...
//如果是clickable或者设置了长按事件 最终返回true
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
(viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
switch (action) {
case MotionEvent.ACTION_UP:
...
...
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// This is a tap, so remove the longpress check
removeLongPressCallback();
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClick();//在这里面执行回调onClick函数
}
}
}
}
return true;//返回true
}
return false;
}
删除的部分很多是对press状态的设置。
在onTouchEvent
中,如果view
设置了clickable
,或者设置了onClickListener
或onLongClickListener
,都会导致该方法返回 true
,也就是会导致View的dispatchTouchListener
返回true
,父容器就知道你要消耗事件,父容器就不会对事件进行拦截。
总结一下View的分发机制:
- 当有
MotionEvent
消息来到的时候,会先调用dispatchTouchEvent
方法 - 在
dispatchTouchEvent
中会调用onTouch
方法,如果该方法返回false,则会调用onTouchEvent
方法,在此方法中会触发onClick
与onLongClick
方法。 -
onTouch
与onTouchEvent
都返回了false,dispatchTouchEvent
才会返回false
- 如果在
ACTION_DOWN
事件中,如果dispatchTouchEvent
返回了false
,那么之后的MOVE
与UP
等事件该View
都无法收到。这点要结合下面的分析。
ViewGroup分发机制
ViewGroup
的父类是View
,所以对于super
方法的调用就是调用上面View
的方法。
ViewGroup
重写了dispatchTouchEvent
方法,并多了一个拦截的方法:onInterceptTouchEvent()
依然先来看看ViewGroup
重写的dispatchTouchEvent
方法,由于方法还是蛮重要的,所以我一部分一部分来讲解,先来看看第一部分:
......
final boolean intercepted;
//检查是否拦截MotionEvent的传递
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//默认为false
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 {
//没有消耗事件的目标view
//而且不是ACTION_DOWN事件 所以直接拦截掉
intercepted = true;
}
从上面我们可以得出一些信息:
- 如果是
ACTION_DOWN
事件,或者mFirstTouchTarget
不为空,即有子View消耗事件,会重新查看是否需要拦截事件。( if 中的内容 ) -
disallowIntercept
的值起着关键的作用,如果值为true
,那么intercepted
会设为false
,即不拦截。 - 如果
onInterceptTouchEvent
(ev)返回了true,那么会进行拦截 - 如果
mFirstTouchTarget
为空(这个变量说明之前没有消耗事件的子View),并且不是ACTION_DOWN
事件,那么直接拦截事件,不再进行分发。( else 中的内容 )
接着下面的代码:
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
TouchTarget newTouchTarget = null; //用来保存消耗事件的view
boolean alreadyDispatchedToNewTouchTarget = false;
if (!canceled && !intercepted) {//不拦截才进入下面的代码块
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {//DOWN事件才会执行下面
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
// Find a child that can receive the event.
// Scan children from front to back.
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的dispatchTouchEvent方法的
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
}
}
}
上面我删除了很多代码,留下了最主要的几个部分:
-
intercepted
为false
才会执行上面的代码 (见第一个if判断) - 只有
DOWN
事件才会执行第二个if块的代码。所以如果是MOVE
或者是UP
事件,那么事件会直接分发给目标View
。 - 倒叙遍历子
view
,点击坐标在子view
范围之内的才进行事件的传递 -
dispatchTransformedTouchEvent
返回值为true
的时候,会为newTouchTarget
赋值,也就是消耗事件的view
,且调用了addTouchTarget
方法,此方法会将子view
加入到mFirstTouchTarget
链表之中。
我们看继续最后一部分:
// 没有子view消耗事件
if (mFirstTouchTarget == null) {
// 第三个参数为null 方法中会调用父类的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) {
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;
}
}
...
return handled;
}
如果消耗事件的子View
为空,那么会把自己当做View
对待,通过super.dispatchTouchEvent
方法调用自己的onTouch
与onTouchEvent
方法。
如果消耗事件的子View
不为空,那么会调用子View
的dispatchTouchEvent
方法。
我们再看看上面出现了两次的dispatchTransformedTouchEvent
方法:
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
// Perform any necessary transformations and dispatch.
if (child == null) {
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());
}
handled = child.dispatchTouchEvent(transformedEvent);
}
...
可以看到如果child对象不为空,那么会调用子child的dispatchTouchEvent方法,否则调用父类的dispatchTouchEvent方法,也就是View原生的dispatchTouchEvent方法,此方法会调用自己的onTouch和onTouchEvent方法(如果满足调用条件)
再来看看onInterceptTouchEvent方法:
public boolean onInterceptTouchEvent(MotionEvent ev) {
return false;
}
很简单,直接返回了false。
所以再来总结一下ViewGroup的dispatchTouchEvent方法:
- 如果disallowIntercepted没被子view修改,那么intercepted变量的值取决于onInterceptTouchEvent方法。
- 如果intercepted的值为false,那么会进行事件的分发,由符合条件的子view的dispatchTouchEvent进行处理
- 如果没有符合条件的,那么调用super.dispatchTouchEvent方法进行处理,也就是View的dispatchTouchEvent方法,此方法会调用onTouch和onTouchEvent方法(如果满足调用条件)
- 如果intercepted的值为true,那么不会进行分发,会去调用super.dispatchTouchEvent
- 如果是DOWN事件,那么会遍历子View进行事件分发,如果是MOVE或者是UP事件,则会直接交给目标View进行处理(可能是自己)
这里画了一个图来表示一下,橘色代表ViewGroup的方法,蓝色代表子View的方法。
后记
这是我自己看了别人很多的总结之后,自己再对着源码走一遍,总结出来的自己的感想。
如果哪里有错误,请务必指出,感激不尽!