1. 程式人生 > >Android中ViewGroup、View事件分發機制原始碼分析總結(雷驚風)

Android中ViewGroup、View事件分發機制原始碼分析總結(雷驚風)

1.概述

        很長時間沒有回想Android中的事件分發機制了,開啟目前的原始碼發現與兩三年前的實現程式碼已經不一樣了,5.0以後發生了變化,更加複雜了,但是萬變不離其宗,實現原理還是一樣的,在這裡將5.0以前的時間分發機制做一下原始碼剖析及總結。會涉及到幾個方法,dispatchTouchEvent()表示事件開始分發方法,在ViewGroup與View中都有,onInterCeptTouchEvent()表示是否攔截當前事件,比如ACTION_DOWN、ACTION_MOVE、ACTION_UP、ACTION_CANCLE,只在ViewGroup中存在,因為一個View的子類已經是最內層View,而ViewGroup的子類還會包含子View,所以它需要可以設定是否攔截當前事件傳遞到子View中,onToutchEvent()表示消費當前事件,OnToutchListener,OnClickListener,OnLongClickListener。

2.原始碼剖析

        我會將詳細的程式碼註釋加在原始碼中,首先說一下在Activity與Window中的分發過程。當我們點選螢幕上的一個View時,事件的分發就開始了,首先會呼叫當前Activity的dispatchTouchEvent(MotionEvent ev)方法,我們開啟Activity類看一下它的實現:

public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }
只挑主要的說,可以看到呼叫了getWindow().superDispatchTouchEvent()方法,如果這個方法返回true,則表示事件被消費,直接返回true,如果返回false,會執行自己的onTouchEvent()表明如果View樹中都沒有消費事件,當前Activity消費事件。我們知道在Phone上邊PhoneWindow是Window的唯一子類,那我們就去PhoneWindow中找找相關方法:
//phoneWindow
public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }
在PhoneWindow中可以看到呼叫了mDocor的super***方法,大家應該都知道mDecor為window中的最頂級View類,他是PhoneWindow的內部類,繼承了FrameLayout。我們進入mDecor的內部看看它的實現:
//DecorView
public boolean superDispatchTouchEvent(MotionEvent event) {
            return super.dispatchTouchEvent(event);
        }
在DecorView中又呼叫了父類方法,我們最終在GroupView中找到了相關實現,這樣我們就完成了一個點選事件從Activity到ViewGroup的過程。無論是ACTION_DOWN、ACTION_MOVE、ACTION_UP、ACTION_CANCLE任何一個都是如此的一直傳遞流程。

總結:點選--->Activity.dispatchTouchEvent(event)--->PhoneWindow.superDispatchTouchEvent(event)--->DecorView.superDispatchTouchEvent(event)--->ViewGroup.dispatchTouchevent(event);

說完了一個事件從Activity到ViewGroup的傳遞流程,下邊說一下一個事件在ViewGroup中是如何分發的,看一下原始碼:

//ViewGroup中;
public boolean dispatchTouchEvent(MotionEvent ev) {  
    final int action = ev.getAction();  
    final float xf = ev.getX();  
    final float yf = ev.getY();  
    final float scrolledXFloat = xf + mScrollX;  
    final float scrolledYFloat = yf + mScrollY;  
    final Rect frame = mTempRect;  
    boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;  
	//ViewGroup中按下事件處理;
    if (action == MotionEvent.ACTION_DOWN) { 
		//如果mMotionTarget儲存了View,因為是從新開始的DOWN,所以清空;
        if (mMotionTarget != null) {  
            mMotionTarget = null;  
        }  
		//是否禁用攔截事件功能,如果禁用了攔截功能(不攔截)或者onInterceptTouchEvent()返回false,說明不攔截事件。
        if (disallowIntercept || !onInterceptTouchEvent(ev)) {  
            ev.setAction(MotionEvent.ACTION_DOWN);  
            final int scrolledXInt = (int) scrolledXFloat;  
            final int scrolledYInt = (int) scrolledYFloat;  
            final View[] children = mChildren;  
            final int count = mChildrenCount; 
			//迴圈每一個子View;
            for (int i = count - 1; i >= 0; i--) {  
                final View child = children[i];  
                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE  
                        || child.getAnimation() != null) {  
                    child.getHitRect(frame);
					//判斷當前子View是否包含點選區域;
                    if (frame.contains(scrolledXInt, scrolledYInt)) {  
                        final float xc = scrolledXFloat - child.mLeft;  
                        final float yc = scrolledYFloat - child.mTop;  
                        ev.setLocation(xc, yc);  
                        child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
						//呼叫子View的dispatchTouchEvent() ,向子事件分發事件;
						//1.如果返回true,說明子類或者子類的某個子類中消費了事件,將這個child記錄到mMotionTarget中,每一個ViewGroup都記錄了
						//消費了這個事件的子View,像鏈式,最後當前ViewGroup返回true,返回給呼叫這個ViewGroup的外層ViewGroup,一直到Activity中
						//的dispatchTouchEvent()中,最後Activity中也返回true;
						//2.如果返回false,說明子類或者子類的子類中沒有消費這個事件,所以mMotionTarget不會被賦值,會繼續向下執行程式碼;
                        if (child.dispatchTouchEvent(ev))  {  
                            mMotionTarget = child;  
                            return true;  
                        }  
                    }  
                }  
            }  
        }  
    }  
    boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||  
            (action == MotionEvent.ACTION_CANCEL);  
    if (isUpOrCancel) {  
        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;  
    }  
    final View target = mMotionTarget;
    //ACTION_DOWN事件時,被攔截,或者子類沒有消費ACTION_DOWN,呼叫自己的onTouchEvent()執行onTouch、onClick()方法等;
	//ACTION_UP時,子類沒有消費ACTION_DOWN;
    if (target == null) {  
        ev.setLocation(xf, yf);  
        if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {  
            ev.setAction(MotionEvent.ACTION_CANCEL);  
            mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
        }
		//呼叫super方法即呼叫到View中的dispatchTouchEvent()方法,還是呼叫的當前View,
		//在View中會呼叫自己的onTouchEvent()執行onTouch、onClick()方法等處理;
		//這裡兩種情況:1.自身沒有處理返回false;2.自身處理了返回true,本身處理了,不會儲存到mMotionTarget中,而它的外層ViewGroup會儲存他;
		//true/false都會一直返回到Activity中。
        return super.dispatchTouchEvent(ev);  
    }  
	//ACTION_UP或者MOVE時,子類中消費了ACTION_DOWN事件,沒有禁用攔截功能(可以攔截),並且攔截事件;
	// 無論 target 是否為 null ,ACTION_DOWN事件的處理都不能走到這裡,在之下都是ACTION_MOVE和ACTION_UP的邏輯
	// 如果執行到這裡,說明有響應ACTION_DOWN事件的view物件,這就看我們是否被允許攔截和要不要攔截了
	// 如果允許攔截並且攔截了ACTION_MOVE和ACTION_UP事件,則將ACTION_CANCEL事件分發給target
	// 然後直接返回true,表示已經響應了該次事件
    if (!disallowIntercept && onInterceptTouchEvent(ev)) {  
        final float xc = scrolledXFloat - (float) target.mLeft;  
        final float yc = scrolledYFloat - (float) target.mTop;  
        mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
        ev.setAction(MotionEvent.ACTION_CANCEL);  
        ev.setLocation(xc, yc);
		//將子類事件重置為ACTION_CANCEL;
        if (!target.dispatchTouchEvent(ev)) {  
        }  
        mMotionTarget = null;  
        return true;  
    }  
    if (isUpOrCancel) {  
        mMotionTarget = null;  
    }  
    final float xc = scrolledXFloat - (float) target.mLeft;  
    final float yc = scrolledYFloat - (float) target.mTop;  
    ev.setLocation(xc, yc);  
    if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {  
        ev.setAction(MotionEvent.ACTION_CANCEL);  
        target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
        mMotionTarget = null;  
    }
    // 如果沒有攔截ACTION_MOVE和ACTION_UP事件,則直接派發給target	
    return target.dispatchTouchEvent(ev);  
} 
註釋加了不少,看幾遍就能明白。
事件分發機制在ViewGroup中的派發流程:
在ViewGroup的dispatchTouchEvent()中,
1.ACTION_DOWN:首先判斷攔截相關設定,不攔截:獲取每一個子類判斷點選區域是否在當前子類上,
在的話呼叫其dispatchTouchEvent()方法,方法返回true說明當前View處理當前事件序列,記錄到mMotionTarget中返回true,
表明找到了處理當前事件的View,停止事件分發。方法返回false,說明當前子View及其子孫View不處理當前事件,如果所有子View都不處理,
後續程式碼會呼叫當前ViewGroup的super.dispatchTouchEvent(ev),即執行自己的OnTouch(),OnClick()等(自己處理),如果返回了true,
表明自己處理當前事件,如果返回false,自己不處理當前事件。無論返回true還是false最終都是返回到Activity中。
2.ACTION_MOVE:判斷攔截相關:不攔截,呼叫target.dispatchTouchEvent(ev)進行事件分發;攔截:將target的MotionEvent置為Cancel,
mMotionTarget賦值為null,返回true,表明自己消費了事件。這次不會呼叫自己的super.dispatchTouchEvent(ev)。
再次MOVE分發時由於mMotionTarget為空了,所以會呼叫自己的super.dispatchTouchEvent(ev),即自身處理。
3.ACTION_CANCEL或ACTION_UP:回覆狀態預設值,判斷是否攔截,攔截返回true;不攔截向下派發事件。

下邊看一下在Viewgroup的dispatchTouchEvent方法中繼續向下分發事件程式碼if(child.dispatchTouchEvent(ev))執行後如果child也是一個ViewGroup子類那麼跟上邊的邏輯是一樣的,那麼如果child是一個View的子類,比如Button、TextView、ImageView等時,是如何分發事件的,即事件在View類中的傳遞流程,接著看一下原始碼:

//View中的dispatchTouchEvent;
public boolean dispatchTouchEvent(MotionEvent event) {  
    if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&  
            mOnTouchListener.onTouch(this, event)) {  
        return true;  
    }  
    return onTouchEvent(event);  
}
重要原始碼就這些,在這裡可以看到首先判斷我們有沒有給當前View設定OnTouchListener事件,並且當前View是enabled狀態的,如果前兩個條件都滿足,則會呼叫我們OnTouchListener中的onTouch()方法,如果上邊三個條件都為true,則直接返回true,表明當前View消費當前事件,如果我們在onTouch()方法中返回false,那麼就會跳過判斷執行自己的onTouchEvent()方法,這裡先提前說一下,呼叫onTouchEvent()方法也有繼續判斷當前View是否消費當前事件的作用,在其內部會根據我們點選在控制元件上停留時間判斷是否執行OnClick,OnLongClick事件。下邊看一下onTouchEvent()原始碼:
//View中onTouchEvent();
public boolean onTouchEvent(MotionEvent event) {  
    final int viewFlags = mViewFlags;
	//當前View是Disabled狀態且是可點選則會消費掉事件	
    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));  
    }  
	//將事件交給代理者處理,直接return true
    if (mTouchDelegate != null) {  
        if (mTouchDelegate.onTouchEvent(event)) {  
            return true;  
        }  
    }  
	//View可以點選或者長按,只要進入判斷,一定反回true,即消費事件;
    if (((viewFlags & CLICKABLE) == CLICKABLE ||  
            (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {  
        switch (event.getAction()) {  
            case MotionEvent.ACTION_UP:  
                boolean prepressed = (mPrivateFlags & PREPRESSED) != 0; 
				//100ms內或者以後都會進入程式碼塊;
                if ((mPrivateFlags & 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();  
                    }
				    //如果是在0到500ms之間或者500秒之後執行了LongClick事件但返回的false(不攔截事件);
                    if (!mHasPerformedLongPress) {  
                        // This is a tap, so remove the longpress check
					    //取消長按事件任務監聽;
                        removeLongPressCallback();  
                        // Only perform take click actions if we were in the pressed state
						//在pressed狀態,只執行Click事件;
                        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();  
                            }  
                        }  
                    }  
                    if (mUnsetPressedState == null) {  
                        mUnsetPressedState = new UnsetPressedState();  
                    }  
                    if (prepressed) {  
                        mPrivateFlags |= PRESSED;  
                        refreshDrawableState();  
                        postDelayed(mUnsetPressedState,  
                                ViewConfiguration.getPressedStateDuration());  
                    } else if (!post(mUnsetPressedState)) {  
                        // If the post failed, unpress right now  
                        mUnsetPressedState.run();  
                    }  
                    removeTapCallback();  
                }  
                break;  
            case MotionEvent.ACTION_DOWN:  
                if (mPendingCheckForTap == null) {  
                    mPendingCheckForTap = new CheckForTap();  
                }
				//設定被點選狀態;
                mPrivateFlags |= PREPRESSED;
				//沒有觸發長按事件;
                mHasPerformedLongPress = false;
				//傳送100毫秒延時訊息,執行CheckForTap類中方法。
                postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());  
                break;  
            case MotionEvent.ACTION_CANCEL:  
                mPrivateFlags &= ~PRESSED;  
                refreshDrawableState();  
                removeTapCallback();  
                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 = mTouchSlop;
                if ((x < 0 - slop) || (x >= getWidth() + slop) ||  
                        (y < 0 - slop) || (y >= getHeight() + slop)) {  
                    // Outside button 
					//手指滑出了View範圍,取消100ms計時任務;					
                    removeTapCallback(); 
					//判斷是否包含PRESSED標識,如果包含,移除長按的檢查任務;					
                    if ((mPrivateFlags & PRESSED) != 0) {  
                        // Remove any future long press/tap checks  
                        removeLongPressCallback();  
                        // Need to switch from pressed to not pressed  
                        mPrivateFlags &= ~PRESSED;  
                        refreshDrawableState();  
                    }  
                }  
                break;  
        }  
        return true;  
    }  
    return false;  
} 
有些相對來說重要的類或者程式碼我會在下邊貼出來,幫助大家更好地理解,在這裡程式碼就不詳細的講了,在最後我還會總結,相關程式碼:
//點選狀態變為PRESSED類;
private final class CheckForTap implements Runnable {  
      public void run() {  
          mPrivateFlags &= ~PREPRESSED;
		  //設定PRESSED標識
          mPrivateFlags |= PRESSED;
		  //重新整理背景
          refreshDrawableState(); 
		  //如果View支援長按事件,呼叫postCheckForLongClick()方法;
          if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {  
              postCheckForLongClick(ViewConfiguration.getTapTimeout());  
          }  
      }  
  }
//ViewConfiguration.getLongPressTimeout() 為500ms;再次傳送500-100=400ms延時訊息
private void postCheckForLongClick(int delayOffset) {  
       mHasPerformedLongPress = false;  
  
       if (mPendingCheckForLongPress == null) {  
           mPendingCheckForLongPress = new CheckForLongPress();  
       }  
       mPendingCheckForLongPress.rememberWindowAttachCount();  
       postDelayed(mPendingCheckForLongPress,  
               ViewConfiguration.getLongPressTimeout() - delayOffset);  
   } 
//長按事件發生處理器;
class CheckForLongPress implements Runnable {  
  
        private int mOriginalWindowAttachCount;  
  
        public void run() {  
            if (isPressed() && (mParent != null)  
                    && mOriginalWindowAttachCount == mWindowAttachCount) {  
				//執行LongClick監聽;
                if (performLongClick()) {  
                    mHasPerformedLongPress = true;  
                }  
            }  
        }  
   }
總結:也就是說如果使用者設定了LongClickListener,從使用者觸發ACTION_DOWN開始500ms才能觸發事件,如果達不到500ms,視為點選事件;
如果longClickListener中返回true,mHasPerformedLongPress就是true;
//還在100ms內,取消監聽;
   private void removeTapCallback() {  
       if (mPendingCheckForTap != null) {  
           mPrivateFlags &= ~PREPRESSED;  
           removeCallbacks(mPendingCheckForTap);  
       }  
   } 

//取消長按事件任務監聽;
   private void removeLongPressCallback() {
        if (mPendingCheckForLongPress != null) {
          removeCallbacks(mPendingCheckForLongPress);
        }
    }
總結:主要就是判斷是否移出當前View,如果移出,根據時間移出相關計時任務;
private final class UnsetPressedState implements Runnable {
        public void run() {
            setPressed(false);
        }
    }
 public void setPressed(boolean pressed) {
        if (pressed) {
            mPrivateFlags |= PRESSED;
        } else {
            mPrivateFlags &= ~PRESSED;
        }
        refreshDrawableState();
        dispatchSetPressed(pressed);
    }
清除PRESSED,重新整理背景,向下傳遞pressed;
事件分發在View中的流程總結:
呼叫View的dispatchTouchEvent(),判斷是否設定了OnTouchListener並且View為Enable的,如果都滿足,呼叫OnTouchListener.onTouch(this, event),如果返回true,
直接返回true,表明當前View處理當前事件,不會呼叫自己的onTouchEvent(),如果onTouth()方法返回false,則呼叫onTouchEvent()。
在onTouchEvent()中如果當前View為可點選或者可長按則一定返回true,否則返回false。
1.ACTION_DOWN:初始化狀態為PREPRESSED,設定mHasPerformedLongPress為flase,傳送100ms任務,如果100ms到後,還沒有UP或者移出當前View,
則將標誌置為PRESSED,如果支援長按事件,傳送另一個400ms計時任務,如果到時間後依然沒有UP,LongClickListener不為空,呼叫執行,返回true,將
mHasPerformedLongPress設定為true。
2.ACTION_MOVE:檢測使用者有沒有移出View,並移除相關時間任務。
3.ACTION_UP:100ms內仍然是PREPRESSED狀態,不會觸發Click;在100到500ms之間,取消仍然執行的計時任務,執行 performClick去執行Onclick;
如果在500ms以後,沒有設定onLongClickListener或者返回false,仍然會執行performClick;返回true,onClick不執行;LongClick在Click前執行。

       在事件分發過程中OnTouchListener優先OnClickListener與OnLongClickListener執行,如果返回false,OnClickListener與OnLongClickListener才有可能執行到,返回true不執行。在500ms內擡起手指不會執行OnLongClickListener,會執行OnClickListener,在500ms以後如果OnLongClickListener返回false,OnClickListener會執行,返回true不執行。這就是他們幾個的執行順序及規則,當然這是建立在我們註冊了這三個監聽。今天的總結就到這裡,希望對初學者有幫助,如果哪裡不對,歡迎大神拍磚,謝謝!