聊聊Android中的事件分發(fā)機制

前言

  • 聊之前先簡單看下Activity,PhoneWindow,DecorView的關(guān)系,這樣才更好理解事件的傳遞


    aa.png

    圖片1.png

    捋一下:
    在Activity創(chuàng)建完成的時候,會調(diào)用Activity的attach方法來創(chuàng)建一個PhoneWindow對象設(shè)置給Activity,而PhoneWindow對象內(nèi)部又創(chuàng)建一個DecorView柱告,DecorView繼承自FrameLayout,所以說我們通過setContentView設(shè)置的布局文件其實是被添加到DecorView中成為DecorView的一個子View,也就是說最外層的ViewGroup就是DecorView,DecorView這個類是在PhoneWindow中的內(nèi)部類!
    可以看下上面圖!加深理解!
    感興趣的可以去看下源碼,此處就不詳細的說了,只是簡單的介紹一下!

OK現(xiàn)在我們可以聊聊事件傳遞了

  • 我們都知道android中的事件分發(fā)是從Activity開始分發(fā)的,那么是誰傳遞給Activity的,從Activity又是怎么分發(fā)的呢?
  • 假如我們點擊屏幕中的一個按鈕到底是怎么執(zhí)行的匪燕?

再來一張圖:

  • 圖片2.png

我們看著上面的圖跟著源碼走一走
WindowManagerService接受到Input事件然后發(fā)送給ViewRootImpl,會執(zhí)行到ViewRootImpl的dispatchInputEvent的方法

    public void dispatchInputEvent(InputEvent event) {
        dispatchInputEvent(event, null);
    }

長話短說:
在ViewRootImpl中最終會調(diào)用DecorView的dispathPointerEvent方法
想看dispathPointerEvent方法源碼的應(yīng)該去View中,因為DecorView類中并沒有dispathPointerEvent方法的源碼,這個方法是存在于View中的
DecorView.java:

    public final boolean dispatchPointerEvent(MotionEvent event) {
        if (event.isTouchEvent()) {
            return dispatchTouchEvent(event);
        } else {
            return dispatchGenericMotionEvent(event);
        }
    }

在dispatchPointerEvent中判斷是否是觸摸事件,如果是自然就到了dispatchTouchEvent方法中

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        final Window.Callback cb = mWindow.getCallback();
        return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
                ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
    }

上面方法中的cb對象是什么?在Activity的attach方法中創(chuàng)建PhoneWindow的時候又這樣一句代碼
mWindow.setCallback(this);
這個this就是Activity,因為Activity實現(xiàn)了Window.CallBack的接口,所以自然就將實例傳入的PhoneWindow中

        mWindow = new PhoneWindow(this, window);
        mWindow.setWindowControllerCallback(this);
        mWindow.setCallback(this);

所以在DecorView的dispatchTouchEvent中會調(diào)用執(zhí)行到Activity的dispatch方法中!
所以在觸摸事件被傳到了Activity的地方,看下上面的圖左邊部分捋一下大概思路就可以喧笔!

接下來就是Activity的事件分發(fā)了

真正的事件分發(fā)是從Activity開始的,上面的只是觸摸事件傳到Activity
現(xiàn)在開始由Activity進行分發(fā)處理
從Activity的dispatchTouchEvent方法開始

  • Activity.java
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }

我們看這行代碼:
getWindow().superDispatchTouchEvent(ev)
這里的getWindow()返回的就是在attach中設(shè)置給Activity的PhoneWindow對象
所以就是調(diào)用了PhoneWindow對象的superDispatchTouchEvent的方法

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

在PhoneWindow中的superDispatchTouchEvent方法又調(diào)用了mDecor對象的superDispatchTouchEvent方法
這個mDecor自然就是DecorView了,我們前面也說了在PhoneWindow中會創(chuàng)建一個DecorView的對象!

DecorView.java

    public boolean superDispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
    }

在DecorView的superDispatchTouchEvent方法中調(diào)用了父類的dispatchTouchEvent
注意:是調(diào)用了父類的dispatchTouchEvent帽驯,而不是自己實現(xiàn)的dispatchTouchEvent方法
DecorView的父類是FrameLayout,而FrameLayout中并沒有重寫dispatchTouchEvent方法
應(yīng)為FrameLayout繼承子ViewGroup,所以這里調(diào)用的super.dispatchTouchEvent方法就是ViewGroup的dispatchTouchEvent方法

下來就是ViewGroup的事件分發(fā)方法了

ViewGroup中dispatchTouchEvent的源碼跟以前的相比,加了一些東西,比較復(fù)雜,但是原理都一樣的
代碼就不貼了,所以挑重點的說
----->哈哈

bq1.png

ViewGroup:
兩個主要方法:
onInterceptTouchEvent();事件攔截,即攔截本次事件,不在傳遞
dispatchTouchEvent();事件分發(fā)

首先在dispatchTouchEvent中會判斷是否攔截此次事件,默認為不攔截!
如果不攔截又不是取消事件,則進行遍歷子view尋找符合消費此次事件的view书闸,如果找到符合的則調(diào)用該view的dispatchTouchEvent方法
假如在這個ViewGroup下沒有任何的子View,則調(diào)用此ViewGroup的super.dispatchTouchEvent()方法,
ViewGroup繼承自View,所以就是調(diào)用View的dispatchTouchEvent方法,意思就是把當(dāng)前的ViewGroup當(dāng)作View去處理當(dāng)前的觸摸事件

如果所有的ViewGroup及View都沒有消費此次事件,則進行事件回傳,最終由Activity的onTouchEvent方法進行處理
Activity的dispatch方法
        if (getWindow().superDispatchTouchEvent(ev)) {
            // 這里表示事件被消費,直接返回
            return true;
        }
        // 事件沒有被消費,由activity的onTouchEvent處理
        return onTouchEvent(ev);
事件最終會分發(fā)到我們點擊的那個View上面
那么我們就看看View的事件分發(fā)方法

因為Button繼承自View,所以現(xiàn)在我們看View的dispatchTouchEvent方法,刪除部分代碼,直說關(guān)鍵步驟的
我在代碼中用注釋解釋
View.java

public boolean dispatchTouchEvent(MotionEvent event) {
        .....
        .....
        boolean result = false;

        if (onFilterTouchEventForSecurity(event)) {
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
            ListenerInfo li = mListenerInfo;
            // 這里判斷了li和li.mOnTouchListener是否為空
            // 這里的li中存儲了我們設(shè)置給View的setOnTouchListener事件
            // 也就是我們實現(xiàn)的OnTouchListener,這里執(zhí)行了我們實現(xiàn)的onTouch方法
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }

            // 如果上面的onTouch方法返回false,或者根本沒設(shè)置onTouchlistener事件就會執(zhí)行下面的操作
            // 如果result是true,則不執(zhí)行onTouchEvent方法
            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }
        .....
        .....
        // 最后返回result
        return result;
}

我們看上面的代碼先判斷OnTouchListener是否為null,然后執(zhí)行OnTouchListener的onTouch方法,
如果onTouchListener為null,或者onTouch方法返回false就會執(zhí)行下面的onTouchEvent方法
在onTouchEvent方法中的MotionEvent.ACTION_UP事件中會執(zhí)行performClick();
performClick();也就是我們設(shè)置的setOnClickListener點擊事件
View.java

public boolean performClick() {
        final boolean result;
        final ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            li.mOnClickListener.onClick(this);
            result = true;
        } else {
            result = false;
        }

        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
        return result;
    }

在performClick方法中的li.mOnClickListener其實就是我們設(shè)置的setOnClickListner
和mOnTouchListener一樣都被保存到ListenerInfo類中
在上面代碼中如果我們設(shè)置了OnClickListener,并且用戶進行了點擊操作,那么onClick就會被執(zhí)行了

來張圖


圖片3.png

到此結(jié)束!,如由錯誤的地方,歡迎糾正!

Paste_Image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末尼变,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子浆劲,更是在濱河造成了極大的恐慌嫌术,老刑警劉巖哀澈,帶你破解...
    沈念sama閱讀 222,378評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異蛉威,居然都是意外死亡日丹,警方通過查閱死者的電腦和手機走哺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,970評論 3 399
  • 文/潘曉璐 我一進店門蚯嫌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人丙躏,你說我怎么就攤上這事择示。” “怎么了晒旅?”我有些...
    開封第一講書人閱讀 168,983評論 0 362
  • 文/不壞的土叔 我叫張陵栅盲,是天一觀的道長。 經(jīng)常有香客問我废恋,道長谈秫,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,938評論 1 299
  • 正文 為了忘掉前任鱼鼓,我火速辦了婚禮拟烫,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘迄本。我一直安慰自己硕淑,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,955評論 6 398
  • 文/花漫 我一把揭開白布嘉赎。 她就那樣靜靜地躺著置媳,像睡著了一般。 火紅的嫁衣襯著肌膚如雪公条。 梳的紋絲不亂的頭發(fā)上拇囊,一...
    開封第一講書人閱讀 52,549評論 1 312
  • 那天,我揣著相機與錄音靶橱,去河邊找鬼寥袭。 笑死,一個胖子當(dāng)著我的面吹牛抓韩,可吹牛的內(nèi)容都是我干的纠永。 我是一名探鬼主播,決...
    沈念sama閱讀 41,063評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼谒拴,長吁一口氣:“原來是場噩夢啊……” “哼尝江!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起英上,我...
    開封第一講書人閱讀 39,991評論 0 277
  • 序言:老撾萬榮一對情侶失蹤炭序,失蹤者是張志新(化名)和其女友劉穎啤覆,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體惭聂,經(jīng)...
    沈念sama閱讀 46,522評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡窗声,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,604評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了辜纲。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片笨觅。...
    茶點故事閱讀 40,742評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖耕腾,靈堂內(nèi)的尸體忽然破棺而出见剩,到底是詐尸還是另有隱情,我是刑警寧澤扫俺,帶...
    沈念sama閱讀 36,413評論 5 351
  • 正文 年R本政府宣布苍苞,位于F島的核電站,受9級特大地震影響狼纬,放射性物質(zhì)發(fā)生泄漏羹呵。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,094評論 3 335
  • 文/蒙蒙 一疗琉、第九天 我趴在偏房一處隱蔽的房頂上張望冈欢。 院中可真熱鬧,春花似錦没炒、人聲如沸涛癌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,572評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽拳话。三九已至,卻和暖如春种吸,著一層夾襖步出監(jiān)牢的瞬間弃衍,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,671評論 1 274
  • 我被黑心中介騙來泰國打工坚俗, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留镜盯,地道東北人。 一個月前我還...
    沈念sama閱讀 49,159評論 3 378
  • 正文 我出身青樓猖败,卻偏偏與公主長得像速缆,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子恩闻,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,747評論 2 361

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