事件分發總結

一、基礎知識

1.分發對象

事件:Touch事件相關細節(發生觸摸的位置、時間、歷史記錄、手勢動作等)被封裝成MotionEvent對象

2.事件

主要發生的Touch事件大致分爲以下四種:

  • MotionEvent.ACTION_DOWN:按下事件(所有事件的開始)

  • MotionEvent.ACTION_MOVE:滑動事件

  • MotionEvent.ACTION_CANCEL:非人爲原因結束本次事件

  • MotionEvent.ACTION_UP:擡起事件(與DOWN對應)

3.大體流程

 4.主要方法

5.傳遞對象

一個點擊事件產生後,傳遞順序是:Activity(Window) -> ViewGroup -> View

 二、流程介紹

文字敘述太繁瑣,圖更好理解

主要方法

 主體流程

三、源碼分析

1.Activity的事件分發

當一個點擊事件發生時,事件最先傳到Activity的dispatchTouchEvent()進行事件分發

public boolean dispatchTouchEvent(MotionEvent ev) {
    //一般事件列開始都是DOWN,所以這裏基本是true
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
    //關注點1
        onUserInteraction();
    }
    //關注點2
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    //關注點3
    return onTouchEvent(ev);
}

關注點1

onUserInteraction:每當Key,Touch,Trackball事件分發到當前Activity就會被調用。如果你想當你的Activity在運行的時候,能夠得知用戶正在與你的設備交互,你可以override該方法。

關注點2

getWindow()可以得到一個Window對象,Window類是抽象類,且PhoneWindow是Window類的唯一實現類

superDispatchTouchEvent(ev)是抽象方法,通過PhoneWindow類中看一下superDispatchTouchEvent()的作用

@Overridepublic boolean superDispatchTouchEvent(MotionEvent event) {
    return mDecor.superDispatchTouchEvent(event);
    //mDecor是DecorView的實例
    //DecorView是視圖的頂層view,繼承自FrameLayout,是所有界面的父類
}

接下來看mDecor.superDispatchTouchEvent(event):

public boolean superDispatchTouchEvent(MotionEvent event) {
    return super.dispatchTouchEvent(event);
    //DecorView繼承自FrameLayout
    //那麼它的父類就是ViewGroup,而super.dispatchTouchEvent(event)方法,其實就應該是ViewGroup的dispatchTouchEvent()
}

從這開始ViewGroup的事件分發了

關注點3

當viewGroup分發事件失敗,Activity將會自己處理

2.ViewGroup的事件分發

ViewGroup的dispatchTouchEvent()源碼分析,該方法比較複雜,截取幾個重要的邏輯片段進行介紹,來解析整個分發流程。

// 發生ACTION_DOWN事件或者已經發生過ACTION_DOWN,並且將mFirstTouchTarget賦值,才進入此區域,主要功能是攔截器
final boolean intercepted;
if(actionMasked==MotionEvent.ACTION_DOWN||mFirstTouchTarget!=null){
    //disallowIntercept:是否禁用事件攔截的功能(默認是false),即不禁用
    //可以在子View通過調用requestDisallowInterceptTouchEvent方法對這個值進行修改,不讓該View攔截事件
    final boolean disallowIntercept=(mGroupFlags&FLAG_DISALLOW_INTERCEPT)!=0;
    //默認情況下會進入該方法
    if(!disallowIntercept){
        //調用攔截方法
        intercepted=onInterceptTouchEvent(ev);
        ev.setAction(action);
    }else{
        intercepted=false;
    }
}else{
    // 當沒有觸摸targets,且不是down事件時,開始持續攔截觸摸。
    intercepted=true;
}

這一段的內容主要是爲判斷是否攔截。如果當前事件的MotionEvent.ACTION_DOWN,則進入判斷,調用ViewGroup onInterceptTouchEvent()方法的值,判斷是否攔截。如果mFirstTouchTarget != null,即已經發生過MotionEvent.ACTION_DOWN,並且該事件已經有ViewGroup的子View進行處理了,那麼也進入判斷,調用ViewGroup onInterceptTouchEvent()方法的值,判斷是否攔截。如果不是以上兩種情況,即已經是MOVE或UP事件了,並且之前的事件沒有對象進行處理,則設置成true,開始攔截接下來的所有事件。這也就解釋瞭如果子View的onTouchEvent()方法返回false,那麼接下來的一些列事件都不會交給他處理。如果VieGroup的onInterceptTouchEvent()第一次執行爲true,則mFirstTouchTarget = null,則也會使得接下來不會調用onInterceptTouchEvent(),直接將攔截設置爲true。

當ViewGroup不攔截事件的時候,事件會向下分發交由它的子View或ViewGroup進行處理。

/* 從最底層的父視圖開始遍歷, ** 找尋newTouchTarget,即上面的mFirstTouchTarget ** 如果已經存在找尋newTouchTarget,說明正在接收觸摸事件,則跳出循環。 */
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;
final View child = (preorderedList == null) ? children[childIndex] : preorderedList.get(childIndex);

// 如果當前視圖無法獲取用戶焦點,則跳過本次循環
if (childWithAccessibilityFocus != null) {
        if (childWithAccessibilityFocus != child) {
            continue;
        }
    childWithAccessibilityFocus = null;
    i = childrenCount - 1;
}
//如果view不可見,或者觸摸的座標點不在view的範圍內,則跳過本次循環
if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) {
    ev.setTargetAccessibilityFocus(false);
    continue;
}

newTouchTarget = getTouchTarget(child);
// 已經開始接收觸摸事件,並退出整個循環。
if (newTouchTarget != null) {
    newTouchTarget.pointerIdBits |= idBitsToAssign;
    break;
}

//如果觸摸位置在child的區域內,則把事件分發給子View或ViewGroup
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
    // 獲取TouchDown的時間點
    mLastTouchDownTime = ev.getDownTime();
    // 獲取TouchDown的Index
    if (preorderedList != null) {
        for (int j = 0; j < childrenCount; j++) {
            if (children[childIndex] == mChildren[j]) {
                mLastTouchDownIndex = j;
                break;
            }
        }
    } else {
        mLastTouchDownIndex = childIndex;
    }
//獲取TouchDown的x,y座標
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
//添加TouchTarget,則mFirstTouchTarget != null
newTouchTarget = addTouchTarget(child, idBitsToAssign);
//表示以及分發給NewTouchTarget
alreadyDispatchedToNewTouchTarget = true;
break;
}

dispatchTransformedTouchEvent()方法實際就是調用子元素的dispatchTouchEvent()方法。 其中dispatchTransformedTouchEvent()方法的重要邏輯如下:

if (child == null) {
    handled = super.dispatchTouchEvent(event);
} else {
    handled = child.dispatchTouchEvent(event);
}

由於其中傳遞的child不爲空,所以就會調用子元素的dispatchTouchEvent()。 如果子元素的dispatchTouchEvent()方法返回true,那麼mFirstTouchTarget就會被賦值,同時跳出for循環。

//添加TouchTarget,則mFirstTouchTarget != null。
newTouchTarget = addTouchTarget(child, idBitsToAssign);
//表示以及分發給NewTouchTarget
alreadyDispatchedToNewTouchTarget = true;

其中在addTouchTarget(child, idBitsToAssign);內部完成mFirstTouchTarget被賦值。 如果mFirstTouchTarget爲空,將會讓ViewGroup默認攔截所有操作。 如果遍歷所有子View或ViewGroup,都沒有消費事件。ViewGroup會自己處理事件。

3.View的事件分發

public boolean dispatchTouchEvent(MotionEvent event) {
    if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && mOnTouchListener.onTouch(this, event)) {
        return true;
    }
    return onTouchEvent(event);
}

第一個條件:mOnTouchListener!= null

//mOnTouchListener是在View類下setOnTouchListener方法裏賦值的
public void setOnTouchListener(OnTouchListener l) {
    //即只要我們給控件註冊了Touch事件,mOnTouchListener就一定被賦值(不爲空)
    mOnTouchListener = l;
}

第二個條件:(mViewFlags & ENABLED_MASK) == ENABLED

  • 該條件是判斷當前點擊的控件是否enable

  • 由於很多View默認是enable的,因此該條件恆定爲true

第三個條件:mOnTouchListener.onTouch(this, event)

  • 如果在onTouch方法返回true,就會讓上述三個條件全部成立,從而整個方法直接返回true。

  • 如果在onTouch方法裏返回false,就會去執行onTouchEvent(event)方法。

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;
        }
    }

    //如果該控件是可以點擊的就會進入到下兩行的switch判斷中去;
    if (((viewFlags & CLICKABLE) == CLICKABLE ||
            (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
        //如果當前的事件是擡起手指,則會進入到MotionEvent.ACTION_UP這個case當中。
        switch (event.getAction()) {
            case MotionEvent.ACTION_UP:
                boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
                // 在經過重重判斷之後,會執行到performClick()方法。
                if ((mPrivateFlags & PRESSED) != 0 || prepressed) {
                    boolean focusTaken = false;
                    if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                        focusTaken = requestFocus();
                    }
                    if (!mHasPerformedLongPress) {
                        removeLongPressCallback();
                        if (!focusTaken) {
                            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)) {
                        mUnsetPressedState.run();
                    }
                    removeTapCallback();
                }
                break;

            case MotionEvent.ACTION_DOWN:
                if (mPendingCheckForTap == null) {
                    mPendingCheckForTap = new CheckForTap();
                }
                mPrivateFlags |= PREPRESSED;
                mHasPerformedLongPress = false;
                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();
                int slop = mTouchSlop;
                if ((x < 0 - slop) || (x >= getWidth() + slop) ||
                        (y < 0 - slop) || (y >= getHeight() + slop)) {
                    removeTapCallback();
                    if ((mPrivateFlags & PRESSED) != 0) {
                        removeLongPressCallback();
                        mPrivateFlags &= ~PRESSED;
                        refreshDrawableState();
                    }
                }
                break;
        }
        //如果該控件是可以點擊的,就一定會返回true
        return true;
    }
    //如果該控件是不可以點擊的,就一定會返回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;
}

只要mOnClickListener不爲null,就會去調用onClick方法;

那麼,mOnClickListener又是在哪裏賦值的呢?

public void setOnClickListener(OnClickListener l) {
    if (!isClickable()) {
        setClickable(true);
    }
    mOnClickListener = l;
}

四、總結

android事件產生後的傳遞過程是從Activity--->Window--->View的,即隧道式傳遞,而View又分爲不包含子 View的View以及包含子View的ViewGroup,事件產生之後首先傳遞到Activity上面,而Activity接着會傳遞到 PhoneWindow上,PhoneWindow會傳遞給RootView,而RootView其實就是DecorView了,接下來便是從 DecorView到View上的分發過程了,具體就可以分成ViewGroup和View的分發兩種情況了;

        對於ViewGroup而言,當事件分發到當前ViewGroup上面的時候,首先會調用他的dispatchTouchEvent方法,在 dispatchTouchEvent方法裏面會調用onInterceptTouchEvent來判斷是否要攔截當前事件,如果要攔截的話,就會調用 ViewGroup自己的onTouchEvent方法了,如果onInterceptTouchEvent返回false的話表示不攔截當前事件,那麼 事件將會繼續往當前ViewGroup的子View上面傳遞了,如果他的子View是ViewGroup的話,則重複ViewGroup事件分發過程,如 果子View就是View的話,則轉到下面的View分發過程;

        對於View而言,事件傳遞過來首先當然也是執行他的dispatchTouchEvent方法了,如果我們爲當前View設置了 onTouchListener監聽器的話,首先就會執行他的回調方法onTouch了,這個方法的返回值將決定事件是否要繼續傳遞下去了,如果返回 false的話,表示事件沒有被消費,還會繼續傳遞下去,如果返回true的話,表示事件已經被消費了,不再需要向下傳遞了;如果返回false,那麼將 會執行當前View的onTouchEvent方法,如果我們爲當前View設置了onLongClickListener監聽器的話,則首先會執行他的 回調方法onLongClick,和onTouch方法類似,如果該方法返回true表示事件被消費,不會繼續向下傳遞,返回false的話,事件會繼續 向下傳遞,爲了分析,我們假定返回false,如果我們設置了onClickListener監聽器的話,則會執行他的回調方法onClick,該方法是 沒有返回值的,所以也是我們事件分發機制中最後執行的方法了;可以注意到的一點就是隻要你的當前View是clickable或者 longclickable的,View的onTouchEvent方法默認都會返回true,也就是說對於事件傳遞到View上來說,系統默認是由 View來消費事件的,但是ViewGroup就不是這樣了;

        上面的事件分發過程只是正常情況下的,如果有這樣一種情況,比如事件傳遞到最裏層的View之後,調用該View的oonTouchEvent方法返回了 false,那麼這時候事件將通過冒泡式的方式向他的父View傳遞,調用它父View的onTouchEvent方法,如果正好他的父View的 onTouchEvent方法也返回false的話,這個時候事件最終將會傳遞到Activity的onTouchEvent方法了,也就是最終就只能由 Activity自己來處理了;

事件分發機制需要注意的幾點:

        (1):如果說除Activity之外的View都沒有消費掉DOWN事件的話,那麼事件將不再會傳遞到Activity裏面的子View了,將直接由Activity自己調用自己的onTouchEvent方法來處理了;

        (2):一旦一個ViewGroup決定攔截事件,那麼這個事件序列剩餘的部分將不再會由該ViewGroup的子View去處理了,即事件將在此 ViewGroup層停止向下傳遞,同時隨後的事件序列將不再會調用onInterceptTouchEvent方法了;

        (3):如果一個View開始處理事件但是沒有消費掉DOWN事件,那麼這個事件序列隨後的事件將不再由該View來處理,通俗點講就是你自己沒能力就別瞎BB,要不以後的事件就都不給你了;

        (4):View的onTouchEvent方法是否執行是和他的onTouchListener回調方法onTouch的返回值息息相關 的,onTouch返回true,onTouchEvent方法不執行;onTouch返回false,onTouchEvent方法執行,因爲 onTouchEvent裏面會執行onClick,所以造成了onClick是否執行和onTouch的返回值有了關係;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章