事件的分發(fā)和消費機制
一、簡介:
Activity或View類的onTouchEvent()回調(diào)函數(shù)會接收到touch事件锨苏。
一個完整的手勢是從ACTION_DOWN開始,到ACTION_UP結(jié)束改橘。
簡單的情況下叠纹,我們只需要在onTouchEvent()中寫個switch case語句,處理各種事件(Touch Down嫌套、Touch Move局冰、Touch Up等),但是比較復(fù)雜的動作就需要更多的處理了灌危。
ViewGroup作為一個parent是可以截獲傳向它的child的touch事件的。
如果一個ViewGroup的onInterceptTouchEvent()方法返回true碳胳,說明Touch事件被截獲勇蝙,子View不再接收到Touch事件,而是轉(zhuǎn)向本ViewGroup的onTouchEvent()方法處理挨约。從Down開始味混,之后的Move产雹,Up都會直接在onTouchEvent()方法中處理。
先前還在處理touch event的child view將會接收到一個ACTION_CANCEL翁锡。
如果onInterceptTouchEvent()返回false蔓挖,則事件會交給child view處理。
Android中提供了ViewGroup馆衔、View瘟判、Activity三個層次的Touch事件處理。
處理過程是按照Touch事件從上到下傳遞角溃,再按照是否消費的返回值拷获,從下到上返回,即如果View的onTouchEvent返回false减细,將會向上傳給它的parent的ViewGroup匆瓜,如果ViewGroup不處理,將會一直向上返回到Activity未蝌。
即隧道式向下分發(fā)驮吱,然后冒泡式向上處理。
Android 中與 Touch 事件相關(guān)的方法包括:dispatchTouchEvent(MotionEvent ev)萧吠、onInterceptTouchEvent(MotionEvent ev)左冬、onTouchEvent(MotionEvent ev);能夠響應(yīng)這些方法的控件包括:ViewGroup怎憋、View又碌、Activity。方法與控件的對應(yīng)關(guān)系如下表所示:
Touch 事件相關(guān)方法方法功能ViewGroupViewActivity
public booleandispatchTouchEvent(MotionEvent ev)事件分發(fā)YesYesYes
public booleanonInterceptTouchEvent(MotionEvent ev)事件攔截YesYes / NoNo
public booleanonTouchEvent(MotionEvent ev)事件響應(yīng)YesYesYes
從這張表中我們可以看到 ViewGroup 和 View 對與 Touch 事件相關(guān)的三個方法均能響應(yīng)绊袋,而 Activity 對 onInterceptTouchEvent(MotionEvent ev) 也就是事件攔截不進行響應(yīng)毕匀。另外需要注意的是 View 對 onInterceptTouchEvent(MotionEvent ev) 的響應(yīng)的前提是可以向該 View 中添加子 View,如果當前的 View 已經(jīng)是一個最小的單元View(比如TextView)癌别,那么就無法向這個最小 View 中添加子 View皂岔,也就無法向子 View 進行事件的攔截,所以它沒有onInterceptTouchEvent(MotionEvent ev)展姐。
三個方法的用法:
dispatchTouchEvent()用來分派事件躁垛。
其中調(diào)用了onInterceptTouchEvent()和onTouchEvent(),一般不重寫該方法
onInterceptTouchEvent()用來攔截事件圾笨。
ViewGroup類中的源碼實現(xiàn)就是{return false;}表示不攔截該事件教馆,
事件將向下傳遞(傳遞給其子View);
若手動重寫該方法擂达,使其返回true則表示攔截土铺,事件將終止向下傳遞,
事件由當前ViewGroup類來處理,就是調(diào)用該類的onTouchEvent()方法
onTouchEvent()用來處理事件悲敷。
返回true則表示該View能處理該事件究恤,事件將終止向上傳遞(傳遞給其父View);
返回false表示不能處理后德,則把事件傳遞給其父View的onTouchEvent()方法來處理
【注】:ViewGroup的某些子類(GridView部宿、ScrollView...)重寫了onInterceptTouchEvent()方法,當發(fā)生ACTION_MOVE事件時瓢湃,返回true進行攔截理张。
一、Touch事件分析
(一)箱季、事件分發(fā):public booleandispatchTouchEvent(MotionEvent ev)
Touch事件發(fā)生時Activity的dispatchTouchEvent(MotionEvent ev)方法會以隧道方式(從根元素依次往下傳遞直到最內(nèi)層子元素或在中間某一元素中由于某一條件停止傳遞)將事件傳遞給最外層View的dispatchTouchEvent(MotionEvent ev)方法涯穷,并由該View的dispatchTouchEvent(MotionEvent ev)方法對事件進行分發(fā)。
(二)藏雏、事件攔截:public booleanonInterceptTouchEvent(MotionEvent ev)
在外層View的dispatchTouchEvent(MotionEvent ev)方法返回系統(tǒng)默認的super.dispatchTouchEvent(ev)情況下拷况,事件會自動的分發(fā)給當前View的onInterceptTouchEvent方法。onInterceptTouchEvent的事件攔截邏輯如下:
?如果onInterceptTouchEvent返回true掘殴,則表示將事件進行攔截赚瘦,并將攔截到的事件交由當前View的onTouchEvent進行處理;
?如果onInterceptTouchEvent返回false奏寨,則表示將事件放行起意,當前View上的事件會被傳遞到子View上,再由子View的dispatchTouchEvent來開始這個事件的分發(fā)病瞳;
?如果onInterceptTouchEvent返回super.onInterceptTouchEvent(ev)揽咕,事件默認不會被攔截,并將攔截到的事件交由當前View的onTouchEvent進行處理套菜。
(三)亲善、事件響應(yīng):public booleanonTouchEvent(MotionEvent ev)
在dispatchTouchEvent返回super.dispatchTouchEvent(ev)并且onInterceptTouchEvent返回true或返回super.onInterceptTouchEvent(ev)的情況下onTouchEvent會被調(diào)用。onTouchEvent的事件響應(yīng)邏輯如下:
?如果事件傳遞到當前View的onTouchEvent方法逗柴,而該方法返回了false蛹头,那么這個事件會從當前View向上傳遞,并且都是由上層View的onTouchEvent來接收戏溺,如果傳遞到上面的onTouchEvent也返回false渣蜗,這個事件就會“消失”,而且接收不到下一次事件旷祸。
?如果返回了true則會接收并消費該事件耕拷。
?如果返回super.onTouchEvent(ev)默認處理事件的邏輯和返回false時相同。
onInterceptTouchEvent用于改變事件的傳遞方向托享。決定傳遞方向的是返回值骚烧,返回為false時事件會傳遞給子控件控淡,返回值為true時事件會傳遞給當前控件的onTouchEvent(),這就是所謂的Intercept(攔截)止潘。
正確的使用方法是,在此方法內(nèi)僅判斷事件是否需要攔截辫诅,然后返回凭戴。即便需要攔截也應(yīng)該直接返回true,然后由onTouchEvent方法進行處理炕矮。
onTouchEvent用于處理事件么夫,返回值決定當前控件是否消費(consume)了這個事件。尤其對于ACTION_DOWN事件肤视,返回true档痪,表示我想要處理后續(xù)事件;返回false邢滑,表示不關(guān)心此事件腐螟,并返回由父類進行處理。
可能你要問是否消費了又區(qū)別嗎困后,反正我已經(jīng)針對事件編寫了處理代碼乐纸?答案是有區(qū)別!比如ACTION_MOVE或者ACTION_UP發(fā)生的前提是一定曾經(jīng)發(fā)生了ACTION_DOWN摇予,如果你沒有消費ACTION_DOWN汽绢,那么系統(tǒng)會認為ACTION_DOWN沒有發(fā)生過,所以ACTION_MOVE或者ACTION_UP就不能被捕獲侧戴。
在沒有重寫onInterceptTouchEvent()和onTouchEvent()的情況下(他們的返回值都是false)
Android系統(tǒng)中的每個View的子類都具有下面三個和TouchEvent處理密切相關(guān)的方法:
1)public boolean dispatchTouchEvent(MotionEvent ev)這個方法用來分發(fā)TouchEvent
2)public boolean onInterceptTouchEvent(MotionEvent ev)這個方法用來攔截TouchEvent
3)public boolean onTouchEvent(MotionEvent ev)這個方法用來處理TouchEvent
1宁昭、如果dispatchTouchEvent返回true,則交給這個view的onTouchEvent處理酗宋, 如果最終需要處理事件的view的onTouchEvent()返回了false积仗,那么該事件將被傳遞至其上一層次的view的onTouchEvent()處理。如果最終需要處理事件的view的onTouchEvent()返回了true本缠,那么后續(xù)事件將可以繼續(xù)傳遞給該view的onTouchEvent()處理斥扛。
2、如果dispatchTouchEvent6返回false丹锹,則交給這個view的interceptTouchEvent方法來決定是否
要攔截這個事件稀颁,如果interceptTouchEvent返回true,表示攔截掉了楣黍,則交給它的onTouchEvent來處理匾灶,如果interceptTouchEvent返回false,那么就傳遞給子view租漂,由子view的dispatchTouchEvent再來開始這個事件的分發(fā)阶女。
3颊糜、如果事件傳遞到某一層的子view的onTouchEvent上了,這個方法返回了false秃踩,那么這個事件
會從這個view往上傳遞衬鱼,都是onTouchEvent來接收。如果傳遞到最上面的onTouchEvent也返回false的話憔杨,這個事件就會“消失”鸟赫,而且接收不到下一次事件。
【備注:】
藍色區(qū)域為MyViewGroup消别、綠色為MyViewGroupInner抛蚤、紅色為MyView。分別繼承于LinearLayout寻狂、LinearLayout和TextView岁经。
1、核心代碼:
【以下動作均為點擊自定義View】
一蛇券、無攔截缀壤,無消費:12個
二、ViewGroup攔截怀读,無消費:7個
03-31 18:50:26.344: I/MainActivity(15304): --->dispatchTouchEvent: 0
03-31 18:50:26.344: I/MyViewGroup(15304): --->dispatchTouchEvent: 0
03-31 18:50:26.372: I/MyViewGroup(15304): --->onInterceptTouchEvent: 0
03-31 18:50:26.392: I/MyViewGroup(15304): --->onTouchEvent: 0
03-31 18:50:26.402: I/MainActivity(15304): --->onTouchEvent: 0
03-31 18:50:26.526: I/MainActivity(15304): --->dispatchTouchEvent: 1
03-31 18:50:26.526: I/MainActivity(15304): --->onTouchEvent: 1
三诉位、無攔截,View消費:14個
四菜枷、無攔截苍糠,僅有ViewGroup消費:12個
五、無攔截啤誊,Activity消費:12個
六岳瞭、無攔截、無消費蚊锹,僅在ViewGroup上設(shè)置單擊監(jiān)聽瞳筏,執(zhí)行單擊動作后:13個
七、無攔截牡昆、無消費姚炕,僅在ViewGroup上設(shè)置長按監(jiān)聽和單擊監(jiān)聽,執(zhí)行長按動作后:(如果長按返回false:14個 丢烘, 如果長按返回true:13個)
八柱宦、無攔截、無消費播瞳,僅僅在View上設(shè)置長按監(jiān)聽和單擊監(jiān)聽掸刊,執(zhí)行單擊動作后:15個
九、無攔截赢乓、無消費忧侧,在View上設(shè)置長按監(jiān)聽和單擊監(jiān)聽石窑,執(zhí)行長按動作后:(如果長按返回false:16個 , 如果長按返回true:15個)
十蚓炬、無攔截松逊、無消費,僅在View上設(shè)置clickable=true:14個
十一肯夏、無攔截棺棵、無消費,僅在ViewGroupInner上設(shè)置clickable=true:13個
A:dispatch:0
G:dispatch:0
G:intercept:0
Gi:dispatch:0
Gi:intercepte:0
V:dispatch:0
V:touch:0
Gi:touch:0
A:dispatch:1
G:disaptch:1
G:intercept:1
Gi:dispatch:1
Gi:touch:1
【備注:】
onLongClick按下到一定的時間就調(diào)用了,在ACTION_UP之前調(diào)用熄捍。如果長按返回false,則長按結(jié)束的ACTION_UP調(diào)用onClick(onClick是ACTION_UP之后調(diào)用的)母怜;如果長按返回true余耽,onLongClick后不再調(diào)用onClick。
Click事件處理
Click事件:View的短按和長按都是注冊監(jiān)聽器的(setListener):
onClick是在ACTION_UP之后執(zhí)行的苹熏。
onLongClick則是按下到一定時間之后執(zhí)行的碟贾,這個時間是ViewConfiguration中的:
private static final int TAP_TIMEOUT =180;//180毫秒
這里需要注意onLongClick的返回值,如果是false轨域,則onLongClick之后袱耽,手指抬起,ACTION_UP之后還是會執(zhí)行到onClick干发;但是如果onLongClick返回true朱巨,則不會再調(diào)用onClick。
【備注:】
如果一個View是Clickable或者longClickable枉长,onTouchEvent()就直接返回true,表示該View就一直消費Touch事件冀续。也就是說,一個clickable或者longclickable的View是一直消費Touch事件的必峰,而一般的View既不是clickable也不是longclickable的(即不會消費Touch事件洪唐,只會執(zhí)行ACTION_DOWN而不會執(zhí)行ACTION_MOVE和ACTION_UP)。
Button是clickable的吼蚁,可以消費Touch事件凭需,但是我們可以通過setClickable()和setLongClickable()來設(shè)置View是否為clickable和longClickable。當然還可以通過重寫View的onTouchEvent()方法來控制Touch事件的消費與否肝匆。