北京時(shí)間06月17日消息,中國觸摸屏網(wǎng)訊,用戶觸摸屏幕所產(chǎn)生的Touch Event在Android里是用一個(gè)MotionEvent對(duì)象來傳遞和處理的,我們最關(guān)注的是MotionEvent里的action,可以看到有ACTION_DOWN, ACTION_UP,ACTION_MOVE,ACTION_CANCEL,ACTION_POINTER_DOWN,ACTION_POINTER_UP等等很多種,在這里面最需要關(guān)注的是ACTION_DOWN和ACTION_UP,它們一個(gè)代表了用戶按壓動(dòng)作的開始,一個(gè)代表了用戶按壓動(dòng)作的結(jié)束,其他的一些ACTION基本都是發(fā)生在這2個(gè)ACTION之間的(ACTION_CANCEL等特殊的暫不討論)。
本文來自:http://www.zc28898.cn/touchscreen/news/front/201506/17-36507.html
在用戶的一次單手指觸摸屏幕過程中,簡單的講,會(huì)按順序產(chǎn)生一個(gè)ACTION_DOWN,若干個(gè)ACTION_MOVE,和一個(gè)ACTION_UP,我們下面的討論也會(huì)基于這個(gè)簡單case。
觸摸事件的傳遞處理順序
一個(gè)觸摸事件首先是在硬件層面觸發(fā),然后逐層傳遞到軟件直至我們的app,前面的細(xì)節(jié)一般來說不用了解,我們討論的事件入口從Activity開始。
籠統(tǒng)的說(實(shí)際上細(xì)節(jié)有所不同,下面會(huì)提到)
觸摸事件的傳遞順序是:
Activity -> 當(dāng)前活動(dòng)窗口(PhoneWindow) -> 窗口的Top-Level View(DecorView) -> 各級(jí)ViewGroup (如各種Layout) -> ... -> 各級(jí)ViewGroup -> 葉子節(jié)點(diǎn) View
而觸摸事件的處理順序則剛好相反:
葉子節(jié)點(diǎn) View -> 各級(jí)ViewGroup -> ... -> 各級(jí)ViewGroup -> (Window和DecorView只有分發(fā)沒有處理) -> Activity
當(dāng)葉子節(jié)點(diǎn)View接受到事件之后,會(huì)試圖做出處理,如果它處理了,則上面各層不再處理,如果它沒有處理則往上由它的父ViewGroup處理,這樣逐層向上按順序試圖處理,直到Activity。
分發(fā)和處理的關(guān)鍵函數(shù)
從上面籠統(tǒng)提到的事件分發(fā)處理順序可以看到,關(guān)鍵的分發(fā)處理集中在Activity,ViewGroup和View中,那么對(duì)于它們來說,有如下幾個(gè)分發(fā)和處理的關(guān)鍵函數(shù),這里先做一個(gè)簡單介紹,后續(xù)再做詳細(xì)說明。
1,boolean dispatchTouchEvent(MotionEvent event)
這個(gè)函數(shù)是最關(guān)鍵的分發(fā)處理函數(shù),里面既包含了分發(fā)的邏輯,又包含了對(duì)處理的邏輯調(diào)用
分發(fā)邏輯:這個(gè)函數(shù)會(huì)先調(diào)用子view的dispatchTouchEvent進(jìn)行分發(fā)
處理邏輯:如果子view沒有對(duì)事件進(jìn)行消化處理的話,這個(gè)函數(shù)就會(huì)調(diào)用本UI組件的處理函數(shù)如onTouchEvent
函數(shù)返回值表示這個(gè)觸摸事件是否被當(dāng)前這個(gè)UI組件(Activity/ViewGroup/View)消化處理了
2,boolean onTouchEvent(MotionEvent event)
這個(gè)函數(shù)是UI組件自己實(shí)現(xiàn)用來響應(yīng)處理觸摸事件的
函數(shù)返回值表示這個(gè)觸摸事件是否被當(dāng)前這個(gè)UI組件(Activity/ViewGroup/View)消化處理了
3,View.OnTouchListener: boolean onTouch(View v, MotionEvent event)
這個(gè)函數(shù)不是Activity/ViewGroup/View本身的響應(yīng)處理函數(shù),而是一個(gè)Listener的響應(yīng)處理函數(shù)
需要給View通過setOnTouchListener來設(shè)置Listener以使得這個(gè)onTouch函數(shù)能夠起作用
Activity沒有setOnTouchListener
函數(shù)返回值表示這個(gè)觸摸事件是否被當(dāng)前這個(gè)UI組件(Activity/ViewGroup/View)消化處理了
4,ViewGroup: boolean onInterceptTouchEvent(MotionEvent event)
這個(gè)函數(shù)表明是否要攔截這個(gè)事件,前面提到過事件的分發(fā)順序是在View tress里從根到葉子逐層分發(fā),處理則是反向的從葉子到根逐層處理,onInterceptTouchEvent如果返回true,則表示我這一層要攔截這個(gè)事件自己進(jìn)行處理了,不要把它分發(fā)到子View里
這個(gè)函數(shù)只有ViewGroup含有,View沒有,因?yàn)閂iew已經(jīng)是葉子節(jié)點(diǎn)了,沒有子View了
這個(gè)函數(shù)默認(rèn)是返回false的,一般不要輕易o(hù)verride它,因?yàn)槌R?guī)來講是應(yīng)該由子View先響應(yīng)處理的
分發(fā)和處理的細(xì)節(jié)流程
ACTION_DOWN的處理
前面說過ACTION_DOWN是一個(gè)觸摸動(dòng)作的起始,所以對(duì)ACTION_DOWN的處理和對(duì)其他事件的處理在細(xì)節(jié)上是有不同的,各個(gè)UI組件對(duì)于ACTION_DOWN事件的處理流程可以看到如下:
(注意這里 view 表示一個(gè)視圖組件,它可以是一個(gè) View 也可以是一個(gè) ViewGroup)
Activity -> dispatchTouchEvent:
通過getWindow().superDispatchTouchEvent(event)把事件分發(fā)到當(dāng)前活動(dòng)窗口(PhoneWindow),之后是 窗口的Top-Level View(DecorView),調(diào)用了DecorView的dispatchTouchEvent,DecorView繼承自ViewGroup,所以這里實(shí)際上就進(jìn)入了ViewGroup層面的dispatchTouchEvent
如果superDispatchTouchEvent最終返回true(即下層的某個(gè)ViewGroup或者View消化處理了該函數(shù),dispatchTouchEvent返回true),則直接返回
如果返回值為false,則調(diào)用Activity的onTouchEvent對(duì)事件進(jìn)行處理
ViewGroup -> dispatchTouchEvent:
首先是檢查本view里是否保存了一個(gè)motion target(步驟4提到了怎么設(shè)置motion target),如果有則清除它
然后是調(diào)用onInterceptTouchEvent看這個(gè)事件是否需要被自己攔截,如果返回true,則直接進(jìn)入步驟7開始自己處理事件的流程
如果返回false,則需要遍歷所有的子view,遍歷的順序是:
首先按照Z-order
在同一Z值下如果可以的話按照子view的drawing order,這里的drawing order需要ViewGroup的子類override了getChildDrawingOrder才會(huì)實(shí)際生效
遍歷子view的時(shí)候,如果這個(gè)觸摸事件發(fā)生的位置在這個(gè)view的視覺范圍以內(nèi),那么就調(diào)用child.dispatchTouchEvent將事件分發(fā)給這個(gè)子view,如果這個(gè)子view消化處理了這個(gè)事件(即dispatchTouchEvent返回true),本view會(huì)將該子view賦值給一個(gè)表明motion target的變量,且此時(shí)跳出循環(huán)
循環(huán)遍歷結(jié)束后,如果有子view處理了該事件(即motion target不為空),則返回true表明此事件已經(jīng)被處理
如果沒有任何一個(gè)子view處理了該事件(即motion target不為空),則本view需要進(jìn)行處理,進(jìn)入步驟6
查看當(dāng)前view是否注冊了OnTouchListener,如果有,則調(diào)用該listener的onTouch函數(shù)來處理事件,如果onTouch返回true表示消化處理了該事件,則直接返回true
如果onTouch返回false表示沒有處理,則繼續(xù)調(diào)用本view的onTouchEvent函數(shù)來處理事件,這里會(huì)返回onTouchEvent的返回值
View -> dispatchTouchEvent:
查看當(dāng)前view是否注冊了OnTouchListener,如果有,則調(diào)用該listener的onTouch函數(shù)來處理事件,如果onTouch返回true表示消化處理了該事件,則直接返回true
如果onTouch返回false表示沒有處理,則繼續(xù)調(diào)用本view的onTouchEvent函數(shù)來處理事件,這里會(huì)返回onTouchEvent的返回值
其他ACTION的處理
上面說了ACTION_DOWN的處理,那ACTION_DOWN后續(xù)的ACTION_MOVE,ACTION_UP之類的事件又是怎么處理的呢?它們的處理方式略有不同:
在Activity層面來看,它們的處理和ACTION_DOWN沒有區(qū)別。
在ViewGroup層面來看,處理開始有了差異:
ViewGroup -> dispatchTouchEvent:
檢查當(dāng)前view在之前處理ACTION_DOWN(ACTION_MOVE,ACTION_UP之類的事件一定是有一個(gè)配對(duì)的ACTION_DOWN事件在前面發(fā)生)的時(shí)候是否已經(jīng)設(shè)置了一個(gè)motion target
如果有這個(gè)target,那么表明之前的ACTION_DOWN事件就是由該子view處理的,此時(shí)直接調(diào)用motion_target.dispatchTouchEvent
如果沒有這個(gè)target,那么表明之前的ACTION_DOWN沒有任何一個(gè)子view處理,那么后續(xù)這些事件也不要發(fā)給子view了,直接自己處理,進(jìn)入步驟4
查看當(dāng)前view是否注冊了OnTouchListener,如果有,則調(diào)用該listener的onTouch函數(shù)來處理事件,如果onTouch返回true表示消化處理了該事件,則直接返回true
如果onTouch返回false表示沒有處理,則繼續(xù)調(diào)用本view的onTouchEvent函數(shù)來處理事件,這里會(huì)返回onTouchEvent的返回值
注意整個(gè)流程中都跳過了onInterceptTouchEvent的攔截
在View層面來看,處理方式也是一樣的。
所以這里可以看到的現(xiàn)象就是:ACTION_DOWN被誰處理了,后續(xù)的ACTION_MOVE,ACTION_UP等事件最終也會(huì)交由誰處理。
onTouchEvent() or OnTouchListener.onTouch()?
從上面的內(nèi)容可以看出來,我們要想對(duì)一個(gè)觸摸事件進(jìn)行響應(yīng),可以在view的onTouchEvent()函數(shù)里處理,也可以給view設(shè)置一個(gè)OnTouchListener然后在listener的onTouch()函數(shù)里處理,那么它們有些什么區(qū)別?我們應(yīng)該怎么選擇呢?
首先看區(qū)別:
onTouchEvent()是View自己的函數(shù),對(duì)于我們來說無法override 各種View或者ViewGroup的onTouchEvent()函數(shù),只能是在自己自定義的view里面實(shí)現(xiàn)override;而OnTouchListener的onTouch()是可以對(duì)任何View/ViewGroup起作用的,我們只需要在代碼里為該View/ViewGroup加一個(gè)listener就行
OnTouchListener的onTouch()的執(zhí)行順序在view的onTouchEvent()之前,如果在onTouch()函數(shù)里面響應(yīng)完了觸摸事件并返回true之后,onTouchEvent()是不會(huì)被調(diào)用的
知道了區(qū)別,那么我們就可以輕易的做出選擇了(只是一家之言,歡迎各種不同意見):
OnTouchListener的onTouch()基本上是萬能的,任何時(shí)候都可以用它,所以大部分時(shí)候直接用它就行了
在自定義view里面,如果出于代碼結(jié)構(gòu)和功能清晰的目的,可以使用onTouchEvent()