30分鐘搞清楚Android Touch事件分發(fā)機(jī)制
Touch事件分發(fā)中只有兩個(gè)主角:ViewGroup和View。Activity的Touch事件事實(shí)上是調(diào)用它內(nèi)部的ViewGroup的Touch事件,可以直接當(dāng)成ViewGroup處理。
View在ViewGroup內(nèi),ViewGroup也可以在其他ViewGroup內(nèi),這時(shí)候把內(nèi)部的ViewGroup當(dāng)成View來分析。
ViewGroup的相關(guān)事件有三個(gè):onInterceptTouchEvent、dispatchTouchEvent、onTouchEvent。View的相關(guān)事件只有兩個(gè):dispatchTouchEvent、onTouchEvent。
先分析ViewGroup的處理流程:首先得有個(gè)結(jié)構(gòu)模型概念:ViewGroup和View組成了一棵樹形結(jié)構(gòu),最頂層為Activity的ViewGroup,下面有若干的ViewGroup節(jié)點(diǎn),每個(gè)節(jié)點(diǎn)之下又有若干的ViewGroup節(jié)點(diǎn)或者View節(jié)點(diǎn),依次類推。如圖:

當(dāng)一個(gè)Touch事件(觸摸事件為例)到達(dá)根節(jié)點(diǎn),即Acitivty的ViewGroup時(shí),它會(huì)依次下發(fā),下發(fā)的過程是調(diào)用子View(ViewGroup)的dispatchTouchEvent方法實(shí)現(xiàn)的。簡(jiǎn)單來說,就是ViewGroup遍歷它包含著的子View,調(diào)用每個(gè)View的dispatchTouchEvent方法,而當(dāng)子View為ViewGroup時(shí),又會(huì)通過調(diào)用ViwGroup的dispatchTouchEvent方法繼續(xù)調(diào)用其內(nèi)部的View的dispatchTouchEvent方法。上述例子中的消息下發(fā)順序是這樣的:①-②-⑤-⑥-⑦-③-④。dispatchTouchEvent方法只負(fù)責(zé)事件的分發(fā),它擁有boolean類型的返回值,當(dāng)返回為true時(shí),順序下發(fā)會(huì)中斷。在上述例子中如果⑤的dispatchTouchEvent返回結(jié)果為true,那么⑥-⑦-③-④將都接收不到本次Touch事件。來個(gè)簡(jiǎn)單版的代碼加深理解:
/**
* ViewGroup
* @param ev
* @return
*/
public boolean dispatchTouchEvent(MotionEvent ev){
....//其他處理,在此不管
View[] views=getChildView();
for(int i=0;i<views.length;i++){
//判斷下Touch到屏幕上的點(diǎn)在該子View上面
if(...){
if(views[i].dispatchTouchEvent(ev))
return true;
}
}
...//其他處理,在此不管
}
/**
* View
* @param ev
* @return
*/
public boolean dispatchTouchEvent(MotionEvent ev){
....//其他處理,在此不管
return false;
}
在此可以看出,ViewGroup的dispatchTouchEvent是真正在執(zhí)行“分發(fā)”工作,而View的dispatchTouchEvent方法,并不執(zhí)行分發(fā)工作,或者說它分發(fā)的對(duì)象就是自己,決定是否把touch事件交給自己處理,而處理的方法,便是onTouchEvent事件,事實(shí)上子View的dispatchTouchEvent方法真正執(zhí)行的代碼是這樣的
/**
* View
* @param ev
* @return
*/
public boolean dispatchTouchEvent(MotionEvent ev){
....//其他處理,在此不管
return onTouchEvent(event);
}
一般情況下,我們不該在普通View內(nèi)重寫dispatchTouchEvent方法,因?yàn)樗⒉粓?zhí)行分發(fā)邏輯。當(dāng)Touch事件到達(dá)View時(shí),我們?cè)撟龅木褪鞘欠裨趏nTouchEvent事件中處理它。
那么,ViewGroup的onTouchEvent事件是什么時(shí)候處理的呢?當(dāng)ViewGroup所有的子View都返回false時(shí),onTouchEvent事件便會(huì)執(zhí)行。由于ViewGroup是繼承于View的,它其實(shí)也是通過調(diào)用View的dispatchTouchEvent方法來執(zhí)行onTouchEvent事件。
在目前的情況看來,似乎只要我們把所有的onTouchEvent都返回false,就能保證所有的子控件都響應(yīng)本次Touch事件了。但必須要說明的是,這里的Touch事件,只限于Acition_Down事件,即觸摸按下事件,而Aciton_UP和Action_MOVE卻不會(huì)執(zhí)行。事實(shí)上,一次完整的Touch事件,應(yīng)該是由一個(gè)Down、一個(gè)Up和若干個(gè)Move組成的。Down方式通過dispatchTouchEvent分發(fā),分發(fā)的目的是為了找到真正需要處理完整Touch請(qǐng)求的View。當(dāng)某個(gè)View或者ViewGroup的onTouchEvent事件返回true時(shí),便表示它是真正要處理這次請(qǐng)求的View,之后的Aciton_UP和Action_MOVE將由它處理。當(dāng)所有子View的onTouchEvent都返回false時(shí),這次的Touch請(qǐng)求就由根ViewGroup,即Activity自己處理了。
看看改進(jìn)后的ViewGroup的dispatchTouchEvent方法
View mTarget=null;//保存捕獲Touch事件處理的View
public boolean dispatchTouchEvent(MotionEvent ev) {
//....其他處理,在此不管
if(ev.getAction()==KeyEvent.ACTION_DOWN){
//每次Down事件,都置為Null
if(!onInterceptTouchEvent()){
mTarget=null;
View[] views=getChildView();
for(int i=0;i<views.length;i++){
if(views[i].dispatchTouchEvent(ev))
mTarget=views[i];
return true;
}
}
}
//當(dāng)子View沒有捕獲down事件時(shí),ViewGroup自身處理。這里處理的Touch事件包含Down、Up和Move
if(mTarget==null){
return super.dispatchTouchEvent(ev);
}
//...其他處理,在此不管
if(onInterceptTouchEvent()){
//...其他處理,在此不管
}
//這一步在Action_Down中是不會(huì)執(zhí)行到的,只有Move和UP才會(huì)執(zhí)行到。
return mTarget.dispatchTouchEvent(ev);
}
ViewGroup還有個(gè)onInterceptTouchEvent,看名字便知道這是個(gè)攔截事件。這個(gè)攔截事件需要分兩種情況來說明:
1.假如我們?cè)谀硞€(gè)ViewGroup的onInterceptTouchEvent中,將Action為Down的Touch事件返回true,那便表示將該ViewGroup的所有下發(fā)操作攔截掉,這種情況下,mTarget會(huì)一直為null,因?yàn)閙Target是在Down事件中賦值的。由于mTarge為null,該ViewGroup的onTouchEvent事件被執(zhí)行。這種情況下可以把這個(gè)ViewGroup直接當(dāng)成View來對(duì)待。
2.假如我們?cè)谀硞€(gè)ViewGroup的onInterceptTouchEvent中,將Acion為Down的Touch事件都返回false,其他的都返回True,這種情況下,Down事件能正常分發(fā),若子View都返回false,那mTarget還是為空,無影響。若某個(gè)子View返回了true,mTarget被賦值了,在Action_Move和Aciton_UP分發(fā)到該ViewGroup時(shí),便會(huì)給mTarget分發(fā)一個(gè)Action_Delete的MotionEvent,同時(shí)清空mTarget的值,使得接下去的Action_Move(如果上一個(gè)操作不是UP)將由ViewGroup的onTouchEvent處理。
情況一用到的比較多,情況二個(gè)人還未找到使用場(chǎng)景。
從頭到尾總結(jié)一下:
1.Touch事件分發(fā)中只有兩個(gè)主角:ViewGroup和View。ViewGroup包含onInterceptTouchEvent、dispatchTouchEvent、onTouchEvent三個(gè)相關(guān)事件。View包含dispatchTouchEvent、onTouchEvent兩個(gè)相關(guān)事件。其中ViewGroup又繼承于View。
2.ViewGroup和View組成了一個(gè)樹狀結(jié)構(gòu),根節(jié)點(diǎn)為Activity內(nèi)部包含的一個(gè)ViwGroup。
3.觸摸事件由Action_Down、Action_Move、Aciton_UP組成,其中一次完整的觸摸事件中,Down和Up都只有一個(gè),Move有若干個(gè),可以為0個(gè)。
4.當(dāng)Acitivty接收到Touch事件時(shí),將遍歷子View進(jìn)行Down事件的分發(fā)。ViewGroup的遍歷可以看成是遞歸的。分發(fā)的目的是為了找到真正要處理本次完整觸摸事件的View,這個(gè)View會(huì)在onTouchuEvent結(jié)果返回true。
5.當(dāng)某個(gè)子View返回true時(shí),會(huì)中止Down事件的分發(fā),同時(shí)在ViewGroup中記錄該子View。接下去的Move和Up事件將由該子View直接進(jìn)行處理。由于子View是保存在ViewGroup中的,多層ViewGroup的節(jié)點(diǎn)結(jié)構(gòu)時(shí),上級(jí)ViewGroup保存的會(huì)是真實(shí)處理事件的View所在的ViewGroup對(duì)象:如ViewGroup0-ViewGroup1-TextView的結(jié)構(gòu)中,TextView返回了true,它將被保存在ViewGroup1中,而ViewGroup1也會(huì)返回true,被保存在ViewGroup0中。當(dāng)Move和UP事件來時(shí),會(huì)先從ViewGroup0傳遞至ViewGroup1,再由ViewGroup1傳遞至TextView。
6.當(dāng)ViewGroup中所有子View都不捕獲Down事件時(shí),將觸發(fā)ViewGroup自身的onTouch事件。觸發(fā)的方式是調(diào)用super.dispatchTouchEvent函數(shù),即父類View的dispatchTouchEvent方法。在所有子View都不處理的情況下,觸發(fā)Acitivity的onTouchEvent方法。
7.onInterceptTouchEvent有兩個(gè)作用:1.攔截Down事件的分發(fā)。2.中止Up和Move事件向目標(biāo)View傳遞,使得目標(biāo)View所在的ViewGroup捕獲Up和Move事件。
補(bǔ)充:
“觸摸事件由Action_Down、Action_Move、Aciton_UP組成,其中一次完整的觸摸事件中,Down和Up都只有一個(gè),Move有若干個(gè),可以為0個(gè)。”,這里補(bǔ)充下其實(shí)UP事件是可能為0個(gè)的。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家理解Touch事件分發(fā)機(jī)制有所幫助。
- Android View 事件分發(fā)機(jī)制詳解
- Android View事件分發(fā)機(jī)制詳解
- Android事件分發(fā)機(jī)制(上) ViewGroup的事件分發(fā)
- Android View的事件分發(fā)機(jī)制
- 談?wù)剬?duì)Android View事件分發(fā)機(jī)制的理解
- Android事件分發(fā)機(jī)制(下) View的事件處理
- android事件分發(fā)機(jī)制的實(shí)現(xiàn)原理
- Android事件分發(fā)機(jī)制的詳解
- Android從源碼的角度徹底理解事件分發(fā)機(jī)制的解析(上)
- Android從源碼的角度徹底理解事件分發(fā)機(jī)制的解析(下)
相關(guān)文章
Flutter使用RepositoryProvider解決跨組件傳值問題
在實(shí)際開發(fā)過程中,經(jīng)常會(huì)遇到父子組件傳值的情況。本文將利用RepositoryProvider解決跨組件傳值的問題,感興趣的小伙伴可以了解一下2022-04-04
Android使用ViewPager實(shí)現(xiàn)頂部tabbar切換界面
這篇文章主要為大家詳細(xì)介紹了使用ViewPager實(shí)現(xiàn)頂部tabbar切換界面,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-08-08
Android實(shí)現(xiàn)京東App分類頁面效果
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)京東App分類頁面效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-02-02
Android協(xié)程的7個(gè)重要知識(shí)點(diǎn)匯總
在現(xiàn)代Android應(yīng)用開發(fā)中,協(xié)程(Coroutine)已經(jīng)成為一種不可或缺的技術(shù),它不僅簡(jiǎn)化了異步編程,還提供了許多強(qiáng)大的工具和功能,可以在高階場(chǎng)景中發(fā)揮出色的表現(xiàn),本文將深入探討Coroutine重要知識(shí)點(diǎn),幫助開發(fā)者更好地利用Coroutine來構(gòu)建高效的Android應(yīng)用2023-09-09
Android sd卡讀取數(shù)據(jù)庫實(shí)例代碼
這篇文章主要介紹了Android sd卡讀取數(shù)據(jù)庫實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下2017-02-02
Android中Toolbar隨著ScrollView滑動(dòng)透明度漸變效果實(shí)現(xiàn)
這篇文章主要介紹了Android中Toolbar隨著ScrollView滑動(dòng)透明度漸變效果實(shí)現(xiàn),非常不錯(cuò),具有參考借鑒價(jià)值,需要的的朋友參考下2017-01-01
Android使用ViewPager實(shí)現(xiàn)滾動(dòng)廣告
這篇文章主要為大家詳細(xì)介紹了Android使用ViewPager實(shí)現(xiàn)滾動(dòng)廣告,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-11-11
詳解Android中fragment和viewpager的那點(diǎn)事兒
本文主要對(duì)Android中fragment和viewpager進(jìn)行詳細(xì)介紹,具有一定的參考價(jià)值,需要的朋友一起來看下吧2016-12-12
Android IPC進(jìn)程間通信詳解最新AndroidStudio的AIDL操作)
這篇文章主要介紹了Android IPC進(jìn)程間通信的相關(guān)資料,需要的朋友可以參考下2016-09-09
Android實(shí)現(xiàn)九宮格手勢(shì)解鎖
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)九宮格手勢(shì)解鎖的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-07-07

