Android事件分發機制流程詳解(二)
前言:上一篇我們已經從事件分發執行流程入手,一起來了解並分析了事件分發的經過,大家應該從分析中能對事件分發的有個總體的認識,並且我相信應該也能自己分析出事件會如何執行,其實就那麼點東西,弄明白了就不難了,但是今天我們還是要來看看activity,viewgroup,view的相關原始碼來學習一下他們的工作原理,那就開始吧!
首先來結合我們上一篇的工程情況來看看:
再次強調,1個點選事件發生後,事件先傳到Activity、再傳到ViewGroup、最終再傳到 View,那麼我們分別從Activity對點選事件的分發機制、ViewGroup對點選事件的分發機制、View對點選事件的分發機制來分析原始碼。
一、Activity對點選事件的分發機制
從我們上一天的分析可以看到,當一個新的點選事件發生時,首先是會進入到Activity的dispatchTouchEvent()方法內,那麼我們先來看看Activity的dispatchTouchEvent()的原始碼:
public boolean dispatchTouchEvent(MotionEvent ev) {
//由於每次點選事件第一個肯定是ACTION_DOWN,所以這裡肯定是true
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
//如果getWindow().superDispatchTouchEvent(ev)是true的話
//那麼dispatchTouchEvent()返回的就是true,事件被消費,傳遞結束
return true;
}
//否則就會執行onTouchEvent方法
return onTouchEvent(ev);
}
- activity的onUserInteraction方法:
/**
* Called whenever a key, touch, or trackball event is dispatched to the
* activity. Implement this method if you wish to know that the user has
* interacted with the device in some way while your activity is running.
* This callback and {@link #onUserLeaveHint} are intended to help
* activities manage status bar notifications intelligently; specifically,
* for helping activities determine the proper time to cancel a notfication.
*
* <p>All calls to your activity's {@link #onUserLeaveHint} callback will
* be accompanied by calls to {@link #onUserInteraction}. This
* ensures that your activity will be told of relevant user activity such
* as pulling down the notification pane and touching an item there.
*
* <p>Note that this callback will be invoked for the touch down action
* that begins a touch gesture, but may not be invoked for the touch-moved
* and touch-up actions that follow.
*
* @see #onUserLeaveHint()
*/
public void onUserInteraction() {
}
這裡返回的是一個空方法,當我們不知道這個方法是幹啥用的時候,官方的註釋就是最好的答案,也許你英語不好,但是我們有線上翻譯不是麼?這個方法主要是給使用者提供告知和手機的一些互動的情況,要是用得到的話就實現它然後重寫程式碼,這裡我們用不到,不做了解。
- activity的getWindow().superDispatchTouchEvent(ev)
通過滑鼠點進去後發現,這是Window的抽象類
/**
* Used by custom windows, such as Dialog, to pass the touch screen event
* further down the view hierarchy. Application developers should
* not need to implement or call this.
*
*/
public abstract boolean superDispatchTouchEvent(MotionEvent event);
我們只能看看Window的實現類了
可以看到Window唯一的實現類只有PhoneWindow,那麼我們進到PhoneWindow中查詢superDispatchTouchEvent方法,程式碼如下:
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
接著點進mDecor中去看看,會發現進入了DecorView中:
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
繼續往下找,發現進入了ViewGroup中:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
...具體的到ViewGroup中講解
}
現在再來審視一下這張圖
發現沒有,事件通過getWindow().superDispatchTouchEvent(ev)往下一直傳遞到ViewGroup中,如果ViewGroup的dispatchTouchEvent返回了false,那麼我們這裡就會執行onTouchEvent方法,交由我們自己實現的onTouchEvent方法處理,如果ViewGroup的dispatchTouchEvent返回了true,那麼我們這return true,事件就會往下執行,符合我們上一篇首圖的邏輯。那麼接下來我們順著往下走來看看onTouchEvent方法。
- activity的onTouchEvent()方法(系統預設的,如果未重寫執行此流程)
來看看原始碼:
/**
* Called when a touch screen event was not handled by any of the views
* under it. This is most useful to process touch events that happen
* outside of your window bounds, where there is no view to receive it.
*
* @param event The touch screen event being processed.
*
* @return Return true if you have consumed the event, false if you haven't.
* The default implementation always returns false.
*/
public boolean onTouchEvent(MotionEvent event) {
if (mWindow.shouldCloseOnTouch(this, event)) {
finish();
return true;
}
return false;
}
這段程式碼表示:onTouchEvent方法在觸控式螢幕幕事件沒有被任何檢視處理時呼叫,即沒有任何檢視(ViewGroup、View)消費事件時,Activity的onTouchEvent方法會執行。如果你已經消費了這個事件,返回true,如果你沒有,返回false,預設的實現總是返回false。即只有在點選事件在Window邊界外才會返回true,一般情況都返回false。
下面我們來看看mWindow.shouldCloseOnTouch(this, event)方法。
- Window的shouldCloseOnTouch()方法
public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
if (mCloseOnTouchOutside && event.getAction() == MotionEvent.ACTION_DOWN
&& isOutOfBounds(context, event) && peekDecorView() != null) {
return true;
}
return false;
}
這個方法主要用來判斷是否是邊界外點選事件+是否是DOWN事件+event的座標是否在邊界內。如果是在邊界外並且是DOWN事件,那麼就意味著返回true,那麼就是消費事件,如果不是則返回false,不消費事件,到這裡Activity的事件分發已經執行完畢了。
因為我們在MainActivity中已經重寫了onTouchEvent方法,所以上述過程當getWindow().superDispatchTouchEvent(ev)返回false的時候,事件就會交由我們MainActivity中的onTouchEvent方法處理,所以看看我們上一篇的測試3
並且執行結果也和我們上述分析一致:
二、ViewGroup對點選事件的分發機制
由於我的api較新,所以這裡的程式碼是Android7.0的程式碼,並且由於ViewGroup中的dispatchTouchEvent方法較長,所以咱們這裡只擇出部分關鍵程式碼來分析如何運作的。
// Check for interception.
//定義一個變數是否攔截
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//如果是down事件並且傳入物件不為空則進入
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
//如果不禁止攔截的話執行,是否攔截由自己的onInterceptTouchEvent方法決定
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;
}
- ViewGroup的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;
}
實現此方法攔截所有觸控式螢幕運動事件,這允許我們觀看事件,因為它們被分派給它的孩子,並在任何時候對當前的手勢擁有所有權。使用這個函式需要一些注意,因為它是相當複雜的,您將在這裡收到down事件,down事件將由這個viewgroup的子節點處理,或者由您自己的onTouchEvent()方法處理。這意味著您應該實現onTouchEvent()以返回true。同樣,通過從onTouchEvent()返回true,您將不會在onInterceptTouchEvent()中接收任何後續事件,並且所有的觸控處理都必須在onTouchEvent()中發生,就像normal一樣。只要您從這個函式返回false,每個後續事件(包括最終的up)將首先在這裡傳遞,然後傳遞到目標的onTouchEvent()。
當前的目標將接收ACTION_CANCEL事件,並且不會在這裡傳遞更多的訊息。
盜取網上一篇部落格的其中的一張圖結合分析的結論來看看
到這裡相信大家對ViewGroup如何分發事件有了自己的理解了,下面接著來看看View是處理事件的。
三、View對點選事件的分發機制
鑑於Android7.0的程式碼關於View這塊比較複雜,咱們還是看看Android2.0的程式碼吧,雖然程式碼簡單,但是工作流程肯定是一樣的,只不過後期做了優化,咱們瞭解原理就好了。
public boolean dispatchTouchEvent(MotionEvent event) {
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener.onTouch(this, event)) {
return true;
}
return onTouchEvent(event);
}
- mOnTouchListener
關於onTouchListener的賦值在這個方法裡:
public void setOnTouchListener(OnTouchListener l) {
mOnTouchListener = l;
}
可以看到,只要我們給view設定了setOnTouchListener,那麼mOnTouchListener一定是有值的。
- (mViewFlags & ENABLED_MASK) == ENABLED
這個條件是判斷當前點選的控制元件是否enable,因為我們既然要給這個view設定點選事件的話,那麼我們肯定會給它setOnClickListener,所以這個條件應該是成立的。
- mOnTouchListener.onTouch(this, event)
這個事件需要我們註冊了onTouchListener後進行回撥返回結果,舉個例子,在咱們上一篇中咱們也進行了註冊。
view.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
System.out.println("執行了onTouch(), 動作是:" + event.getAction());
return false;
}
});
我們已經分析過:
return false;那麼上述的if條件不成立,那麼就會執行onTouchEvent方法,和我們上一篇驗證的結果一樣。
return true;上述條件成立,事件傳遞結束,直接被消費掉,和我們驗證的結果也一樣。
接下來我們來看看onTouchEvent方法。
- View.onTouchEvent()方法
public boolean onTouchEvent(MotionEvent event) {
final int viewFlags = mViewFlags;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
}
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
//如果view是可點選的,包括長按,那麼就進入switch條件中
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
if ((mPrivateFlags & PRESSED) != 0) {
// 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 (!mHasPerformedLongPress) {
// This is a tap, so remove the longpress check
if (mPendingCheckForLongPress != null) {
removeCallbacks(mPendingCheckForLongPress);
}
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
//執行這個方法,見詳解
performClick();
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
mUnsetPressedState.run();
}
}
break;
case MotionEvent.ACTION_DOWN:
mPrivateFlags |= PRESSED;
refreshDrawableState();
if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {
postCheckForLongClick();
}
break;
case MotionEvent.ACTION_CANCEL:
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
break;
case MotionEvent.ACTION_MOVE:
final int x = (int) event.getX();
final int y = (int) event.getY();
// Be lenient about moving outside of buttons
int slop = ViewConfiguration.get(mContext).getScaledTouchSlop();
if ((x < 0 - slop) || (x >= getWidth() + slop) ||
(y < 0 - slop) || (y >= getHeight() + slop)) {
// Outside button
if ((mPrivateFlags & PRESSED) != 0) {
// Remove any future long press checks
if (mPendingCheckForLongPress != null) {
removeCallbacks(mPendingCheckForLongPress);
}
// Need to switch from pressed to not pressed
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
}
} else {
// Inside button
if ((mPrivateFlags & PRESSED) == 0) {
// Need to switch from not pressed to pressed
mPrivateFlags |= PRESSED;
refreshDrawableState();
}
}
break;
}
//如果該控制元件可點選的話,那麼一定是return true;
return true;
}
//如果該控制元件不可點選,一定return false;
return false;
}
- 詳解performClick()
public boolean performClick() {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
if (mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
mOnClickListener.onClick(this);
return true;
}
return false;
}
如果我們給view.setOnClickListener(new View.OnClickListener() {});那麼這裡mOnClickListener一定不為null,所以return true;如果沒設定click監聽,那麼就return false;然後事件根據返回結果進行傳遞或者消費。
總結:
通過上一篇各種情況下執行流程和本篇原始碼分析,我們應該能夠清楚的瞭解了Android事件分發到底是個什麼東西了,最好的學習方法還是寫個demo,然後有不明白的地方點進去看看原始碼,不要求全部看懂,找找我們關心的重點,能大概分析清楚是怎麼工作的即可,祝大家學習愉快~