Android事件分發(fā)過程(筆記)

之前都是在網(wǎng)上看別人的文章,很容易忘掉切油,今天重新翻一下源碼并簡單記錄一下
先簡單看一下dispatchTouchEvent认罩、onInterceptTouchEvent皱蹦、onTouchEvent這三個方法在Activity衫冻、ViewGroup诀紊、View中是怎么個流程。如下圖(有些地方可能畫的不太準確)

事件分發(fā)流程.png

源碼

根據(jù)上圖我們再梳理一下源碼就容易很多了隅俘。

Activity事件傳遞

先從 ActivitydispatchTouchEvent看起

    public boolean dispatchTouchEvent(MotionEvent ev) {
        //判斷是否是   MotionEvent.ACTION_DOWN  事件
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            //這是一個空的方法 
            onUserInteraction();
        }
        //調(diào)用到了 Window 中的  superDispatchTouchEvent
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }

上面的getWindow().superDispatchTouchEvent(ev)句話調(diào)用了抽象類Window 中的方法邻奠, PhoneWindowWindow 的唯一子類,所以我們看一下 子類PhoneWindow中的superDispatchTouchEvent方法

    @Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }

ViewGroup事件分發(fā)

上面這個方法調(diào)用到了 DecorView 中的方法为居,而他是繼承了 FrameLayout , FrameLayout又繼承了ViewGroup碌宴,這里調(diào)用到了 ViewGroupdispatchTouchEvent方法

    //DecorView
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
    }
    //ViewGroup
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
        }

        // If the event targets the accessibility focused view and this is it, start
        // normal event dispatch. Maybe a descendant is what will handle the click.
        if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
            ev.setTargetAccessibilityFocus(false);
        }
        //該方法的最終返回值
        boolean handled = false;
        if (onFilterTouchEventForSecurity(ev)) {//過濾觸摸事件以確保安全
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;

            // Handle an initial down.   處理 ACTION_DOWN 事件
            if (actionMasked == MotionEvent.ACTION_DOWN) {
                //在開始一個新的Touch之前先清空所有的狀態(tài)
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }

            // Check for interception.//  onInterceptTouchEvent 方法的結果
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                //禁用攔截
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    //調(diào)用 onInterceptTouchEvent()方法,方法返回false蒙畴,
                    intercepted = onInterceptTouchEvent(ev);//該方法決定是否攔截事件
                    //恢復Action 防止他被改變
                    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;
            }
            ...
        //默認情況下canceled 和  intercepted 為false
        if (!canceled && !intercepted) {
            ...
          //判斷是否是ACTION_DOWN 事件贰镣,如果是那么表示這是一個系列事件的開始
          if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
            ...
            
            //這個方法是對事件進行分發(fā)(分發(fā)給子View)
            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                // Child wants to receive touch within its bounds.
                mLastTouchDownTime = ev.getDownTime();
                if (preorderedList != null) {
                    // childIndex points into presorted list, find original index
                    for (int j = 0; j < childrenCount; j++) {
                        if (children[childIndex] == mChildren[j]) {
                            mLastTouchDownIndex = j;
                            break;
                        }
                    }
                } else {
                    mLastTouchDownIndex = childIndex;
                }
                mLastTouchDownX = ev.getX();
                mLastTouchDownY = ev.getY();
                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                alreadyDispatchedToNewTouchTarget = true;
                break;
            }
         }     
        }
            if (mFirstTouchTarget == null) {
                //如果沒有子View消費事件,這里最終會調(diào)用ViewGroup的onTouchEvent()
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
              ...
          }

        if (!handled && mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
        }
        return handled;
    }

ViewGroup中沒有自己的onTouchEvent方法忍抽,ViewGroup是繼承在View的,最終是通過dispatchTransformedTouchEvent方法將事件分發(fā)到View進行事件消費的董朝。接下來看下這個方法是如何進行事件分發(fā)的

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;
        ...
        
        if (child == null) {
            //如果 子View 為 null時說明事件不需要繼續(xù)往下分發(fā)鸠项,調(diào)用本身父類(ViewGroup的父類是View)的dispatchTouchEvent,
            //也就是說調(diào)用了View類中的dispatchTouchEvent方法
            handled = super.dispatchTouchEvent(transformedEvent);
        } else {
            ...
            //如果子View不為null,則調(diào)用 子View的 dispatchTouchEvent 方法
            handled = child.dispatchTouchEvent(transformedEvent);
        }
        ...
         // Done.
        transformedEvent.recycle();
        return handled;
    }

上面調(diào)用到View類中的 dispatchTouchEvent 方法子姜,事件就是從這個方法中消費的祟绊。
對于上面 super.dispatchTouchEventchild.dispatchTouchEvent(transformedEvent); 這兩個要分清,第一個是 ViewGroup 父類中的方法哥捕,第二個是 子View(比如 TextView牧抽、Button等) 的方法,這兩個都是為了去調(diào)用 onTouchEvent 方法消費事件
從Activity——>ViewGroup到這算是結束了遥赚,接下來就是View中消費事件

View消費事件

    public boolean dispatchTouchEvent(MotionEvent event) {
        ...
        boolean result = false;
         ...
        final int actionMasked = event.getActionMasked();
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            // Defensive cleanup for new gesture
            stopNestedScroll();
        }

        if (onFilterTouchEventForSecurity(event)) {
            //按鈕是不是Enable的
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
            //ListenerInfo  監(jiān)聽的合集扬舒,OnFocusChangeListener、OnScrollChangeListener凫佛、OnClickListener等等
           //如果他不為null說明我們設置了監(jiān)聽
            //判斷是否設置了onTouchListener 監(jiān)聽
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {//這里會執(zhí)行OnTouchListener 的 onTouch 方法
                result = true;
            }
            //如果設置了onTouchListener的監(jiān)聽并且返回為true時就不會調(diào)用onTouchEvent方法
            //如果上面沒有處理就會調(diào)用onTouchEvent
            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }
         ...
        return result;
    }

上面這個方法可以看出onTouchListener讲坎、onTouchEvent 的優(yōu)先級孕惜;onTouchListener—>onTouchEvent
還有一點 就onTouchListeneronTouchEvent這兩個方法而言晨炕,如果沒有 setOnTouchListener那么一定會執(zhí)行onTouchEvent方法衫画,如果設置了setOnTouchListener那就要看onTouch的返回值了
那么我們的setOnClickListener是什么時候調(diào)用的呢,接下來看onTouchEvent中的源碼

    public boolean onTouchEvent(MotionEvent event) {
        final float x = event.getX();
        final float y = event.getY();
        final int viewFlags = mViewFlags;
        final int action = event.getAction();
        //是否可以點擊
        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:
                    ...
                        //mHasPerformedLongPress是否已經(jīng)執(zhí)行了長按事件瓮栗,true已經(jīng)執(zhí)行了,false未執(zhí)行
                        // 還未執(zhí)行長按事件時才能進行
                        if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                            //先移除還未被執(zhí)行的長按事件
                            removeLongPressCallback();
                            // 只有在我們處于按下狀態(tài)時才執(zhí)行點擊操作
                            if (!focusTaken) {
                                 //使用Runnable 處理這個點擊事件削罩,而不是直接執(zhí)行點擊。目的就是在單擊操作開始之前可以更新View的狀態(tài)
                                if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }
                                if (!post(mPerformClick)) {
                                     //真正執(zhí)行Click的方法费奸,內(nèi)部調(diào)用了performClick這個方法
                                    performClickInternal();
                                }
                            }
                        }

                    break;

                case MotionEvent.ACTION_DOWN:
                    ...
                    if (isInScrollingContainer) {
                      ...
                    } else {
                        // Not inside a scrolling container, so show the feedback right away
                        setPressed(true, x, y);
                        ...
                          //長按事件弥激,是一個延遲的消息,
                          //剛調(diào)用checkForLongClick方法時 mHasPerformedLongPress=false货邓,
                          //當長按事件的延遲消息被執(zhí)行后 mHasPerformedLongPress = true
                        checkForLongClick(
                                ViewConfiguration.getLongPressTimeout(),
                                x,
                                y,
                                TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
                    }
                    break;
                case MotionEvent.ACTION_CANCEL:
                    ...省略代碼
                    break;
                case MotionEvent.ACTION_MOVE:
                    ...省略代碼
                    break;
              ... 省略代碼 
        }

最后看一下performClick這個方法秆撮,這個方法中還是使用LinstenerInfo 判斷是否設置了 onClickListener監(jiān)聽,如果用戶手動設置了setOnclickListener()换况, 就調(diào)用 onClick

    public boolean performClick() {
        // We still need to call this method to handle the cases where performClick() was called
        // externally, instead of through performClickInternal()
        notifyAutofillManagerOnClick();

        final boolean result;
        //ListenerInfo 各種監(jiān)聽的合集
        final ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            //執(zhí)行用戶設置的 onClickListener  中的 onClick方法
            li.mOnClickListener.onClick(this);
            result = true;
        } else {
            result = false;
        }

        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);

        notifyEnterOrExitForAutoFillIfNeeded(true);

        return result;
    }

到這整個流程就算是走完了职辨,onTouchListeneronTouchEvent戈二、onClick 的優(yōu)先級也比較清楚了
onTouchListener—>onTouchEvent—>onClick

下面貼出事件分發(fā)的注意事項:

事件分發(fā)注意的問題.png
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末舒裤,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子觉吭,更是在濱河造成了極大的恐慌腾供,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鲜滩,死亡現(xiàn)場離奇詭異伴鳖,居然都是意外死亡,警方通過查閱死者的電腦和手機徙硅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進店門榜聂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人嗓蘑,你說我怎么就攤上這事须肆。” “怎么了桩皿?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵豌汇,是天一觀的道長。 經(jīng)常有香客問我泄隔,道長拒贱,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任佛嬉,我火速辦了婚禮柜思,結果婚禮上岩调,老公的妹妹穿的比我還像新娘。我一直安慰自己赡盘,他們只是感情好号枕,可當我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著陨享,像睡著了一般葱淳。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上抛姑,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天赞厕,我揣著相機與錄音,去河邊找鬼定硝。 笑死皿桑,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的蔬啡。 我是一名探鬼主播诲侮,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼箱蟆!你這毒婦竟也來了沟绪?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤空猜,失蹤者是張志新(化名)和其女友劉穎绽慈,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體辈毯,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡坝疼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了谆沃。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片钝凶。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖管毙,靈堂內(nèi)的尸體忽然破棺而出腿椎,到底是詐尸還是另有隱情桌硫,我是刑警寧澤夭咬,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站铆隘,受9級特大地震影響卓舵,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜膀钠,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一掏湾、第九天 我趴在偏房一處隱蔽的房頂上張望裹虫。 院中可真熱鬧,春花似錦融击、人聲如沸筑公。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽匣屡。三九已至,卻和暖如春拇涤,著一層夾襖步出監(jiān)牢的瞬間捣作,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工鹅士, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留券躁,地道東北人。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓掉盅,卻偏偏與公主長得像也拜,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子怔接,可洞房花燭夜當晚...
    茶點故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內(nèi)容