解析Android點(diǎn)擊事件分發(fā)機(jī)制
開頭說(shuō)說(shuō)初衷
網(wǎng)上關(guān)于點(diǎn)擊事件分發(fā)的文章一搜一大堆,標(biāo)題一看,不是“30分鐘讓你弄明白XXX”就是“這是講解XXX最好的文章”,滿懷憧憬與信心,忍不住興奮的點(diǎn)進(jìn)去一看,發(fā)現(xiàn)不是代碼就全是圖,我基本上看完了所有相關(guān)的文章,結(jié)果硬是看了三個(gè)小時(shí)也沒搞懂。所以最后還是決定自己去試一試,看一看點(diǎn)擊事件分發(fā)到底是怎么個(gè)流程,我寫的肯定不會(huì)比其他文章好多少,但是呢,帶著一個(gè)初學(xué)者的心,去分析這個(gè)東西,自己能弄明白的同時(shí),也讓想學(xué)習(xí)這個(gè)的人看了之后有些許收獲,那就足夠了。
運(yùn)行的環(huán)境
所有的源碼都基于API 26,也就是Android8.0奧利奧,Android Studio 3.0.1,想要自己敲代碼試試的同學(xué)可以參考一下
進(jìn)入正題
分析點(diǎn)擊事件分發(fā)流程,是想弄明白當(dāng)我們用手指去點(diǎn)擊屏幕的時(shí)候,分為三個(gè)動(dòng)作,按下,移動(dòng)和抬起,屏幕上的東西是怎么知道我們點(diǎn)了它的,在這中間到底經(jīng)歷了什么。所以要先來(lái)模擬一下這個(gè)點(diǎn)擊的過程,看看到底調(diào)用了哪些方法。
搭建最簡(jiǎn)單的結(jié)構(gòu)
新建Activity,重寫dispatchTouchEvent和onTouchEvent,前面的方法負(fù)責(zé)點(diǎn)擊事件的分發(fā),后面的方法負(fù)責(zé)點(diǎn)擊事件的消耗,然后打印三種觸摸事件的觸發(fā)
private static final String TAG = MainActivity.class.getSimpleName(); @Override public boolean onTouchEvent(MotionEvent event) { int action = event.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: Log.d(TAG, "Activity onTouchEvent ACTION_DOWN");//按下 break; case MotionEvent.ACTION_MOVE: Log.d(TAG, "Activity onTouchEvent ACTION_MOVE");//移動(dòng) break; case MotionEvent.ACTION_UP: Log.d(TAG, "Activity onTouchEvent ACTION_UP");//抬起 break; } return super.onTouchEvent(event); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { int action = ev.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: Log.d(TAG, "Activity dispatchTouchEvent ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.d(TAG, "Activity dispatchTouchEvent ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.d(TAG, "Activity dispatchTouchEvent ACTION_UP"); break; } return super.dispatchTouchEvent(ev); }
新建一個(gè)類繼承自LinearLayout,同樣也重寫dispatchTouchEvent和onTouchEvent,還有因?yàn)長(zhǎng)inearLayout繼承自ViewGroup,ViewGroup是可以攔截點(diǎn)擊事件的,這個(gè)很好理解,因?yàn)榭丶际欠旁谒锩娴穆?。所以還要重寫onInterceptTouchEvent方法
@Override public boolean onInterceptTouchEvent(MotionEvent ev) { int action = ev.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: Log.d(TAG, "onInterceptTouchEvent ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.d(TAG, "onInterceptTouchEvent ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.d(TAG, "onInterceptTouchEvent ACTION_UP"); break; default: break; } return false; }
(這里有個(gè)重點(diǎn)).
這里有個(gè)需要注意的地方就是Android的控件有些是默認(rèn)可以點(diǎn)擊的(如Button),還有一些是默認(rèn)不可點(diǎn)擊的(如TextView)他們的分發(fā)是有一些不同的,這里我們先看不可點(diǎn)擊的,新建一個(gè)類繼承android.support.v7.widget.AppCompatTextView,兼容的TextView,同時(shí)跟Activity一樣重寫dispatchTouchEvent和onTouchEvent,代碼不貼了,跟上面一樣,它是普通控件,沒有攔截的方法。
開始點(diǎn)擊,移動(dòng)手指后抬起
然后來(lái)點(diǎn)一點(diǎn)屏幕上的控件看看打印的log,dispatchTouchEvent和onTouchEvent都返回默認(rèn)的實(shí)現(xiàn)super,onInterceptTouchEvent默認(rèn)返回false,表示不攔截,默認(rèn)的情況打?。?/p>
1.-------------------------------------- D/MainActivity: Activity dispatchTouchEvent ACTION_DOWN D/MyLayout: dispatchTouchEvent ACTION_DOWN D/MyLayout: onInterceptTouchEvent ACTION_DOWN D/MyTextView: dispatchTouchEvent ACTION_DOWN 2.-------------------------------------- D/MyTextView: onTouchEvent ACTION_DOWN D/MyLayout: onTouchEvent ACTION_DOWN D/MainActivity: Activity onTouchEvent ACTION_DOWN 3.-------------------------------------- D/MainActivity: Activity dispatchTouchEvent ACTION_MOVE D/MainActivity: Activity onTouchEvent ACTION_MOVE D/MainActivity: Activity dispatchTouchEvent ACTION_MOVE D/MainActivity: Activity onTouchEvent ACTION_MOVE D/MainActivity: Activity dispatchTouchEvent ACTION_UP D/MainActivity: Activity onTouchEvent ACTION_UP
這是默認(rèn)的情況,把它分為三個(gè)階段:
1. 事件的分發(fā),從上到下,從Activity到Layout到Text的dispatchTouchEvent結(jié)束
2. 事件的消耗,從下到上,從Text到Layout到Activity的onTouchEvent結(jié)束
3. 這套動(dòng)作的后續(xù)事件交給上個(gè)事件的最后消耗者,不經(jīng)過其他控件的分發(fā)
三個(gè)函數(shù)的其他返回值
dispatchTouchEvent和onTouchEvent都有三種返回情況
- true
- false
- super.dispatchTouchEvent(ev)和super.onTouchEvent(event)
onInterceptTouchEvent有兩種返回,true和false,這樣來(lái)看, 組合起來(lái)真是情況太多了,寫下來(lái)挨個(gè)分析看代碼的話怕是要看暈,所以這里用一張圖來(lái)看看所有的情況:
普通不可點(diǎn)擊View的事件分發(fā)流程
默認(rèn)可點(diǎn)擊控件的事件分發(fā)
比如Button這種默認(rèn)可以點(diǎn)擊的控件,或者設(shè)置android:clickable=”true”的控件,在分發(fā)流程中有一些不同,主要是onTouchEvent的默認(rèn)方法不同,它直接消耗點(diǎn)擊事件,不再往上傳遞。
可點(diǎn)擊View的事件分發(fā)流程
結(jié)語(yǔ)
事件的分發(fā)流程到此就結(jié)束了,目的已經(jīng)達(dá)到了,找到了我們想要點(diǎn)擊的那個(gè)按鈕或者其他控件,總結(jié)下來(lái)就是從Activity經(jīng)過ViewGroup然后到View依次分發(fā),然后又從底向上確認(rèn)自己是否消耗該事件,如果某個(gè)對(duì)象消耗了,動(dòng)作的后續(xù)事件都由他來(lái)處理。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android自定義LocationMarker的實(shí)現(xiàn)詳解
這篇文章主要為大家詳細(xì)介紹一個(gè)比較簡(jiǎn)單的東西:自定義繪制Marker 其實(shí)就是自定義view, 跟軌跡沒太多關(guān)聯(lián),感興趣的小伙伴可以跟隨小編一起了解一下2023-02-02android實(shí)現(xiàn)底部導(dǎo)航欄
這篇文章主要為大家詳細(xì)介紹了android實(shí)現(xiàn)底部導(dǎo)航欄,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-06-06簡(jiǎn)單實(shí)現(xiàn)Android端搜索框示例詳解
這篇文章主要為大家介紹了簡(jiǎn)單實(shí)現(xiàn)Android端搜索框示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11關(guān)于Android發(fā)送短信獲取送達(dá)報(bào)告的問題(推薦)
最近公司開發(fā)一個(gè)項(xiàng)目,要求app能夠發(fā)送短信并獲取送達(dá)報(bào)告。實(shí)現(xiàn)代碼非常簡(jiǎn)單的,下面小編給大家分享關(guān)于Android發(fā)送短信獲取送達(dá)報(bào)告的問題,感興趣的朋友一起看看吧2017-03-03Android通過SharedPreferences實(shí)現(xiàn)自動(dòng)登錄記住用戶名和密碼功能
最近使用SharedPreferences實(shí)現(xiàn)了一個(gè)android自動(dòng)登錄功能,特此分享到腳本之家平臺(tái)供大家參考2017-07-07Android ViewPager與radiogroup實(shí)現(xiàn)關(guān)聯(lián)示例
本篇文章主要介紹了Android ViewPager與radiogroup實(shí)現(xiàn)關(guān)聯(lián)示例,具有一定的參考價(jià)值,有興趣的可以了解一下。2017-03-03Android開發(fā)ListView中下拉刷新上拉加載及帶列的橫向滾動(dòng)實(shí)現(xiàn)方法
這篇文章主要介紹了Android開發(fā)ListView中下拉刷新上拉加載及帶列的橫向滾動(dòng)實(shí)現(xiàn)方法的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-07-07Android中的Dalvik和ART詳解及區(qū)別分析
小編通過這篇文章給大家整理了什么是Dalvik和ART,并進(jìn)行了區(qū)別的分析,下面一起來(lái)看看。2016-07-07Android實(shí)現(xiàn)字幕滾動(dòng)的方法
這篇文章主要介紹了Android實(shí)現(xiàn)字幕滾動(dòng)的方法,很實(shí)用的功能,需要的朋友可以參考下2014-07-07