個人部落格:http://zhangsunyucong.top
android事件的源頭在哪裡?
當使用者觸控螢幕或者按鍵等時,形成事件,事件經過linux底層Event節點捕獲之後,一直傳到android應用層。中間傳遞的過程不是本文的重點,我也不是很清楚(哈哈哈)。本文的重點是事件在應用層的分發機制。
事件在View樹中的分發過程
View樹:
在Android中,事件的分發過程就是MotionEvent在view樹分發的過程。預設是中從上而下,然後從下而上的傳遞的,直到有view、viewgroup或者Activity處理事件為止。
為什麼要先從上而下?是為了在預設情況下,螢幕上層疊的所有控制元件都有機會處理事件。這個階段我們稱為事件下發階段。
為什麼要從下而上?是為了在從上而下分發時,事件沒有控制元件處理時,再從下而上冒泡事件,是否有控制元件願意處理事件。如果中間沒有控制元件處理,事件就只能由Acitivity處理了。這個階段我們稱為事件的冒泡階段。
準備
事件序列:從使用者手指觸控螢幕開始,經過滑動到手指離開螢幕。這個操作產生了一個dowm事件,一系列move事件,最後一個up事件結束。我們把這一個操作產生的事件稱為一個事件序列。
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:
1、返回false,事件不分發,所有事件在Acitivity的分發函式中就中斷(真的不見了),連Acitivity的事件處理函式都到達不了。
2、返回true,所有事件在Acitivity的分發函式中就中斷,和false一樣
3、返回父函式方法,事件就傳給直接子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:
1、返回false,不分發,down事件給父ViewGroup處理,以後的事件全部直接通過父ViewGroup分發函式給父ViewGroup的事件處理函式處理。
2、返回true,則所有的事件都從頭來到這裡就中斷,不見了。
3、返回父函式方法,看下面攔截函式
重寫ViewGroup#onInterceptTouchEvent(詢問是否攔截):
1、返回true,就呼叫處理函式,在處理函式中是否消耗down事件
2、返回false,是否是最後一個view?否,down事件就分發給子View;是,就呼叫一次它的處理函式,進入冒泡階段(就是一寸事件處理函式呼叫)
3、返回父函式的方法,和返回false一樣
重寫ViewGroup的onTouchEvent,當down事件來到中onTouchEvent時,
1、返回true,就消耗down事件,後面全部事件從頭分發到處理函式(不用再詢問是否攔截)。後面的事件根據是否消耗而是否消失(不消耗就消失),消失的所有事件由Acitivity處理(注意消失的事件也是從頭傳遞到這裡再傳給Acitivity的)。
2、返回false,將down事件冒泡回去,看誰會處理。
3、返回父函式方法,是預設不消耗。
(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的回溯過程,自下而上。