Android 點擊和滑動事件分發(fā)攔截消費流程的源碼解讀

為了寫這篇文章斥难,我反復(fù)的看了好幾十遍源碼。而且寫的時候時間間隔比較長帘饶,有時候?qū)懼鴮懼约憾蓟靵y了哑诊,又去看一遍源碼去分析,所以可能會重復(fù)的內(nèi)容比較多也會稍微亂一點及刻,不過我相信你跟著源碼和這邊文章一步一步走镀裤,應(yīng)該還是會有收獲的!

本片文章將會介紹缴饭,view事件是怎么傳遞的和分發(fā)的暑劝,以及點擊滑動沖突產(chǎn)生的原因和解決辦法。這些都會通過閱讀源碼解決~

一些基礎(chǔ)的知識

MotionEvent

當(dāng)手指接觸屏幕時颗搂,會先觸發(fā)ActionDown一次担猛,然后會觸發(fā)一次或多次ActionMove,最后觸發(fā)一次ActionUp


image.png

事件分發(fā),攔截丢氢,消費

這是三個方法分別在activity傅联,viewGroup,View中的存在狀況
本片文章也是一直圍繞這三個方法做不同情況的解讀

image.png

簡陋事件分發(fā)圖

這里堆疊的是一個個view疚察,因為view都是一個個堆疊在屏幕上的


image.png

onTouch 和 onClick

首先看一段簡單的代碼蒸走,給button設(shè)置onTouch和onClick事件。我們可以知道貌嫡,
當(dāng)onTouch事件 return false時比驻,onClick 會執(zhí)行
當(dāng)onTouch事件 return true時,onClick 不會執(zhí)行
結(jié)果我們都知道岛抄,也知道是onTouch攔截了才不會執(zhí)行onClick别惦。
但是這兩段簡單的代碼在源碼中是怎么體現(xiàn)的呢?


image.png

因為Button屬于一個View弦撩,所有我們直接進入view的源碼步咪,
看他的dispatchTouchEvent事件分發(fā)的方法,
可以知道第一個if判斷的結(jié)果益楼,是會影響第二個if語句的執(zhí)行的


image.png

首先在第一個if看到了我們的onTouch事件

if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }

我們把if條件里的判斷拆解一下
1.li != null && li.mOnTouchListener != null
首先可以看到 li = mLisenterInfo猾漫,mLisenterInfo是什么呢?
我們回設(shè)置onTouch事件調(diào)用的setOnTouchListener方法感凤,進入查看悯周,可以看到getListenerInfo()

image.png

接著再進入getListenerInfo()查看

image.png

通過這兩幅圖,我們可以知道的是
li = mLisenterInfo != null陪竿,
li.mOnTouchListener !=null(在setOnTouchListener賦值了我們傳入的值)
所以第一個條件是成立的

2.(mViewFlags & ENABLED_MASK) == ENABLED
這個條件不用做過多的解讀禽翼,就是判斷能不能點擊
所以第二個條件是成立的

3.li.mOnTouchListener.onTouch(this, event)
這調(diào)用的方法就是我們給button設(shè)置的onTouch了

image.png

li.mOnTouchListener.onTouch(this, event) return false ---> result = false
                                         return true ---> result = true 


// 第二個if語句
if (!result && onTouchEvent(event)) { }

3.1 假設(shè)我們 return false屠橄,那么第一個if語句就失效了不能進入,result還是初始值fasle
所以我們會執(zhí)行第二個if ---> 執(zhí)行onTouchEvent(event)闰挡,事件消費

image.png

接著進入performClick锐墙,最終可以看到我們的onClick方法的調(diào)用


image.png

3.2 假設(shè)我們 return true,那么第一個if語句就失效了能進入长酗,result被賦值為true
所以我們不會執(zhí)行第二個if ---> 也就不能執(zhí)行事件消費onTouchEvent(event)溪北,也就不能執(zhí)行到onClick方法了

再從事件分發(fā)流程的角度來看

首先進入viewGroup#dispatchTouchEvent,分析一個正常的Down事件

image.png

注意:if(!canceled && !intercepted){} 這一個if語句里面的代碼塊夺脾,全都是與事件分發(fā)相關(guān)的之拨,可以說只要進入了一個if語句,就會執(zhí)行事件分發(fā)咧叭,接下來也是對if語句里面的代碼進行分析

image.png

題外話
這里我們先來看2注釋中buildTouchDispatchChildList方法 它最后會執(zhí)行到buildOrderedChildList
里面將所有childView按照Z軸的大小蚀乔,從小到大排序,
最小的在最前面菲茬,最大的在后面

image.png

Z軸是怎么來的呢吉挣,我們知道一個layout布局里面的所有View都是一個個疊加上去的就像這樣,所以最底層的Z軸越小婉弹,越排在列表的前面听想。所以遍歷的時候,也是從最后一個拿的


image.png

再看一下4注釋中isTransformedTouchPointInView方法
比如你點擊的是圖中的小圓圈马胧,當(dāng)他遍歷button1時,就會根據(jù)你點擊的坐標(biāo)和button1的區(qū)域做比較衔峰,看看是不是在自己的范圍內(nèi)佩脊,不是的話continue繼續(xù)遍歷下一個childview

image.png

再看一下4注釋下面的方法newTouchTarget = getTouchTarget(child);

image.png

題外話結(jié)束

繼續(xù)往下走的話,會進入到dispatchTransformedTouchEvent

image.png

因為child != null 進入else語句垫卤,接著就會調(diào)用child.dispatchTouchEvent(transformedEvent)
在上述案例中威彰,button就是child,所以事件就這樣分發(fā)給了button去做后續(xù)操作
button中沒有重寫dispatchTouchEvent穴肘,所以就進入View的dispatchTouchEvent
也就是回到我們一開始分析的結(jié)果了

image.png

可以看到歇盼,如果button處理或者消費事件或者在onTouch返回true(也算是處理),就會返回true评抚。進而child.dispatchTouchEvent(transformedEvent)的結(jié)果就是true

image.png

接著回到之前的方法豹缀,if語句會被命中,接著會進入
而if語句最后會break掉慨代,就直接退出了本次for循環(huán)了邢笙,本次事件就被button處理了,也不會被其他或者父view獲取了侍匙,接著就會開始下一個view或者viewgroup了的時間分發(fā)了氮惯。


image.png
image.png

另外這里有兩個紅框的語句,會得到三個條件,特別注意一下
newTouchTarget = mFirstTouchTarget != null
mFirstTouchTarget.next = null
alreadyDispatchedToNewTouchTarget = true

這里把之前的代碼折疊起來了妇汗,為了方便看帘不。
最后的語句因為上面的上面的條件而不會命中,
最后整個if (!canceled && !intercepted) 里的代碼塊就結(jié)束了

image.png

接著往下走杨箭,因為mFirstTouchTarget != null 所以我們來看else語句的代碼塊


image.png
image.png

最后將handled的結(jié)果return寞焙,dispatchTouchEvent方法結(jié)束


image.png

這里Down事件結(jié)束

滑動沖突

上面只是正常的down事件分發(fā)
接下來用這個例子來看一下有沖突的事件分發(fā)來分析一下down和move事件

先介紹一下情況,布局是這樣的:
自定義了一個BadViewpager告唆,里面放著一個listview棺弊,listview里面有很多item,超過一屏幕
所以這里viewpager是父view擒悬,listview就是子view
BadViewpager正常情況下是可以左右滑動的
listview正常情況下是可以上下滑動的


image.png

BadViewpager模她,重寫了onInterceptTouchEvent攔截事件的方法,并且返回true(為了制造沖突)

image.png

如果 BadViewpager的onInterceptTouchEvent返回ture,攔截事件

此時viewpager是可以左右滑動的懂牧,但是listview不能上下滑動

也就是說事件分發(fā)到viewpager就被攔截了侈净,讓我們來看看viewpager是怎么攔截事件,并且自己消費事件的僧凤。
我們從頭開始畜侦,回到ViewGroup的dispatchTouchEvent

注意:現(xiàn)在是ACTION_DOWN事件
因為在viewpager重寫了onInterceptTouchEvent方法,導(dǎo)致intercepted的為true

image.png

我們知道if (!canceled && !intercepted) {}是將事件分發(fā)給子View的關(guān)鍵代碼塊
intercepted是true 就表示if語句不能進入躯保,就不能將事件分發(fā)給子View(也就是listview)

image.png

而且mFirstTouchTarget是在if (!canceled && !intercepted) {}里面賦值的旋膳,所以往下走的話
注意這里傳入的child是null,因為沒有取消事件所以canceled為false

image.png

所以進入dispatchTransformedTouchEvent后我們可以看到
這里直接調(diào)用了自己的dispatchTouchEvent途事,就把事件分發(fā)給自己的

image.png

這里ACTION_DOWN事件就結(jié)束了

如果 BadViewpager的onInterceptTouchEvent返回false,不攔截事件

此時viewpager是不可以左右滑動的验懊,但是listview能上下滑動

ACTION_DOWN事件流程和上面button的事件分發(fā)情況是一樣的,就不分析了尸变。
所以這里是分發(fā)了一次ACTION_DOWN事件后义图,
再次執(zhí)行dispatchTouchEvent分發(fā)ACTION_MOVE。
所以有幾個條件要注意
newTouchTarget = mFirstTouchTarget != null
mFirstTouchTarget.next = null
alreadyDispatchedToNewTouchTarget = false(注意這里有不同)
因為每次執(zhí)行dispatchTouchEvent召烂,
alreadyDispatchedToNewTouchTarget 都會被重置
而alreadyDispatchedToNewTouchTarget 字段只有在分發(fā)子view時才會被賦值為true
但是根據(jù)下圖的判斷碱工,在move事件中是不會執(zhí)行下面的語句的

image.png

這里ACTION_MOVE事件

image.png

所以我們接分析下面的else語句


image.png

進入dispatchTransformedTouchEvent我們可以看到


image.png

這里ACTION_MOVE事件結(jié)束

內(nèi)部攔截和外部攔截

先來看一下內(nèi)部攔截

注意內(nèi)部攔截是子view和父view都要進行處理的
在子view(listview)中


image.png

在父view(viewpager)中


image.png

首先來看一下getParent().requestDisallowInterceptTouchEvent()
傳入true時 mGroupFlags | FLAG_DISALLOW_INTERCEPT
傳入false時 mGroupFlags & FLAG_DISALLOW_INTERCEPT

image.png

所以到dispatchTouchEvent時(mGroupFlags & FLAG_DISALLOW_INTERCEPT)
傳入true時,運算結(jié)果就是 !=0奏夫,disallowIntercept為true怕篷,intercepted為false,后續(xù)事件會正常分發(fā)
傳入false時酗昼,運算結(jié)果就是 =0匙头,disallowIntercept為false,intercepted根據(jù)onInterceptTouchEvent情況定

image.png

接下來再分析一下仔雷,為什么要再父view做處理蹂析,而不是直接返回true就行了
最簡單的:不做處理返回true 子view就根本不會接收到事件舔示。內(nèi)部攔截的代碼都不會執(zhí)行。我認(rèn)為這只是其中一個原因

根據(jù)這個案例來說电抚,viewgroup分發(fā)事件給listview惕稻,listview也是一個ViewGroup。
所以它也會走ViewGroup的dispatchTouchEvent蝙叛,這個時候問題就來了俺祠。
在分發(fā)ACTION_DOWN,會執(zhí)行一個重置方法


image.png

這邊將mGroupFlags 做了運算


image.png

再回到dispatchTouchEvent方法中時借帘,mGroup又做了運算蜘渣,
所以最終的值就是這樣一個操作
mGroupFlags & ~FLAG_DISALLOW_INTERCEPT &FLAG_DISALLOW_INTERCEPT ,導(dǎo)致這個值肯定為0

所以disallowIntercept為false肺然,直接進入下面的if語句
此時如果沒在父view對onInterceptTouchEvent的down事件做處理的話蔫缸,返回true
那么intercepted就會為true

image.png
image.png

cancel事件的產(chǎn)生

還是繼續(xù)用這個案例,還是用內(nèi)部攔截去分析际起,當(dāng)down事件結(jié)束后拾碌,我們會進入到move事件
首先move事件進來時,是listview拿著這個事件街望,他會執(zhí)行他自己的這個方法

image.png

由于上面知道傳入false會導(dǎo)致disallowIntercept為false校翔,intercepted根據(jù)onInterceptTouchEvent情況定

image.png

而此時的ViewGroup返回是true,所以intercepted為true灾前,就會導(dǎo)致如下的執(zhí)行


image.png

執(zhí)行dispatchTransformedTouchEvent 并且下面會對mFirstTouchTarget賦值
前面已經(jīng)分析過了防症,next就是null,所以這里是將mFirstTouchTarget賦值為null

image.png

進入dispatchTransformedTouchEvent查看一下哎甲,
我們可以知道告希,這里是取消掉子view的事件的


image.png

這個move事件執(zhí)行完后,接著還是move事件烧给,因為他是觸發(fā)多次的,
這里就會直接走mFirstTouchTarget == null 的判定了喝噪,這時就是父view拿到事件了
所以就可以從listview的上下滑動轉(zhuǎn)換到viewpager的左右滑動


image.png

結(jié)尾

外部攔截的流程就不分析了础嫡,分析下來其實和cancel事件產(chǎn)生的流程大同小異,就不做重復(fù)了酝惧。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末榴鼎,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子晚唇,更是在濱河造成了極大的恐慌巫财,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,948評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件哩陕,死亡現(xiàn)場離奇詭異平项,居然都是意外死亡赫舒,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,371評論 3 385
  • 文/潘曉璐 我一進店門闽瓢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來接癌,“玉大人,你說我怎么就攤上這事扣讼∪泵停” “怎么了?”我有些...
    開封第一講書人閱讀 157,490評論 0 348
  • 文/不壞的土叔 我叫張陵椭符,是天一觀的道長荔燎。 經(jīng)常有香客問我,道長销钝,這世上最難降的妖魔是什么有咨? 我笑而不...
    開封第一講書人閱讀 56,521評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮曙搬,結(jié)果婚禮上摔吏,老公的妹妹穿的比我還像新娘。我一直安慰自己纵装,他們只是感情好征讲,可當(dāng)我...
    茶點故事閱讀 65,627評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著橡娄,像睡著了一般诗箍。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上挽唉,一...
    開封第一講書人閱讀 49,842評論 1 290
  • 那天滤祖,我揣著相機與錄音,去河邊找鬼瓶籽。 笑死匠童,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的塑顺。 我是一名探鬼主播汤求,決...
    沈念sama閱讀 38,997評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼严拒!你這毒婦竟也來了扬绪?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,741評論 0 268
  • 序言:老撾萬榮一對情侶失蹤裤唠,失蹤者是張志新(化名)和其女友劉穎挤牛,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體种蘸,經(jīng)...
    沈念sama閱讀 44,203評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡墓赴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,534評論 2 327
  • 正文 我和宋清朗相戀三年竞膳,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片竣蹦。...
    茶點故事閱讀 38,673評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡顶猜,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出痘括,到底是詐尸還是另有隱情长窄,我是刑警寧澤,帶...
    沈念sama閱讀 34,339評論 4 330
  • 正文 年R本政府宣布纲菌,位于F島的核電站挠日,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏翰舌。R本人自食惡果不足惜嚣潜,卻給世界環(huán)境...
    茶點故事閱讀 39,955評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望椅贱。 院中可真熱鬧懂算,春花似錦、人聲如沸庇麦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,770評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽山橄。三九已至垮媒,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間航棱,已是汗流浹背睡雇。 一陣腳步聲響...
    開封第一講書人閱讀 32,000評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留饮醇,地道東北人它抱。 一個月前我還...
    沈念sama閱讀 46,394評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像朴艰,于是被迫代替她去往敵國和親观蓄。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,562評論 2 349

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