Android 事件分發(fā)機(jī)制源碼分析

寫(xiě)在前面

首先要了解事件分發(fā)機(jī)制的傳遞流程是在點(diǎn)擊事件發(fā)生后,事件先傳遞到Activity深浮,然后傳遞到ViewGroup剩辟,最終傳遞到View。
主要涉及的三個(gè)方法分別為:

dispatchTouchEvent():分發(fā)傳遞點(diǎn)擊事件
onInterceptTouchEvent() :判斷是否攔截了點(diǎn)擊事件
onTouchEvent() : 處理點(diǎn)擊事件

源碼分析完后做出事件分發(fā)的流程圖


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

源碼分析

1. Activity的事件傳遞機(jī)制
查看自定義Activity中的dispatchTouchEvent方法
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        //系統(tǒng)默認(rèn)點(diǎn)擊空白處 super.dispatchTouchEvent(ev)=false
        //系統(tǒng)默認(rèn)點(diǎn)擊按鈕處 super.dispatchTouchEvent(ev)=true
        //必須執(zhí)行super.dispatchTouchEvent(ev) 方法 否則即使返回true或者false 都不會(huì)往下傳遞也不會(huì)執(zhí)行onTouchEvent()
        return super.dispatchTouchEvent(ev);
    }

查看Activity.java$dispatchTouchEvent方法源碼

   public boolean dispatchTouchEvent(MotionEvent ev) {
        //ev.getAction()獲取屏幕的點(diǎn)擊事件類型躯保,ACTION_DOWN為按下事件
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            // 該方法為空旋膳,作用為實(shí)現(xiàn)屏幕保護(hù)功能
            onUserInteraction();
        }
        //getWindow()方法獲取的是一個(gè)Window對(duì)象
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        //返回處理點(diǎn)擊事件的結(jié)果
        return onTouchEvent(ev);
    }

因?yàn)閃indow是一個(gè)抽象類其具體實(shí)現(xiàn)類為PhoneWindow。
查看PhoneWindow.java$superDispatchTouchEvent方法源碼

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

mDecor是DecorView的一個(gè)對(duì)象吻氧,而DecorView是Window的頂層View也是我們看到的整個(gè)顯示界面溺忧。DecorView繼承FrameLayout類,而FrameLayout是ViewGroup子類盯孙。
查看DecorView.java$superDispatchTouchEvent方法源碼

   public boolean superDispatchTouchEvent(MotionEvent event) {
        //上面說(shuō)到DecorView是ViewGroup的間接子類鲁森,
        //這里調(diào)用的便是ViewGroup的dispatchTouchEvent方法,后面會(huì)分析ViewGroup的該方法
        return super.dispatchTouchEvent(event);
    }

查看自定義Activity中onTouchEvent方法

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        //系統(tǒng)默認(rèn)super.onTouchEvent(event) = false
        //無(wú)論true或者false 都會(huì)執(zhí)行該方法中的代碼
        return super.onTouchEvent(event);
    }

查看Activity.java$onTouchEvent方法源碼

   public boolean onTouchEvent(MotionEvent event) {
        //處理發(fā)生在Window邊界外的點(diǎn)擊事件
        if (mWindow.shouldCloseOnTouch(this, event)) {
            finish();
            return true;
        }
  
        return false;
    }
//查看Window.java$shouldCloseTouch方法源碼
public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
        //判斷點(diǎn)擊事件以及event的x振惰,y軸坐標(biāo)是否在邊界外
        if (mCloseOnTouchOutside && event.getAction() == MotionEvent.ACTION_DOWN
                && isOutOfBounds(context, event) && peekDecorView() != null) {
            //若事件在邊界外則消費(fèi)該事件返回true
            return true;
        }
        return false;
    }
2. ViewGroup的事件分發(fā)機(jī)制

查看自定義ViewGroup的dispatchTouchEvent方法

   @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        //系統(tǒng)默認(rèn)super.dispatchTouchEvent(ev) = true
        //返回true繼續(xù)往下分發(fā) 若返回false 停止事件分發(fā)
        //且onInterceptTouchEvent 歌溉,onTouchEvent將不會(huì)執(zhí)行
        return super.dispatchTouchEvent(ev);
    }

查看ViewGroup.java$dispatchTouchEvent方法源碼

 @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        // 對(duì)輸入框的統(tǒng)一驗(yàn)證,不為null 則對(duì)輸入框的點(diǎn)擊事件進(jìn)行處理
        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;
        //以應(yīng)用安全策略過(guò)濾觸摸事件,返回true即繼續(xù)分發(fā)事件
        if (onFilterTouchEventForSecurity(ev)) {
            //獲取事件點(diǎn)擊類型
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;

            //處理按下事件
            if (actionMasked == MotionEvent.ACTION_DOWN) {

                //取消并清除所有與觸摸目標(biāo)之間的聯(lián)系
                cancelAndClearTouchTargets(ev);

                //重置所有觸摸狀態(tài)準(zhǔn)備新的循環(huán)
                resetTouchState();
            }

            final boolean intercepted;

            // 對(duì)事件是否攔截進(jìn)行處理
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                //是否允許攔截
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                //允許攔截
                if (!disallowIntercept) {
                    // 內(nèi)部調(diào)用onInterceptTouchEvent事件攔截方法
                    intercepted = onInterceptTouchEvent(ev);
                    //恢復(fù)action防止事件發(fā)生變化
                    ev.setAction(action);
                } else {
                    //不允許攔截 intercept 設(shè)置為false
                    intercepted = false;
                }
            } else {
                //沒(méi)有觸摸目標(biāo) 且ation沒(méi)有初始化down事件
                //則ViewGroup將會(huì)繼續(xù)攔截分發(fā)事件
                intercepted = true;
            }

            ......//代碼省略

            TouchTarget newTouchTarget = null;
            boolean alreadyDispatchedToNewTouchTarget = false;
            // 如果沒(méi)有取消觸摸事件以及攔截點(diǎn)擊事件,則繼續(xù)分發(fā)
            if (!canceled && !intercepted) {

                 ......//代碼省略

                if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                    final int actionIndex = ev.getActionIndex(); // always 0 for down
                    final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
                            : TouchTarget.ALL_POINTER_IDS;

                    //移除早期的觸摸目標(biāo)id防止它們不能同步
                    removePointersFromTouchTargets(idBitsToAssign);

                    //mChildrenCount為mChildren數(shù)組中有效的子元素?cái)?shù)量
                    //mChildren為ViewGroup中的子視圖View
                    final int childrenCount = mChildrenCount;
                    if (newTouchTarget == null && childrenCount != 0) {
                        final float x = ev.getX(actionIndex);
                        final float y = ev.getY(actionIndex);
                        // 查找收到觸摸事件的View
                        // 返回觸摸被分發(fā)的自定義排列視圖
 
                         ......//代碼省略

                        final View[] children = mChildren;
                        //遍歷當(dāng)前ViewGroup中的有效視圖
                        for (int i = childrenCount - 1; i >= 0; i--) {
                            final int childIndex = getAndVerifyPreorderedIndex(
                                    childrenCount, i, customOrder);
                            final View child = getAndVerifyPreorderedView(
                                    preorderedList, children, childIndex);

                              ......//代碼省略

                             //該方法處理子View的dispatchTouchEvent
                             if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {}
                        }
                    }

               ......//代碼省略

        return handled;
    }

//繼續(xù)查看dispatchTransformedTouchEvent方法核心源碼
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;

            ......//代碼省略

            //調(diào)用View的dispatchTouchEvent(后面會(huì)分析View該方法)
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                handled = child.dispatchTouchEvent(event);
            }
   
            ......//代碼省略

        return handled;
    }

接著查看自定義ViewGroup的onInterceptTouchEvent方法

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        //系統(tǒng)默認(rèn)super.onInterceptTouchEvent(ev) = false
        //返回true則對(duì)事件進(jìn)行攔截痛垛,且onTouchEvent事件繼續(xù)執(zhí)行
        return super.onInterceptTouchEvent(ev);
    }
//繼續(xù)分析ViewGroup.java$onInterceptTouchEvent方法

//是否攔截事件
public boolean onInterceptTouchEvent(MotionEvent ev) {
        //返回true 即攔截事件草慧,事件停止往下傳遞需要復(fù)寫(xiě)onInterceptTouchEvent()方法
        //系統(tǒng)默認(rèn)返回false,不攔截事件
        return false;
    }

查看自定義ViewGroup的onTouchEvent方法

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        //系統(tǒng)默認(rèn)onTouchEvent() = true
        //返回false 只影響自身的onClickListener事件
        return super.onTouchEvent(event);
    }

因?yàn)閂iewGroup是繼承View匙头,這里調(diào)用onTouchEvent方法也就是調(diào)用View類中的onTouchEvent方法(后面會(huì)分析該方法源碼)漫谷。

3. View的事件分發(fā)機(jī)制

首先查看自定義View的dispatchTouchEvent方法

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        //系統(tǒng)默認(rèn)super.dispatchTouchEvent(event) =true
        //若返回false 將不再分發(fā)事件也不會(huì)執(zhí)行onTouchEvent事件
        return super.dispatchTouchEvent(event);
    }
 //繼續(xù)查看View.java$dispatchTouchEvent方法核心源碼
 public boolean dispatchTouchEvent(MotionEvent event) {
        
        ......//代碼省略

        boolean result = false;

        ......//代碼省略

        if (onFilterTouchEventForSecurity(event)) {
            //mViewFlags為視圖標(biāo)識(shí)記錄視圖狀態(tài)
            //(mViewFlags & ENABLED_MASK) == ENABLED 判斷
            //該點(diǎn)擊控件是否是enabled 類型 默認(rèn)該條件為true
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
            //mListenerInfo 各類監(jiān)聽(tīng)器信息
            ListenerInfo li = mListenerInfo;
            //條件1:li != null
            //條件2:li.mOnTouchListener != null
            //(mViewFlags & ENABLED_MASK) == ENABLED 默認(rèn)為true
            //條件3:li.mOnTouchListener.onTouch(this, event)設(shè)置onTouch事件需要重寫(xiě)onTouch方法
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }
            //若onTouch方法返回false則執(zhí)行onTouchEvent方法 
            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }

        ......//代碼省略

        return result;
    }

先分析條件2:li.mOnTouchListener != null查找mOnTouchListener的賦值代碼

  public void setOnTouchListener(OnTouchListener l) {
        //若注冊(cè)onTouch事件則mOnTouchListener 永不為null
        getListenerInfo().mOnTouchListener = l;
    }
//繼續(xù)分析getListenerInfo方法
ListenerInfo getListenerInfo() {
        if (mListenerInfo != null) {
            return mListenerInfo;
        }
        //該方法為mListenerInfo賦值也就是說(shuō)條件1:li != null一直為true
        mListenerInfo = new ListenerInfo();
        return mListenerInfo;
    }

查看自定義View類中的onTouchEvent方法

   @Override
    public boolean onTouchEvent(MotionEvent event) {
        //系統(tǒng)默認(rèn)super.onTouchEvent() = true
        //返回false 則不再執(zhí)行自身點(diǎn)擊事件
        return super.onTouchEvent(event);
    }
 //查看View.java$onTouchEvent方法核心源碼
 public boolean onTouchEvent(MotionEvent event) {

        ......//代碼省略

        //若view可點(diǎn)擊則進(jìn)入switch語(yǔ)句判斷該事件類型并執(zhí)行對(duì)應(yīng)代碼
        if (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
                (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
            switch (action) {
                //松開(kāi)按下的view
                case MotionEvent.ACTION_UP:
                    boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                 
                  ......//省略各種判斷代碼執(zhí)行performClick方法

                  performClick();

                //按下View
                case MotionEvent.ACTION_DOWN:
                    mHasPerformedLongPress = false;

                    if (performButtonActionOnTouchDown(event)) {
                        break;
                    }

                    // Walk up the hierarchy to determine if we're inside a scrolling container.
                    boolean isInScrollingContainer = isInScrollingContainer();

                    // For views inside a scrolling container, delay the pressed feedback for
                    // a short period in case this is a scroll.
                    if (isInScrollingContainer) {
                        mPrivateFlags |= PFLAG_PREPRESSED;
                        if (mPendingCheckForTap == null) {
                            mPendingCheckForTap = new CheckForTap();
                        }
                        mPendingCheckForTap.x = event.getX();
                        mPendingCheckForTap.y = event.getY();
                        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                    } else {
                        // Not inside a scrolling container, so show the feedback right away
                        setPressed(true, x, y);
                        checkForLongClick(0, x, y);
                    }
                    break;
                // 取消觸摸事件
                case MotionEvent.ACTION_CANCEL:
                    setPressed(false);
                    removeTapCallback();
                    removeLongPressCallback();
                    mInContextButtonPress = false;
                    mHasPerformedLongPress = false;
                    mIgnoreNextUpEvent = false;
                    break;
                //滑動(dòng)view
                case MotionEvent.ACTION_MOVE:
                    drawableHotspotChanged(x, y);

                    // Be lenient about moving outside of buttons
                    if (!pointInView(x, y, mTouchSlop)) {
                        // Outside button
                        removeTapCallback();
                        if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                            // Remove any future long press/tap checks
                            removeLongPressCallback();

                            setPressed(false);
                        }
                    }
                    break;
            }
            //若view可點(diǎn)擊則返回true
            return true;
        }
        //若view不可點(diǎn)擊則返回false
        return false;
    }

查看View.java$performClick方法源碼

   public boolean performClick() {
        final boolean result;
        final ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            //調(diào)用onClick方法
            li.mOnClickListener.onClick(this);
            result = true;
        } else {
            result = false;
        }

        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
        return result;
    }

分析完View的dispatchTouchEvent方法可以發(fā)現(xiàn)該方法中先執(zhí)行onTouch方法,若onTouch方法返回值為false蹂析,則執(zhí)行OnTouchEvent方法舔示,然后執(zhí)行該方法中performClick方法,最后再調(diào)用onclick方法电抚。

view事件傳遞流程圖.png

圖片摘取地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末惕稻,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子蝙叛,更是在濱河造成了極大的恐慌俺祠,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,544評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件借帘,死亡現(xiàn)場(chǎng)離奇詭異蜘渣,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)姻蚓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門宋梧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)报强,“玉大人仑荐,你說(shuō)我怎么就攤上這事澳厢”赴#” “怎么了缰趋?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,764評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵揉燃,是天一觀的道長(zhǎng)迎卤。 經(jīng)常有香客問(wèn)我墩弯,道長(zhǎng)它匕,這世上最難降的妖魔是什么展融? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,193評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮豫柬,結(jié)果婚禮上告希,老公的妹妹穿的比我還像新娘。我一直安慰自己烧给,他們只是感情好燕偶,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著础嫡,像睡著了一般指么。 火紅的嫁衣襯著肌膚如雪酝惧。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,182評(píng)論 1 299
  • 那天伯诬,我揣著相機(jī)與錄音晚唇,去河邊找鬼。 笑死盗似,一個(gè)胖子當(dāng)著我的面吹牛哩陕,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播赫舒,決...
    沈念sama閱讀 40,063評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼萌踱,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了号阿?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,917評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤鸳粉,失蹤者是張志新(化名)和其女友劉穎扔涧,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體届谈,經(jīng)...
    沈念sama閱讀 45,329評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡枯夜,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了艰山。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片湖雹。...
    茶點(diǎn)故事閱讀 39,722評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖曙搬,靈堂內(nèi)的尸體忽然破棺而出摔吏,到底是詐尸還是另有隱情,我是刑警寧澤纵装,帶...
    沈念sama閱讀 35,425評(píng)論 5 343
  • 正文 年R本政府宣布征讲,位于F島的核電站,受9級(jí)特大地震影響橡娄,放射性物質(zhì)發(fā)生泄漏诗箍。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評(píng)論 3 326
  • 文/蒙蒙 一挽唉、第九天 我趴在偏房一處隱蔽的房頂上張望滤祖。 院中可真熱鬧,春花似錦瓶籽、人聲如沸匠童。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,671評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)俏让。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間首昔,已是汗流浹背寡喝。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,825評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留勒奇,地道東北人预鬓。 一個(gè)月前我還...
    沈念sama閱讀 47,729評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像赊颠,于是被迫代替她去往敵國(guó)和親格二。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評(píng)論 2 353

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