面試Android事件分發機制原理及源碼分析(Andro5.0)
首先附上我的幾篇其它文章鏈接感興趣的可以看看,如果文章有異議的地方歡迎指出,共同進步,順便點贊謝謝!!!
Android framework 源碼分析之Activity啓動流程(android 8.0)
Android studio編寫第一個NDK工程的過程詳解(附Demo下載地址)
面試必備1:HashMap(JDK1.8)原理以及源碼分析
面試必備2:JDK1.8LinkedHashMap實現原理及源碼分析
View事件的滑動衝突以及解決方案
Handler機制一篇文章深入分析Handler、Message、MessageQueue、Looper流程和源碼
Android三級緩存原理及用LruCache、DiskLruCache實現一個三級緩存的ImageLoader
概述
Android中的事件分發機制指的是事件從Activity–>ViewGroup—>View的傳遞,然後在由View–>ViewGroup–>Activity依次響應的過程,主要與 Touch 事件相關的三個重要方法息息相關:
- dispatchTouchEvent(MotionEvent ev)負責事件的分發
- onInterceptTouchEvent(MotionEvent ev)用來判斷是否攔截某個事件
- onTouchEvent(MotionEvent ev)用來判斷是否響應響應該事件
本文的源碼分析是基於Andro5.0進行的分析,它比之前的邏輯更加複雜,但是原理是一致的,5.0之前的源碼分析大家可以參考這篇文章:
https://blog.csdn.net/carson_ho/article/details/54136311
Touch事件的三個方法
1:dispatchTouchEvent(ev)事件分發
Touch 事件發生時 Activity 的 dispatchTouchEvent(MotionEvent ev) 方法會以隧道方式(從根元素依次往下傳遞直到最內層子元素或在中間某一元素中由於某一條件停止傳遞)將事件傳遞給最外層 View 的 dispatchTouchEvent(MotionEvent ev) 方法,並由該 View 的 dispatchTouchEvent(MotionEvent ev) 方法對事件進行分發。dispatchTouchEvent 的事件分發邏輯
- 如果 return true,事件會分發給當前 View 並由 onTouchEvent 方法進行消費,同時事件會停止向下傳遞;
- 如果 return false,事件分發分爲兩種情況:如果當前 View 獲取的事件直接來自 Activity,則會將事件返回給 Activity 的 onTouchEvent 進行消費;如果當前 View 獲取的事件來自外層父控件,則會將事件返回給父 View 的 onTouchEvent 進行消費。但是這兩種情況都遵循一個原則就是將事件返回外層控件的onTouchEvent進行消費。
- 如果返回系統默認的 super.dispatchTouchEvent(ev),事件會自動的分發給當前 View 的 onInterceptTouchEvent 方法。
2:onInterceptTouchEvent(MotionEvent ev)事件攔截
onInterceptTouchEvent 的事件攔截邏輯如下:
- 如果 onInterceptTouchEvent 返回 true,則表示將事件進行攔截,並將攔截到的事件交由當前 View 的 onTouchEvent 進行處理;
- 如果 onInterceptTouchEvent 返回 false,則表示將事件放行,當前 View 上的事件會被傳遞到子 View 上,再由子 View 的 dispatchTouchEvent 來開始這個事件的分發;
- 如果 onInterceptTouchEvent 返回 super.onInterceptTouchEvent(ev),邏輯和返回 false 時相同。
- 需要注意的是此方法存在於ViewGroup中,在View中沒有此方法。
3:onTouchEvent(MotionEvent ev)事件響應
onTouchEvent 的事件響應邏輯如下:
- 如 果事件傳遞到當前 View 的 onTouchEvent 方法,而該方法返回了 false,那麼這個事件會從當前 View 向上傳遞,並且都是由上層 View 的 onTouchEvent 來接收,如果傳遞到上面的 onTouchEvent 也返回 false,這個事件就會“消失”,而且接收不到下一次事件。
- 如果返回了 true 則會接收並消費該事件。
- 如果返回 super.onTouchEvent(ev) 默認處理事件的邏輯和返回 false 時相同。
事件的分發機制主要和這三個方法有關,具體的驗證,自己可一區寫一個demo,自定義VIewGroup和View複寫Activity和自定義ViewGroup和View的三個方法,修改其返回值去測試,在這裏我就不貼上自己的Demo了。注意事件機制內部的一個優化,第一次傳遞到這個view時沒有進行處理,則講接收不到下個事件。
Activity的事件分發
1:Activity的dispatchTouchEvent方法源碼分析
當一個事件發生時首先傳遞到Activity的dispatchTouchEvent方法,其源碼如下:
/**
* 源碼分析:Activity的dispatchTouchEvent()
*/
public boolean dispatchTouchEvent(MotionEvent ev) {
// 一般事件列開始都是DOWN事件 = 按下事件,故此處基本是true
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
//onUserInteraction() 在Activity中爲空方法。留給用戶來重寫的:一般用來實現屏保功能
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {//getWindow().superDispatchTouchEvent(ev)
return true;
//1:Activity中對事件的分發是通過getWindow().superDispatchTouchEvent(ev)將事件交給PhoneWindow
// 2:若getWindow().superDispatchTouchEvent(ev)的返回true
//:3:則Activity.dispatchTouchEvent()就返回true,則方法結束。即 :該點擊事件停止往下傳遞 & 事件傳遞過程結束
}
//:4:superDispatchTouchEvent返回false時:繼續往下調用Activity.onTouchEvent
return onTouchEvent(ev);
}
PhoneWindow的dispatchTouchEvent方法源碼:
@Override
/**
* a. DecorView類是PhoneWindow類的一個內部類
* b. DecorView繼承自FrameLayout,是所有界面的父類
* c. FrameLayout是ViewGroup的子類,故DecorView的間接父類 = ViewGroup
*/
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
接下來看一下DecorView.superDispatchTouchEvent(event)
/**
* DecorView.superDispatchTouchEvent(event)
* 定義:屬於頂層View(DecorView)
* 說明:
* a. DecorView類是PhoneWindow類的一個內部類
* b. DecorView繼承自FrameLayout,是所有界面的父類
* c. FrameLayout是ViewGroup的子類,故DecorView的間接父類 = ViewGroup
*/
public boolean superDispatchTouchEvent(MotionEvent event) {
// 調用父類的方法 : ViewGroup的dispatchTouchEvent() 即 將事件傳遞到ViewGroup去處理
return super.dispatchTouchEvent(event);
}
2:Activity的onTouchEvent方法源碼:
/**
*當一個點擊事件未被Activity內任何一個View接收 / 處理時,就會回到Activity 的onTouchEvent
*
public boolean onTouchEvent(MotionEvent event) {
//是否要關閉事件: 只有在點擊事件在Window邊界外才會返回true,一般情況都返回false
if (mWindow.shouldCloseOnTouch(this, event)) {
//消費事件
finish();
return true;
}
return false;
}
/** @hide */
public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
// // 主要是對於處理邊界外點擊事件的判斷:是否是DOWN事件,event的座標是否在邊界內等
if (mCloseOnTouchOutside && event.getAction() == MotionEvent.ACTION_DOWN
&& isOutOfBounds(context, event) && peekDecorView() != null) {
return true;//消費事件
}
return false;
}
經以上源碼分析:
- Activity事件分發方法getWindow().superDispatchTouchEvent(ev)返回值表示事件在Activity的view中事件是否分發成功,如果爲true則事件分發成功(既被Activity中的view所消耗),如果爲false,就調用Activity自己的onTouchEvent()方法來處理事件。
- 事件由Activity傳遞到Activity經歷了:Activity–>PhoneWindow----->DecorView---->ViewGroup的過程
ViewGroup的事件分發機制
1:ViewGroup的dispatchTouchEvent(ev)方法源碼分析
@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;
// onFilterTouchEventForSecurity()用安全機制來過濾觸摸事件,true爲不過濾分發下去,false則銷燬掉該事件。
// 方法具體實現是去判斷是否被其它窗口遮擋住了,如果遮擋住就要過濾掉該事件。onFilterTouchEventForSecurity的源碼不是重點就不在這裏分析了
if (onFilterTouchEventForSecurity(ev)) {
// // 沒有被其它窗口遮住
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// 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.
//重點分析1:
//上面這一塊註釋說的很清楚了,就是在`Down事件的時候把所有的狀態都重置,作爲一個新事件的開始。因爲事件是從DOWN開始UP結束
// 如果是Down,那麼mFirstTouchTarget到這裏肯定是`null`.因爲是新一系列手勢的開始。
// mFirstTouchTarget是處理第一個事件的目標。
cancelAndClearTouchTargets(ev);/把所有的狀態都重置
resetTouchState();
}
// // 檢查是否攔截該事件(如果onInterceptTouchEvent()返回true就攔截該事件)
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
// disallowIntercept:表示是否禁用事件攔截功能, 默認是`false`,可以通過`requestDisallowInterceptTouchEvent(true)`方法來設置,通知父`View`不要攔截該`View`上的事件。
if (!disallowIntercept) {
//每次都要通過onInterceptTouchEvent 判斷該`ViewGroup`是否要攔截該事件。`onInterceptTouchEvent()`方法默認返回`false`即不攔截。
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
// 子`View`通知父`View`不要攔截。這樣就不會走到上面`onInterceptTouchEvent()`方法中了,
// 所以父`View`就不會攔截該事件。
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
// 就是沒有目標來處理該事件,而且也不是一個新的事件`Down`事件(新事件的開始), 攔截該事件。即點擊的空白處沒有子控件攔截改事件
intercepted = true;
}
// If intercepted, start normal event dispatch. Also if there is already
// a view that is handling the gesture, do normal event dispatch.
if (intercepted || mFirstTouchTarget != null) {
ev.setTargetAccessibilityFocus(false);
}
//.檢查當前是否是Cancel事件或者是有Cancel標記。
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
// 這行代碼爲是否需要將當前的觸摸事件分發給多個子`View`,默認爲`true`,分發給多個`View`(比如幾個子`View`位置重疊)。默認是true
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
// 用於記錄當前要分發給的哪個View
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
//沒取消並且沒有攔截
if (!canceled && !intercepted) {
// 下面這部分代碼的意思其實就是找到該事件位置下的`View`(可見或者是在動畫中的View), 並且與`pointID`關聯
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
? findChildWithAccessibilityFocus() : null;
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int actionIndex = ev.getActionIndex(); // always 0 for down
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
// Clean up earlier touch targets for this pointer id in case they
// have become out of sync.
removePointersFromTouchTargets(idBitsToAssign);
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 ArrayList<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
// for循環遍歷找子View進行分發了。
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);
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}
// canViewReceivePointerEvents()`方法會去判斷這個View是否可見或者在播放動畫,只有這兩種情況下可以接受事件的分發
// isTransformedTouchPointInView判斷這個事件的座標值是否在該`View`內。
//此處就不在此深入分析這兩個方法的源碼了:
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
//可見&&事件在該View內 往下執行
// 找到該`View`對應的在`mFristTouchTarget`中的存儲的目標, 判斷這個`View`可能已經不是之前`mFristTouchTarget`中的`View`了。
// 如果找不到就返回null, 這種情況是用於多點觸摸, 比如在同一個`View`上按下了多跟手指。
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
//該View已經接受了這個事件了 , 找到該View了,不用再循環找了
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
// 如果上面沒有break,只有newTouchTarget爲null,說明上面我們找到的Child View和之前的肯定不是同一個了,
// 是新增的, 比如多點觸摸的時候,一個手指按在了這個`View`上,另一個手指按在了另一個`View`上。
// 這時候我們就看child是否分發該事件。dispatchTransformedTouchEvent如果child爲null,就直接該ViewGroup處理事件
// 如果child不爲null,就調用child.dispatchTouchEvent分發給子View
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// 如果這個Child View能分發,那我們就要把之前存儲的值改變成現在的Child View。
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();
// 賦值成現在的Child View對應的值,並且同步更新`mFirstTouchTarget`也改成該值 (即mFristTouchTarget`與`newTouchTarget`是一樣的)
newTouchTarget = addTouchTarget(child, idBitsToAssign);
//分發給子View了跳出循環
alreadyDispatchedToNewTouchTarget = true;
break;
}
// The accessibility focus didn't handle the event, so clear
// the flag and do a normal dispatch to all children.
ev.setTargetAccessibilityFocus(false);
}
if (preorderedList != null) preorderedList.clear();
}
// newTouchTarget == null就是沒有找到新的可以分發該事件的子View,那我們用上一次的分發對象了。
if (newTouchTarget == null && mFirstTouchTarget != null) {
// Did not find a child to receive the event.
// Assign the pointer to the least recently added target.
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
// DOWN事件在上面代碼中會去找touch target
if (mFirstTouchTarget == null) {
// dispatchTransformedTouchEvent方法中如果child爲null,那麼就調用super.dispatchTouchEvent(transformedEvent);否則調用child.dispatchTouchEvent(transformedEvent)
// No touch targets so treat this as an ordinary view.沒有子View接收事件 自己處理
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;
// 找到了新的子View,並且這個是新加的對象,上面已經處理過了
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
// 否則都調用dispatchTransformedTouchEvent處理,傳遞給child
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
//分發給子View
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
// 如果是onInterceptTouchEvent返回true就會遍歷mFirstTouchTarget全部給銷燬,
//這就是爲什麼onInterceptTouchEvent返回true,之後所有的事件都不會再繼續分發的了的原因
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
// Update list of touch targets for pointer up or cancel, if needed.
if (canceled
|| actionMasked == MotionEvent.ACTION_UP
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
resetTouchState();
} else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
// 擡起UP事件發生的時候,清除它相關的數據。
final int actionIndex = ev.getActionIndex();
final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
removePointersFromTouchTargets(idBitsToRemove);
}
}
if (!handled && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
}
return handled;
}
接下來看一看dispatchTransformedTouchEvent()方法的源碼:
/**
* 此方法主要是用來處理事件的,此處指展示核心代碼,以便理解
* @param event
* @param cancel
* @param child 不爲null時則將事件交給自 View的dispatchTouchEvent去處理
* @param desiredPointerIdBits
* @return
*/
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
final int oldAction = event.getAction();
//被攔截之後,之前處理過該事件的View會收到CANCEL的原因是在此處進行了設置
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
//重點
//自己處理,交給自己的父類View的dispatchTouchEvent處理,View的super.dispatchTouchEvent方法將在下面進行分析
handled = super.dispatchTouchEvent(event);
} else {
//子View去處理,如果子View仍然是ViewGroup還是執行同樣的處理,
//如果子View是普通View,普通View的dispatchTouchEveent()會調用onTouchEvent()
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
...................//省略部分代碼
return handled;
}
2: ViewGroup的onInterceptTouchEvent (ev)方法源碼分析
public boolean onInterceptTouchEvent(MotionEvent ev) {
//此方法比較簡單判斷是否攔截,默認情況下返回false
if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
&& ev.getAction() == MotionEvent.ACTION_DOWN
&& ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
&& isOnScrollbarThumb(ev.getX(), ev.getY())) {
return true;
}
return false;
}
注意:ViewGroup的 onTouchEvent方法調用的是父類View的 onTouchEvent方法,所以在這裏不進行分析,將在下面View的事件分發機制中進行分析。
View的事件分發機制
1: View的dispatchTouchEvent方法源碼:
public boolean dispatchTouchEvent(MotionEvent event) {
// If the event should be handled by accessibility focus first.
if (event.isTargetAccessibilityFocus()) {
// We don't have focus or no virtual descendant has it, do not handle the event.
if (!isAccessibilityFocusedViewOrHost()) {
return false;
}
// We have focus and got the event, then use normal event dispatch.
event.setTargetAccessibilityFocus(false);
}
boolean result = false;
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Defensive cleanup for new gesture
stopNestedScroll();
}
// 判斷該View是否被其它View遮蓋住。
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
/**
*重點:當這三個條件都成立是View的dispatchTouchEvent方法才返回true ,即事件結束,否則會掉View的onTouchEvent方法
* 條件一mListenerInfo!=null: 看源碼可知無論設置什麼監聽都會出初始化mListenerInfo,顧mListenerInfo!=null成立
* 條件二 (mViewFlags & ENABLED_MASK) == ENABLED:即View的enable屬性爲true時
* 條件三 mListenerInfo.mOnTouchListener.onTouch(this, event)返回true需要我們手動複寫onTouch方法
*/
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {//先調用onTouch方法
result = true;
}
if (!result && onTouchEvent(event)) {//後調用onTouchEvent方法,onTouchEvent的源碼將在下面進行詳細分析,
//在此需要注意:onClick是在onTouchEvent的DOWN事件中觸發的,所以比onTouch執行的晚
result = true;
}
}
if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
// Clean up after nested scrolls if this is the end of a gesture;
// also cancel it if we tried an ACTION_DOWN but we didn't want the rest
// of the gesture.
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}
return result;
}
2: View的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;
// 對disable按鈕的處理,一個disable但是clickable的view仍然會消耗事件,只是不響應而已。
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;
}
*
*
* 關於TouchDelegate,文檔中是這樣說的The delegate to handle touch events that are physically in this view
* but should be handled by another view. 就是說如果兩個View, View2在View1中,View1比較大,如果我們想點擊
* View1的時候,讓View2去響應點擊事件,這時候就需要使用TouchDelegate來設置。
* 簡單的理解就是如果這個View有自己的事件委託處理人,就交給委託人處理。
*/
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
if ((viewFlags & TOOLTIP) == TOOLTIP) {
handleTooltipUp();
}
if (!clickable) {
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
break;
}
// 最好先看DOWN後再看MOVE最後看UP。
// PFLAG_PREPRESSED 表示在一個可滾動的容器中,要稍後才能確定是按下還是滾動.
// PFLAG_PRESSED 表示不是在一個可滾動的容器中,已經可以確定按下這一操作
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
// take focus if we don't have it already and we should in
// touch mode.
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
//如果現在還沒獲取到焦點,就再獲取一次焦點
focusTaken = requestFocus();
}
// 在前面DOWN事件的時候會延遲顯示View的pressed狀態,用戶可能在我們還沒有顯示按下狀態效果時就不按了.我們還是得在進行實際的點擊操作時,讓用戶看到效果。
if (prepressed) {
// The button is being released before we actually
// showed it as pressed. Make it show the pressed
// state now (before scheduling the click) to ensure
// the user sees it.
setPressed(true, x, y);
}
// /判斷不是長按
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();
}
// PerformClick就是個Runnable,裏面執行performClick()方法,在onClick中調用OnCliclListener的onclick方法,其源碼就不在這裏分析了
if (!post(mPerformClick)) {
performClick();
}
if (!post(mPerformClick)) {
performClick();
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
// 取消按下狀態,UnsetPressedState也是個Runnable,裏面執行setPressed(false)
if (prepressed) {
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
mUnsetPressedState.run();
}
removeTapCallback();
}
mIgnoreNextUpEvent = false;
break;
case MotionEvent.ACTION_DOWN:
if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
}
mHasPerformedLongPress = false;
if (!clickable) {
checkForLongClick(0, x, y);
break;
}
//處理鼠標右鍵菜單,有些View顯示右鍵菜單就直接彈菜單.一般設備用不到鼠標,所以返回false。
if (performButtonActionOnTouchDown(event)) {
break;
}
// Walk up the hierarchy to determine if we're inside a scrolling container.
boolean isInScrollingContainer = isInScrollingContainer();
// For views inside a scrolling container, delay the pressed feedback for
// a short period in case this is a scroll.
//遍歷下View層級,判斷這個View是不是一個能scroll,如果可以滾動則需要判斷是滾動還是點擊監聽
if (isInScrollingContainer) {
// 因爲用戶可能是點擊或者是滾動,所以我們不能立馬判斷,先給用戶設置一個要點擊的事件。
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
//發送一個延時的操作,用於判斷用戶到底是點擊還是滾動。其實就是在tapTimeout中如果用戶沒有滾動,那就是點擊了。
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// Not inside a scrolling container, so show the feedback right away
//設置成點擊狀態
setPressed(true, x, y);
// 檢查是否是長按,就是過一段時間後如果還在按住,那就是長按了。
checkForLongClick(0, x, y);
}
break;
case MotionEvent.ACTION_CANCEL:
// 取消按下狀態,移除個監聽,回覆初始值
if (clickable) {
setPressed(false);
}
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
break;
case MotionEvent.ACTION_MOVE:
if (clickable) {
drawableHotspotChanged(x, y);
}
// Be lenient about moving outside of buttons判斷是否移動到區域外面
if (!pointInView(x, y, mTouchSlop)) {
// Outside button
///移動到區域外面去了,就要取消點擊。
// Remove any future long press/tap checks
removeTapCallback();
removeLongPressCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
}
break;
}
return true;
}
return false;
}
事件分發機制一個應用場景就是事件的滑動衝突,大家可以參考一下我的另一篇博客View事件的滑動衝突以及解決方案
到此爲止事件分發機制的原理和源碼基本分析完,裏面不對的地方請大家留言指正,共同進步!!!!!!