深入淺出Android事件分發(fā)機制:最全面最易懂:基礎(chǔ)篇(一)

歡迎關(guān)注程序引力

如何提升安卓水平巡社?安卓開發(fā)者必須了解的事件分發(fā)機制。
最全面手趣、最易懂的形式來講解Android事件分發(fā)機制晌该。

若有錯漏,煩請斧正绿渣。轉(zhuǎn)載請注明出處朝群。

0. 前言

鑒于安卓分發(fā)機制較為復(fù)雜,故分為多個層次進行講解中符,分別為基礎(chǔ)篇姜胖、實踐篇與高級篇。

1. 內(nèi)容簡介

本文內(nèi)容為(一)基礎(chǔ)篇饲鄙,本篇主要對事件分發(fā)中的基本概念做了介紹凄诞。同時,介紹了負責(zé)參與分發(fā)事件的主要方法忍级。從這些方法的核心邏輯中帆谍,總結(jié)事件分發(fā)的規(guī)律。避免了許多文章直接給初學(xué)者講解源碼所帶來的困惑轴咱。

本文深入淺出汛蝙,通過閱讀本文,可以幫助開發(fā)者對安卓事件分發(fā)機制有一個整體的了解朴肺,并且能夠幫助開發(fā)者快速解決一些常見的實際問題窖剑,從而實現(xiàn)快速開發(fā)。

2. 被分發(fā)的對象

被分發(fā)的對象是那些戈稿?被分發(fā)的對象是用戶觸摸屏幕而產(chǎn)生的點擊事件西土,事件主要包括:按下、滑動鞍盗、抬起與取消需了。這些事件被封裝成MotionEvent對象跳昼。該對象中的主要事件如下表所示:

事件 觸發(fā)場景 單次事件流中觸發(fā)的次數(shù)
MotionEvent.ACTION_DOWN 在屏幕按下時 1次
MotionEvent.ACTION_MOVE 在屏幕上滑動時 0次或多次
MotionEvent.ACTION_UP 在屏幕抬起時 0次或1次
MotionEvent.ACTION_CANCLE 滑動超出控件邊界時 0次或1次

按下、滑動肋乍、抬起鹅颊、取消這幾種事件組成了一個事件流。事件流以按下為開始墓造,中間可能有若干次滑動挪略,以抬起或取消作為結(jié)束。

在安卓對事件分發(fā)的處理過程中滔岳,主要是對按下事件作分發(fā),進而找到能夠處理按下事件的組件挽牢。對于事件流中后續(xù)的事件(如滑動谱煤、抬起等),則直接分發(fā)給能夠處理按下事件的組件禽拔。故本文討論的內(nèi)容則是主要針對按下事件的刘离。

3. 分發(fā)事件的組件

分發(fā)事件的組件,也稱為分發(fā)事件者睹栖,包括Activity硫惕、View和ViewGroup。它們?nèi)叩囊话憬Y(jié)構(gòu)為:

事件分發(fā)者結(jié)構(gòu)

從上圖中可以看出野来,Activity包括了ViewGroup恼除,ViewGroup又可以包含多個View。

組件 特點 舉例
Activity 安卓視圖類 如MainActivity
ViewGroup View的容器曼氛,可以包含若干View 各種布局類
View UI類組件的基類 如按鈕豁辉、文本框

4. 分發(fā)的核心方法

負責(zé)對事件進行分發(fā)的方法主要有三個,分別是:dispatchTouchEvent()舀患、onTouchEvent()和onInterceptTouchEvent()徽级。它們并不存在于所有負責(zé)分發(fā)的組件中,其具體情況總結(jié)于下面的表格中:

組件 dispatchTouchEvent onTouchEvent onInterceptTouchEvent
Activity 存在 存在 不存在
ViewGroup 存在 存在 存在
View 存在 存在 不存在

從表格中看聊浅,dispatchTouchEvent(),onTouchEvent()方法存在于上文的三個組件中餐抢。而onInterceptTouchEvent()為ViewGroup獨有。這些方法的具體作用在下文作介紹低匙。

ViewGroup類中旷痕,實際是沒有onTouchEvent()方法的,但是由于ViewGroup繼承自View顽冶,而View擁有onTouchEvent()方法苦蒿,故ViewGroup的對象也是可以調(diào)用onTouchEvent()方法的。故在表格中表明ViewGroup中存在onTouchEvent()方法的渗稍。

5. 事件分發(fā)過程

這一小節(jié)是本文的核心內(nèi)容佩迟,會從整體上對事件的分發(fā)過程作介紹团滥。

對于事件分發(fā)過程從,筆者認為網(wǎng)上的一些教程中的觀點是有誤的报强。

  • 網(wǎng)上部分教程認為事件是從內(nèi)部(如Button)開始分發(fā)的灸姊,這是有誤的。
  • 網(wǎng)上部分教程常使用’向上‘秉溉、’向下‘傳播等描述力惯,但又未對‘何為上’、‘何為下’作解釋召嘶。
  • 網(wǎng)上部分教程將Java的子類對象調(diào)用父類方法(向上轉(zhuǎn)型)的過程也稱為‘向上’傳播父晶,即將事件在組件之間的傳播與程序語言多態(tài)特性混為一談,讓初學(xué)者費解弄跌。
  • 子類在覆寫的方法中調(diào)用父類的同名方法甲喝,被稱為’向上傳播‘,這也是不對的铛只。

為此在介紹分發(fā)過程之前埠胖,先對一些概念作定義:

  • 向下傳播:Activity包括Layout,事件從Activity向Layout傳播被稱作’向下傳播‘淳玩。Layout包含若干View直撤,事件從Layout向其子View傳播,也被稱為’向下傳播‘蜕着。
  • 向上傳播:與’向下傳播‘相反谋竖。

’向上轉(zhuǎn)型‘不能稱為傳播,即子類對象調(diào)用父類方法承匣,或在覆寫的方法中調(diào)用父類方法圈盔,都不能稱為傳播。不能將面向?qū)ο蟪绦蛘Z言中的概念與布局層次中的上下傳播混為一談悄雅。

分發(fā)方法dispatchTouchEvent()

從方法的名稱中可以看出該方法主要是負責(zé)分發(fā)驱敲,是安卓事件分發(fā)過程中的核心。事件是如何傳遞的宽闲,主要就是看該方法众眨,理解了這個方法,也就理解了安卓事件分發(fā)機制容诬。

在了解該方法的核心機制之前娩梨,需要知道一個結(jié)論:

  • 如果某個組件的該方法返回TRUE,則表示該組件已經(jīng)對事件進行了處理,不用繼續(xù)調(diào)用其余組件的分發(fā)方法览徒,即停止分發(fā)狈定。
  • 如果某個組件的該方法返回FALSE,則表示該組件不能對該事件進行處理,需要按照規(guī)則繼續(xù)分發(fā)事件。在不復(fù)寫該方法的情況下纽什,除了一些特殊的組件措嵌,其余組件都是默認返回False的。后續(xù)有例子說明芦缰。

為何返回TRUE就不用繼續(xù)分發(fā)企巢,而返回FALSE就停止分發(fā)呢?為了解決這個疑問让蕾,需要看一看該方法的具體分發(fā)邏輯浪规。為了便于理解,下面對dispatchTouchEvent方法進行簡化探孝,只保留最核心的邏輯笋婿。

Activity的dispatchTouchEvent方法

// Activity中該方法的核心部分偽代碼
public boolean dispatchTouchEvent(MotionEvent ev) {
    if (child.dispatchTouchEvent(ev)) {
        return true;    //如果子View消費了該事件,則返回TRUE,讓調(diào)用者知道該事件已被消費
    } else {
        return onTouchEvent(ev);    //如果子View沒有消費該事件顿颅,則調(diào)用自身的onTouchEvent嘗試處理缸濒。
    }
}

首先,從核心邏輯中看出元镀,當事件傳遞給Activity后,它先將事件分發(fā)給子View處理霎桅。

  • 如果經(jīng)過子View層層傳遞或處理后栖疑,該事件被消費了(即返回了TRUE),則Activity的分發(fā)方法也返回TRUE滔驶,同樣也表示該事件已經(jīng)被消費了遇革。
  • 如果經(jīng)過子View層層傳遞或處理后,該事件沒有被消費(即返回了FALSE)揭糕,則Activity的分發(fā)方法就不會返回TRUE了萝快,而是調(diào)用onTouchEvent()去處理,看其實際的處理情況著角。
    • 如果onTouchEvent()消費了事件揪漩,那依然能返回TRUE(表示已消費事件),這個TRUE作為dispatchTouchEvent的返回值吏口,讓調(diào)用它的對象知道該Activity已經(jīng)消費了事件奄容。
    • 如果onTouchEvent()沒有消費該事件,那就返回FALSE(表示未消費事件)产徊,這個FALSE作為dispatchTouchEvent的返回值昂勒,讓調(diào)用它的對象知道該Activity沒有消費事件,需要繼續(xù)處理舟铜。

ViewGroup的dispatchTouchEvent方法

// ViewGroup中該方法的核心部分偽代碼
public boolean dispatchTouchEvent(MotionEvent ev) {
    if (!onInterceptTouchEvent(ev)) {
        return child.dispatchTouchEvent(ev);    //不攔截戈盈,則傳給子View進行分發(fā)處理
    } else {
        return onTouchEvent(ev);    //攔截事件,交由自身對象的onTouchEvent方法處理
    }
}

ViewGroup的該方法與Activity的類似谆刨,只是新添了一個onInterceptTouchEvent()方法塘娶。當事件傳入時归斤,首先會調(diào)用onInterceptTouchEvent()。

  • 如果該方法返回了FALSE(表示不攔截)血柳,則交給子View去調(diào)用dispatchTouchEvent()方法
  • 如果該方法返回了TRUE(表示攔截)官册,則直接交給該ViewGroup對象的onTouchEvent(ev)方法處理,具體是否能處理以onTouchEvent()的實際情況為準难捌。

實際上膝宁,在onInterceptTouchEvent()返回TURE表示攔截時,實際調(diào)用的是super.dispatchTouchEvent()方法根吁,即View的該方法员淫,進而由該方法調(diào)用onTouchEvent().

View的dispatchTouchEvent方法

// View中該方法的核心部分偽代碼
public boolean dispatchTouchEvent(MotionEvent ev) {
    //如果該對象的監(jiān)聽成員變量不為空,則會調(diào)用其onTouch方法击敌,
    if (mOnTouchListener != null && mOnTouchListener.onTouch(this, event)) {
        return true;    //若onTouch方法返回TRUE介返,則表示消費了該事件,則dispachtouTouchEvent返回TRUE沃斤,讓其調(diào)用者知道該事件已被消費圣蝎。
    }
    return onTouchEvent(ev);    //若監(jiān)聽成員為空或onTouch沒有消費該事件,則調(diào)用對象自身的onTouchEvent方法處理衡瓶。
}

從該方法的核心邏輯中可以看到徘公,事件傳遞進來后,首先會對mOnTouchListener判空哮针,如果之前Set了Listener关面,則會調(diào)用其onTouch方法。

  • 若onTouch方法返回TRUE十厢,則dispatchTouchEvent也會返回TRUE等太,表示消費該事件。
  • 若onTouch方法返回FALSE蛮放,或者mOnTouchListener本來就是空缩抡,則調(diào)用自身的onTouchEvent()來處理,是否消費事件包颁,可以由其返回值判斷缝其。

實際上,在View的onTouchEvent()方法中徘六,如果設(shè)置了onClickListener監(jiān)聽對象内边,則會調(diào)用其onClick()方法。

在同時設(shè)置了onTouchListener與onClickListener對象的情況下待锈,正是由于View的dispacthTouchEvent方法會先調(diào)用mOnTouchListener的onTouch,才會調(diào)用onTouchEvent()方法漠其,所以onTouchListener對象的onTouch方法是優(yōu)先于onClickListener對象的onClick方法調(diào)用的。這里只簡單描述結(jié)論,具體源碼請查看本文對應(yīng)的高級篇內(nèi)容和屎。

小節(jié):dispatchTouchEvent方法

回顧上面Activity拴驮、ViewGroup和View中的dispatchTouchEvent方法,它們大體都可以分為兩部分柴信,前一部分是交由子View的dispatchTouchEvent方法或onTouch方法進行處理套啤,后一部分是交給自身的onTouchEvent方法處理。這樣理解的話随常,就非常便于記憶了潜沦。

為了便于記憶和理解,可以將各組件的dispatchTouchEvent方法分為兩部分:

  • 子View的dispatchTouchEvent 或 onTouch方法
  • 自身的onTouchEvent方法

這個結(jié)構(gòu)有點類似于遞歸的過程绪氛,就是組件的dispatchTouchEvent會自用子組件的同名方法唆鸡,子組件一樣會調(diào)用子子組件的同名方法,直到遞歸到底枣察,然后在從遞歸底部返回上層争占,直到返回到最上層,整個過程結(jié)束序目”酆郏或者在這個過程中,事件傳遞到某個子View猿涨,該子View決定處理該事件握童,則事件交給其自身的onTouchEvent方法處理,如果onTouchEvent方法處理不了嘿辟,再交由父組件的同名方法處理舆瘪,直到向上傳遞到頂層結(jié)束片效。

于是红伦,就有了很多教程里的U型圖。

安卓分發(fā)事件U型圖

從U型圖中可以發(fā)現(xiàn)淀衣,其實安卓事件分發(fā)的主體思路非常簡單昙读,即由父組件不斷向子組件分發(fā),若子組件能夠處理膨桥,則立刻返回蛮浑。若子組件都不處理,那傳遞到底層的子組件只嚣,再返回回來沮稚。這個過程類似上面說的遞歸的過程。

這里對這個U型圖做一下說明册舞,先看圖中左上角蕴掏,事件傳到Activity,首先調(diào)用其dispatchTouchEvent方法,其會傳遞給子View處理盛杰,該子View(在圖中是ViewGroup)會調(diào)用其dispatchTouchEvent方法挽荡,如果該方法被覆寫直接返回TRUE,則立即返回Activity,表示已經(jīng)消費事件即供。如果該方法沒有被覆寫或調(diào)用了super的同名方法定拟,則會調(diào)用onInterceptTouchEvent方法,如果該方法返回TRUE攔截事件逗嫡,則交給自身的onTouchEvent處理青自,如果該方法返回FALSE不攔截,則繼續(xù)傳給子子View(圖中是View)的dispatchTouchEvent方法處理祸穷。此時性穿,再看看這個U型圖,該遞歸調(diào)用已經(jīng)到底了雷滚,若在該方法中的onTouchListener方法不處理需曾,則調(diào)用自身的onTouchEvent處理。若還是處理不了祈远,則從遞歸底部向上返回呆万,依次調(diào)用ViewGroup的、Activity的onTouchEvent方法车份。

實際上谋减,用這個U型圖來描述安卓的事件分發(fā)機制并不一定準確,因為同一對象的dispatchTouchEvent方法實際是包含了另外幾個方法的(Activity與View只包含onTouchEvent),但是在這個圖中扫沼,卻是將幾個方法分別畫在不同的框中出爹。所以通過該U型圖來理解事件分發(fā)機智是不準確的。但是對于部分讀者可能會有所幫助缎除。要準確理解事件調(diào)用機制严就,還是應(yīng)該回到上面,查看三個核心方法的核心邏輯器罐,就能夠準確理解梢为。

強調(diào)說明,安卓事件分發(fā)的‘向上’與‘向下‘傳播轰坊,不要與面向?qū)ο蟪绦蛘Z言中基類與子類關(guān)系铸董,或子類向上調(diào)用父類方法等概念搞混淆。對于安卓事件分發(fā)的‘向上’與‘向下‘傳播肴沫,這里的上與下粟害,是指在’遞歸‘調(diào)用過程中的上與下(也體現(xiàn)到U型圖里的上與下)。這個概念颤芬,體現(xiàn)到布局中悲幅,就是外與內(nèi)孽文。即這里所說的事件’向下‘傳播,等同于在布局上夺艰,由外向內(nèi)傳播芋哭,而’向上’傳播,等同于在布局上郁副,由內(nèi)向外傳播减牺。

在面向?qū)ο蟪绦蛘Z言中,對于子類覆蓋父類方法存谎,或子類調(diào)用父類方法拔疚,這些‘上’與‘下’的關(guān)系,在布局層面上并沒有跨越布局層次既荚,不要與事件傳播的方向概念相混淆稚失。

攔截方法onInterceptTouchEvent()

該方法是ViewGroup類對象所獨有的,用于對事件進行提前攔截恰聘。在一般情況下句各,該方法是默認返回FALSE的,即不攔截晴叨。
如果自定義的ViewGroup希望攔截事件凿宾,不希望事件繼續(xù)往子View傳播,可以覆寫該方法兼蕊,返回TRUE初厚,即可阻止向下的傳播過程。

實際上孙技,從上面的核心邏輯的偽代碼中可以看出产禾,在ViewGroup調(diào)用dispatchTouchEvent()后,肯定會調(diào)用該方法牵啦,根據(jù)該方法的返回值來確定如何處理亚情。若該方法返回True,則會將事件攔截掉蕾久,就給自身的onTouchEvent()處理势似。如果返回False,則繼續(xù)傳遞給child執(zhí)行分發(fā)流程拌夏。

處理方法onTouchEvent()

該方法主要對事件進行處理僧著,若返回True表示已經(jīng)處理了事件,若返回False則表示沒有對事件進行處理障簿,需要繼續(xù)傳遞事件盹愚。一般情況下,默認為FALSE站故。在View的onTouchEvent()方法中皆怕,如果設(shè)置了onClickListener監(jiān)聽對象毅舆,則會調(diào)用其onClick()方法。

6. 總結(jié)

本文在介紹了事件分發(fā)基本概念的基礎(chǔ)上愈腾,介紹了負責(zé)參與事件分發(fā)的核心方法憋活,包括dispatchTouchEvent()、onInterceptTouchEvent()與onTouchEvent()方法虱黄。通過偽代碼的形式介紹了這些方法的核心邏輯悦即,重點分析了在Activity、ViewGroup與View中的dispatchTouchEvent()方法橱乱。它們?nèi)咧械脑摲椒ńY(jié)構(gòu)類似辜梳,都是先調(diào)用子View的同名方法或者listener方法,然后再調(diào)用自身的onTouchEvent()方法泳叠。

這些方法在調(diào)用關(guān)系中體現(xiàn)了一個類似‘遞歸’的調(diào)用過程作瞄,通過dispatchTouchEvent()將事件傳遞下去,又通過onTouchEvent()將事件傳遞上來危纫。中間的這一過程可以通過讓onInterceptTouchEvent方法(對于ViewGroup)宗挥,或者另外的負責(zé)分發(fā)的方法返回TRUE,均可以提前終止這一類似’遞歸‘的調(diào)用過程种蝶,進而讓事件的處理符合我們的預(yù)期属韧。

若有錯漏,煩請斧正蛤吓。轉(zhuǎn)載請注明出處宵喂。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市会傲,隨后出現(xiàn)的幾起案子锅棕,更是在濱河造成了極大的恐慌,老刑警劉巖淌山,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件裸燎,死亡現(xiàn)場離奇詭異,居然都是意外死亡泼疑,警方通過查閱死者的電腦和手機德绿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來退渗,“玉大人移稳,你說我怎么就攤上這事』嵊停” “怎么了个粱?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長翻翩。 經(jīng)常有香客問我都许,道長稻薇,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任胶征,我火速辦了婚禮塞椎,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘睛低。我一直安慰自己忱屑,他們只是感情好,可當我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布暇昂。 她就那樣靜靜地躺著莺戒,像睡著了一般。 火紅的嫁衣襯著肌膚如雪急波。 梳的紋絲不亂的頭發(fā)上从铲,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天,我揣著相機與錄音澄暮,去河邊找鬼名段。 笑死,一個胖子當著我的面吹牛泣懊,可吹牛的內(nèi)容都是我干的伸辟。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼馍刮,長吁一口氣:“原來是場噩夢啊……” “哼信夫!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起卡啰,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤静稻,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后匈辱,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體振湾,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年亡脸,在試婚紗的時候發(fā)現(xiàn)自己被綠了押搪。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡浅碾,死狀恐怖大州,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情及穗,我是刑警寧澤摧茴,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布绵载,位于F島的核電站埂陆,受9級特大地震影響苛白,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜焚虱,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一购裙、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧鹃栽,春花似錦躏率、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至丰嘉,卻和暖如春夯到,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背饮亏。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工耍贾, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人路幸。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓荐开,卻偏偏與公主長得像,于是被迫代替她去往敵國和親简肴。 傳聞我的和親對象是個殘疾皇子晃听,可洞房花燭夜當晚...
    茶點故事閱讀 42,762評論 2 345

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