之前都是在網(wǎng)上看別人的文章,很容易忘掉切油,今天重新翻一下源碼并簡單記錄一下
先簡單看一下dispatchTouchEvent认罩、onInterceptTouchEvent皱蹦、onTouchEvent這三個方法在Activity衫冻、ViewGroup诀紊、View中是怎么個流程。如下圖(有些地方可能畫的不太準確)
源碼
根據(jù)上圖我們再梳理一下源碼就容易很多了隅俘。
Activity事件傳遞
先從 Activity 的 dispatchTouchEvent看起
public boolean dispatchTouchEvent(MotionEvent ev) {
//判斷是否是 MotionEvent.ACTION_DOWN 事件
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
//這是一個空的方法
onUserInteraction();
}
//調(diào)用到了 Window 中的 superDispatchTouchEvent
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
上面的getWindow().superDispatchTouchEvent(ev)
句話調(diào)用了抽象類Window 中的方法邻奠, PhoneWindow 是 Window 的唯一子類,所以我們看一下 子類PhoneWindow中的superDispatchTouchEvent
方法
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
ViewGroup事件分發(fā)
上面這個方法調(diào)用到了 DecorView 中的方法为居,而他是繼承了 FrameLayout , FrameLayout又繼承了ViewGroup碌宴,這里調(diào)用到了 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之前先清空所有的狀態(tài)
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) {
//調(diào)用 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) {
...
//這個方法是對事件進行分發(fā)(分發(fā)給子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消費事件,這里最終會調(diào)用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
方法將事件分發(fā)到View進行事件消費的董朝。接下來看下這個方法是如何進行事件分發(fā)的
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
...
if (child == null) {
//如果 子View 為 null時說明事件不需要繼續(xù)往下分發(fā)鸠项,調(diào)用本身父類(ViewGroup的父類是View)的dispatchTouchEvent,
//也就是說調(diào)用了View類中的dispatchTouchEvent方法
handled = super.dispatchTouchEvent(transformedEvent);
} else {
...
//如果子View不為null,則調(diào)用 子View的 dispatchTouchEvent 方法
handled = child.dispatchTouchEvent(transformedEvent);
}
...
// Done.
transformedEvent.recycle();
return handled;
}
上面調(diào)用到View類中的 dispatchTouchEvent
方法子姜,事件就是從這個方法中消費的祟绊。
對于上面 super.dispatchTouchEvent
和 child.dispatchTouchEvent(transformedEvent);
這兩個要分清,第一個是 ViewGroup 父類中的方法哥捕,第二個是 子View(比如 TextView牧抽、Button等) 的方法,這兩個都是為了去調(diào)用 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 監(jiān)聽的合集扬舒,OnFocusChangeListener、OnScrollChangeListener凫佛、OnClickListener等等
//如果他不為null說明我們設置了監(jiān)聽
//判斷是否設置了onTouchListener 監(jiān)聽
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {//這里會執(zhí)行OnTouchListener 的 onTouch 方法
result = true;
}
//如果設置了onTouchListener的監(jiān)聽并且返回為true時就不會調(diào)用onTouchEvent方法
//如果上面沒有處理就會調(diào)用onTouchEvent
if (!result && onTouchEvent(event)) {
result = true;
}
}
...
return result;
}
上面這個方法可以看出onTouchListener
讲坎、onTouchEvent
的優(yōu)先級孕惜;onTouchListener
—>onTouchEvent
還有一點 就onTouchListener
、onTouchEvent
這兩個方法而言晨炕,如果沒有 setOnTouchListener
那么一定會執(zhí)行onTouchEvent
方法衫画,如果設置了setOnTouchListener
那就要看onTouch
的返回值了
那么我們的setOnClickListener
是什么時候調(diào)用的呢,接下來看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是否已經(jīng)執(zhí)行了長按事件瓮栗,true已經(jīng)執(zhí)行了,false未執(zhí)行
// 還未執(zhí)行長按事件時才能進行
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
//先移除還未被執(zhí)行的長按事件
removeLongPressCallback();
// 只有在我們處于按下狀態(tài)時才執(zhí)行點擊操作
if (!focusTaken) {
//使用Runnable 處理這個點擊事件削罩,而不是直接執(zhí)行點擊。目的就是在單擊操作開始之前可以更新View的狀態(tài)
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
//真正執(zhí)行Click的方法费奸,內(nèi)部調(diào)用了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);
...
//長按事件弥激,是一個延遲的消息,
//剛調(diào)用checkForLongClick方法時 mHasPerformedLongPress=false货邓,
//當長按事件的延遲消息被執(zhí)行后 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
監(jiān)聽,如果用戶手動設置了setOnclickListener()
换况, 就調(diào)用 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 各種監(jiān)聽的合集
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
//執(zhí)行用戶設置的 onClickListener 中的 onClick方法
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
notifyEnterOrExitForAutoFillIfNeeded(true);
return result;
}
到這整個流程就算是走完了职辨,onTouchListener
、onTouchEvent
戈二、onClick
的優(yōu)先級也比較清楚了
onTouchListener
—>onTouchEvent
—>onClick
下面貼出事件分發(fā)的注意事項: