Android事件機(jī)制(一)

1、重要的方法

// ViewGroup:3個(gè)方法
public boolean dispatchTouchEvent(MotionEvent event)
public boolean onInterceptTouchEvent(MotionEvent event)
public boolean onTouchEvent(MotionEvent event)
// View:2個(gè)方法
public boolean dispatchTouchEvent(MotionEvent event)
public boolean onTouchEvent(MotionEvent event)

有觸摸,就有dispatchTouchEvent方法的調(diào)用。
初始情況下,這些方法返回值都是false。

2、舉例:
定義兩個(gè)ViewGroup(MyViewGroupA, MyViewGroupB),一個(gè)View(MyView)
對(duì)于ViewGroup,重寫3個(gè)方法:

MyViewGroupA

MyViewGroupB同MyViewGroupA.
對(duì)于View,重寫2個(gè)方法:

MyView

布局文件:

activity_motion_event.xml

Activity中不作處理:

MotionEventActivity

(1)先來(lái)看一下事件的傳遞:
運(yùn)行程序,點(diǎn)擊MyView,查看日志如下:

Log_1

此時(shí)可以看出,事件分發(fā)的傳遞順序:

MyViewGroupA - MyViewGroupB - MyView

事件處理的傳遞順序:

MyView - MyViewGroupB - MyViewGroupA

(2)再來(lái)看一下事件的攔截:
下面稍微改動(dòng)一下,MyViewGroupB使用onInterceptTouchEvent將事件攔截,即在MyViewGroupB中onInterceptTouchEvent方法返回true,打印日志如下:

Log_2

可以看出,觸摸事件沒有傳遞給MyView。
如果使MyViewGroupA的onInterceptTouchEvent方法返回true,可以猜測(cè)事件只會(huì)在MyViewGroupA中進(jìn)行處理,不會(huì)傳遞給MyViewGroupB,更不會(huì)傳遞給MyView。
運(yùn)行程序,打印日志如下:

Log_3

果然如此。

(3)最后看一下事件的處理:
事件處理在onTouchEvent方法中進(jìn)行。
首先還原上面兩處onInterceptTouchEvent的返回值,此時(shí)運(yùn)行打印的日志與Log_1相同,如下:

Log_4

先將MyView的onTouchEvent返回值改為true,運(yùn)行程序看一下打印的日志:

Log_5

此時(shí)打印出的日志除了記錄下了MotionEvent.ACTION_DOWN,還記錄了MotionEvent.ACTION_MOVEMotionEvent.ACTION_UP,并且MotionEvent.ACTION_MOVE還有可能隨著觸摸重復(fù)出現(xiàn),但這些在這里暫且不討論,只看事件處理的傳播。
觀察黃框圈中的部分(和下面的兩部分相比,只是action的類型不同),事件傳遞跟以前一樣,但是事件處理到MyView就結(jié)束了,不會(huì)再傳給MyViewGroupA和MyViewGroupB。

如果把MyView的onTouchEvent返回值改回原來(lái)的值,將MyViewGroupB的onTouchEvent返回值改為true,再來(lái)看一下打印的日志:

Log_6

同樣,我們只關(guān)注黃框圈中的部分(剩下的部分后面講),事件傳遞跟以前一樣,但是事件處理到MyViewGroupB就結(jié)束了,不會(huì)再傳給MyViewGroupA。
其實(shí)黃框下面的日志也可以看出,事件同樣處理到MyViewGroupB就結(jié)束了。

(4)好了,現(xiàn)在我們把MyViewGroupB的onTouchEvent返回值也改回去,此時(shí)的日志情況:

Log_7

接下來(lái)看一下MyView分別setOnTouchListenersetOnClickListener的情況。

setOnTouchListener

先讓OnTouchListener.onTouch方法返回false,代碼如下:

Log_8

日志如下:

Log_9

可以看到,事件傳遞和事件處理的順序跟原來(lái)一樣,但是MyView事件處理中,先調(diào)用了OnTouchListener.onTouch,之后才調(diào)用onTouchEvent。

接著讓OnTouchListener.onTouch方法返回true,代碼如下:

Log_10

日志如下:

Log_11

可以看到,事件傳遞的順序跟原來(lái)一樣,但是MyView的事件處理,只調(diào)用了OnTouchListener.onTouch,沒有調(diào)用onTouchEvent,也沒有傳給MyViewGroupA和MyViewGroupB。

這種情況的出現(xiàn)就跟dispatchTouchEvent方法有關(guān)了,需要涉及到源碼分析,這篇文章只是先描述一下出現(xiàn)的情況,所以就先簡(jiǎn)單提一下,具體下篇再分析。
當(dāng)OnTouchListener.onTouch返回true時(shí),就會(huì)導(dǎo)致dispatchTouchEvent返回true,此時(shí)就不再繼續(xù)向下執(zhí)行了,因此onTouchEvent也就不會(huì)被調(diào)用了,事件處理也到此結(jié)束,不會(huì)繼續(xù)傳遞。

setOnClickListener

上面列舉的幾個(gè)情況,有個(gè)隱含的前提,就是MyViewGroupA、MyViewGroupB、MyView都是默認(rèn)不可點(diǎn)擊的;
現(xiàn)在我們把MyView設(shè)置成可點(diǎn)擊的,比如在布局中設(shè)置android:clickable="true"或代碼中setOnClickListener,同時(shí)將OnTouchListener.onTouch的返回值改為false,代碼如圖所示:

Log_12

再次點(diǎn)擊MyView,查看日志如下:

Log_13

事件傳遞順序不變,但是事件處理沒有再傳給MyViewGroupA和MyViewGroupB。

接著還是讓OnTouchListener.onTouch方法返回true,再看一下打印的日志:

Log_14
Log_15

可以看到,OnClickListener.onClick沒有執(zhí)行。
因?yàn)?code>OnClickListener.onClick這個(gè)方法是在onTouchEventperformClick方法中調(diào)用的,OnTouchListener.onTouch方法返回true之后,onTouchEvent就沒有執(zhí)行,導(dǎo)致OnClickListener.onClick沒有調(diào)用,也就不會(huì)有相應(yīng)的日志信息了。
由此可以看出,OnTouchListener.onTouch優(yōu)先OnClickListener.onClick調(diào)用。

3、這篇文章從代碼演示的角度整理了一下我對(duì)Android事件傳遞的理解,最后曝照一張,權(quán)當(dāng)總結(jié),字不好,看官勿笑

參考:
Android事件分發(fā)機(jī)制完全解析,帶你從源碼的角度徹底理解(上)
Android事件分發(fā)機(jī)制完全解析,帶你從源碼的角度徹底理解(下)
Android事件分發(fā)機(jī)制詳解
Android群英傳-事件攔截機(jī)制分析

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容