我的博客:http://zhangsunyucong.toplinux
當用戶觸摸屏幕或者按鍵等時,造成事件,事件通過linux底層Event節點捕獲以後,一直傳到android應用層。中間傳遞的過程不是本文的重點,我也不是很清楚(哈哈哈)。本文的重點是事件在應用層的分發機制。android
View樹: git
在Android中,事件的分發過程就是MotionEvent在view樹分發的過程。默認是中從上而下,而後從下而上的傳遞的,直到有view、viewgroup或者Activity處理事件爲止。github
爲何要先從上而下?是爲了在默認狀況下,屏幕上層疊的全部控件都有機會處理事件。這個階段咱們稱爲事件下發階段。bash
爲何要從下而上?是爲了在從上而下分發時,事件沒有控件處理時,再從下而上冒泡事件,是否有控件願意處理事件。若是中間沒有控件處理,事件就只能由Acitivity處理了。這個階段咱們稱爲事件的冒泡階段。app
事件序列:從用戶手指觸摸屏幕開始,通過滑動到手指離開屏幕。這個操做產生了一個dowm事件,一系列move事件,最後一個up事件結束。咱們把這一個操做產生的事件稱爲一個事件序列。ide
Acitivity中和事件傳遞有關的函數 事件分發:dispatchTouchEvent 事件處理:onTouchEvent函數
ViewGrop中和事件傳遞有關的函數 事件分發:dispatchTouchEvent 事件攔截:onInterceptTouchEvent 事件處理:onTouchEvent源碼分析
View中和事件傳遞有關的函數 事件分發:dispatchTouchEvent 事件處理:onTouchEvent佈局
從上面能夠看出,ViewGrop中多了事件攔截onInterceptTouchEvent函數,是爲了詢問本身是否攔截事件(在事件分發中詢問),若是沒有攔截就傳遞事件給直接子view,若是攔截就將事件交給本身的事件處理函數處理。View中沒有事件攔截函數,由於view是在view樹中的葉節點,已經沒有子view。
下面是先進行源碼分析,而後再驗證得出一些結論。代碼遲點上傳github。 用圖表示佈局的層級關係:
這裏分析事件的分發過程,是從down事件的分發開始,以及分析它在兩個階段的傳遞過程:下發階段和冒泡階段。
(1)在Acitvity中的源碼分析:
Activity#dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
複製代碼
在第4行,Acivity將事件傳遞給了Window,Window是一個抽象類。在手機系統中它的實現是PhoneWindow.下面進入PhoneWindow中。
PhoneWindow#superDispatchTouchEvent
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
複製代碼
從上面能夠看出,事件已經從Acitivity到PhoneWindow,再傳到了DecorView。DecorView是一個繼承FrameLayout的ViewGroup,從而事件進入了View樹的傳遞。
重寫在Acitvity中的事件傳遞方法
重寫Activity#dispatchTouchEvent: 一、返回false,事件不分發,全部事件在Acitivity的分發函數中就中斷(真的不見了),連Acitivity的事件處理函數都到達不了。 二、返回true,全部事件在Acitivity的分發函數中就中斷,和false同樣 三、返回父函數方法,事件就傳給直接子view分發
(2)在ViewGruop中的源碼分析: ViewGruop#dispatchTouchEvent
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.
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
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 {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
複製代碼
在5-11行,是每一個新的事件系列開始前,會重置事件相關的狀態。這裏咱們關注兩個地方。第一個是第17行的disallowIntercept標誌,第二個是第19行調用了事件攔截函數,詢問是否攔截事件。
ViewGruop#onInterceptTouchEvent
public boolean onInterceptTouchEvent(MotionEvent ev) {
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;
}
複製代碼
onInterceptTouchEvent的代碼很簡單。
重寫在ViewGroup中的事件傳遞方法 重寫ViewGroup#dispatchTouchEvent: 一、返回false,不分發,down事件給父ViewGroup處理,之後的事件所有直接經過父ViewGroup分發函數給父ViewGroup的事件處理函數處理。 二、返回true,則全部的事件都從頭來到這裏就中斷,不見了。 三、返回父函數方法,看下面攔截函數
重寫ViewGroup#onInterceptTouchEvent(詢問是否攔截): 一、返回true,就調用處理函數,在處理函數中是否消耗down事件 二、返回false,是不是最後一個view?否,down事件就分發給子View;是,就調用一次它的處理函數,進入冒泡階段(就是一寸事件處理函數調用) 三、返回父函數的方法,和返回false同樣
重寫ViewGroup的onTouchEvent,當down事件來到中onTouchEvent時, 一、返回true,就消耗down事件,後面所有事件從頭分發處處理函數(不用再詢問是否攔截)。後面的事件根據是否消耗而是否消失(不消耗就消失),消失的全部事件由Acitivity處理(注意消失的事件也是從頭傳遞到這裏再傳給Acitivity的)。 二、返回false,將down事件冒泡回去,看誰會處理。 三、返回父函數方法,是默認不消耗。
(3)在View中的源碼分析: View#dispatchTouchEvent
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
}
複製代碼
這裏關注的地方是,第9行和第13行。第9行是當前view若是設置了onTouch事件,而且它返回了true,那它就直接將result設置爲true,事件就消耗了,不會再繼續傳遞下去,只到達onTouch。第13行,是事件處理函數。能夠看出onTouch是優先於onTouchEvent的。
View#onTouchEvent
....
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:
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
if ((viewFlags & TOOLTIP) == TOOLTIP) {
handleTooltipUp();
}
if (!clickable) {
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
break;
}
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(); } 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(); } if (!post(mPerformClick)) { performClick(); } } } ... } ... } return true; } 複製代碼
view根據是否能夠點擊等等一系列判斷什麼的。這裏關注up事件中的第42-53行,有performClick。
View#performClick
public boolean performClick() {
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設置了mOnClickListener,即點擊事件,會調用view的點擊事件。若是在父view中攔截了up事件,使up事件到達不了這裏,會使view的點擊事件失效。
能夠知道,onTouch是優先於onTouchEvent,onTouchEvent優先於onclick。
當down事件到達了最後一個子view,若是仍然沒有view願意處理它,就調用一次最後一個子view的事件處理函數,是否處理dowm事件,若是不處理,就一直冒泡回去,直到有view的onTouchEvent處理爲止。若是都不處理,就只有Acitivity本身處理了。整個事件冒泡階段就是一串onTouchEvent的回溯過程,自下而上。