之前都是在网上看别人的文章,很容易忘掉,今天重新翻一下源码并简单记录一下
先简单看一下dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent这三个方法在Activity、ViewGroup、View中是怎么个流程。如下图(有些地方可能画的不太准确)
源码
根据上图我们再梳理一下源码就容易很多了。
Activity事件传递
先从 Activity 的 dispatchTouchEvent看起
public boolean dispatchTouchEvent(MotionEvent ev) {
//判断是否是 MotionEvent.ACTION_DOWN 事件
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
//这是一个空的方法
onUserInteraction();
}
//调用到了 Window 中的 superDispatchTouchEvent
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
上面的getWindow().superDispatchTouchEvent(ev)
句话调用了抽象类Window 中的方法, PhoneWindow 是 Window 的唯一子类,所以我们看一下 子类PhoneWindow中的superDispatchTouchEvent
方法
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
ViewGroup事件分发
上面这个方法调用到了 DecorView 中的方法,而他是继承了 FrameLayout , FrameLayout又继承了ViewGroup,这里调用到了 ViewGroup 的 dispatchTouchEvent
方法
//DecorView
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
//ViewGroup
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
// If the event targets the accessibility focused view and this is it, start
// normal event dispatch. Maybe a descendant is what will handle the click.
if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
ev.setTargetAccessibilityFocus(false);
}
//该方法的最终返回值
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {//过滤触摸事件以确保安全
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Handle an initial down. 处理 ACTION_DOWN 事件
if (actionMasked == MotionEvent.ACTION_DOWN) {
//在开始一个新的Touch之前先清空所有的状态
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// Check for interception.// onInterceptTouchEvent 方法的结果
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//禁用拦截
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
//调用 onInterceptTouchEvent()方法,方法返回false,
intercepted = onInterceptTouchEvent(ev);//该方法决定是否拦截事件
//恢复Action 防止他被改变
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
...
//默认情况下canceled 和 intercepted 为false
if (!canceled && !intercepted) {
...
//判断是否是ACTION_DOWN 事件,如果是那么表示这是一个系列事件的开始
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
...
//这个方法是对事件进行分发(分发给子View)
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;
}
}
}
if (mFirstTouchTarget == null) {
//如果没有子View消费事件,这里最终会调用ViewGroup的onTouchEvent()
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
...
}
if (!handled && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
}
return handled;
}
ViewGroup中没有自己的onTouchEvent
方法,ViewGroup是继承在View的,最终是通过dispatchTransformedTouchEvent
方法将事件分发到View进行事件消费的。接下来看下这个方法是如何进行事件分发的
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
...
if (child == null) {
//如果 子View 为 null时说明事件不需要继续往下分发,调用本身父类(ViewGroup的父类是View)的dispatchTouchEvent,
//也就是说调用了View类中的dispatchTouchEvent方法
handled = super.dispatchTouchEvent(transformedEvent);
} else {
...
//如果子View不为null,则调用 子View的 dispatchTouchEvent 方法
handled = child.dispatchTouchEvent(transformedEvent);
}
...
// Done.
transformedEvent.recycle();
return handled;
}
上面调用到View类中的 dispatchTouchEvent
方法,事件就是从这个方法中消费的。
对于上面 super.dispatchTouchEvent
和 child.dispatchTouchEvent(transformedEvent);
这两个要分清,第一个是 ViewGroup 父类中的方法,第二个是 子View(比如 TextView、Button等) 的方法,这两个都是为了去调用 onTouchEvent 方法消费事件
从Activity——>ViewGroup到这算是结束了,接下来就是View中消费事件
View消费事件
public boolean dispatchTouchEvent(MotionEvent event) {
...
boolean result = false;
...
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Defensive cleanup for new gesture
stopNestedScroll();
}
if (onFilterTouchEventForSecurity(event)) {
//按钮是不是Enable的
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//ListenerInfo 监听的合集,OnFocusChangeListener、OnScrollChangeListener、OnClickListener等等
//如果他不为null说明我们设置了监听
//判断是否设置了onTouchListener 监听
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {//这里会执行OnTouchListener 的 onTouch 方法
result = true;
}
//如果设置了onTouchListener的监听并且返回为true时就不会调用onTouchEvent方法
//如果上面没有处理就会调用onTouchEvent
if (!result && onTouchEvent(event)) {
result = true;
}
}
...
return result;
}
上面这个方法可以看出onTouchListener
、onTouchEvent
的优先级;onTouchListener
—>onTouchEvent
还有一点 就onTouchListener
、onTouchEvent
这两个方法而言,如果没有 setOnTouchListener
那么一定会执行onTouchEvent
方法,如果设置了setOnTouchListener
那就要看onTouch
的返回值了
那么我们的setOnClickListener
是什么时候调用的呢,接下来看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();
//是否可以点击
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
...
//mHasPerformedLongPress是否已经执行了长按事件,true已经执行了,false未执行
// 还未执行长按事件时才能进行
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
//先移除还未被执行的长按事件
removeLongPressCallback();
// 只有在我们处于按下状态时才执行点击操作
if (!focusTaken) {
//使用Runnable 处理这个点击事件,而不是直接执行点击。目的就是在单击操作开始之前可以更新View的状态
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
//真正执行Click的方法,内部调用了performClick这个方法
performClickInternal();
}
}
}
break;
case MotionEvent.ACTION_DOWN:
...
if (isInScrollingContainer) {
...
} else {
// Not inside a scrolling container, so show the feedback right away
setPressed(true, x, y);
...
//长按事件,是一个延迟的消息,
//刚调用checkForLongClick方法时 mHasPerformedLongPress=false,
//当长按事件的延迟消息被执行后 mHasPerformedLongPress = true
checkForLongClick(
ViewConfiguration.getLongPressTimeout(),
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
}
break;
case MotionEvent.ACTION_CANCEL:
...省略代码
break;
case MotionEvent.ACTION_MOVE:
...省略代码
break;
... 省略代码
}
最后看一下performClick
这个方法,这个方法中还是使用LinstenerInfo 判断是否设置了 onClickListener
监听,如果用户手动设置了setOnclickListener()
, 就调用 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;
//ListenerInfo 各种监听的合集
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
//执行用户设置的 onClickListener 中的 onClick方法
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
notifyEnterOrExitForAutoFillIfNeeded(true);
return result;
}
到这整个流程就算是走完了,onTouchListener
、onTouchEvent
、onClick
的优先级也比较清楚了
onTouchListener
—>onTouchEvent
—>onClick
下面贴出事件分发的注意事项:
事件分发注意的问题.png
网友评论