Android自定義View系列
- Android自定義View之Paint繪制文字和線
- Android自定義View注意事項(xiàng)
- Android自定義View之Canvas
- Android自定義View之圖像的色彩處理
- Android自定義View之雙緩沖機(jī)制和SurfaceView
- Android自定義View之圖片外形特效——輕松實(shí)現(xiàn)圓角和圓形圖片
- Android自定義View之Window、ViewRootImpl和View的三大流程
- Android自定義View之requestLayout方法和invalidate方法
事件序列
(1)手指接觸屏幕后會產(chǎn)生一系列事件,事件分為3種:ACTION_DOWN(手指剛剛接觸屏幕)、ACTION_MOVE(手指在屏幕移動(dòng))、ACTION_UP(手指從屏幕松開)
(2)一個(gè)事件序列為ACTION_DOWN-->ACTION_MOVE-->...-->ACTION_UP
事件傳遞的順序
Activity-->Window-->decor view-->我們的layout,ViewGroup-->我們布局中被點(diǎn)擊的子View
如果我們的子View沒有處理事件,那事件就會反向向上傳遞回來:
我們布局中被點(diǎn)擊的子View-->上層的ViewGroup-->decor view-->Window-->Activity
如果所有的View都沒有消耗事件,那最后事件會傳回到Activity,由Activity處理(Activity的onTouchEvent()方法被調(diào)用)
三大方法
ViewGroup中有3個(gè)跟事件分發(fā)有關(guān)的方法,分別是 dispatchTouchEvent、 onInterceptTouchEvent、onTouchEvent。
(1)dispatchTouchEvent方法
dispatchTouchEvent方法用來進(jìn)行事件的分發(fā)。事件傳遞到當(dāng)前View時(shí),這個(gè)方法就會被調(diào)用。dispatchTouchEvent方法里面包含了具體的事件分發(fā)邏輯,返回結(jié)果受當(dāng)前View的onTouchEvent方法和下級View的dispatchTouchEvent方法的影響。
(2)onInterceptTouchEvent方法
onInterceptTouchEvent方法在dispatchTouchEvent方法內(nèi)部被調(diào)用,用來判斷是否攔截某個(gè)事件。如果當(dāng)前View攔截了某個(gè)事件,那么在同一個(gè)事件序列當(dāng)中,此方法不會被再次調(diào)用,返回結(jié)果表示是否攔截當(dāng)前事件。這個(gè)方法只有VewGroup中有,View中沒有。
(3)onTouchEvent方法
在dispatchTouchEvent方法中調(diào)用,用來處理點(diǎn)擊事件,返回結(jié)果表示是否消耗當(dāng)前事件,如果不消耗,則在同一個(gè)事件序列中,當(dāng)前View無法再次接收到事件。
onTouchListener、onTouchEvent、onClickListener的優(yōu)先級
(1)onTouchListener和onTouchEvent都在dispatchTouchEvent方法中被調(diào)用,onClickListener在onTouchEvent方法中被調(diào)用
(2)onTouchListener的優(yōu)先級高于onTouchEvent方法,如果onTouchListener的onTouch方法返回true,則onTouchEvent方法不會被調(diào)用,當(dāng)然onClickListener就更不會被調(diào)用了
(3)在onTouchEvent方法中,如果當(dāng)前View設(shè)置了onClickListener,那么onClickListener的onClick方法會被調(diào)用
(4)只要View的CLICKABLE和LONKG_CLICKABLE有一個(gè)為true,View就會消耗當(dāng)前事件,也就是說onTouchEvent方法最后會返回true。
(5)View的LONG_CLICKABLE屬性默認(rèn)為false,而CLICKABLE屬性和具體的View有關(guān),可點(diǎn)擊的View的CLICKABLE屬性為true,不可點(diǎn)擊的View的CLICKABLE屬性為false。
ViewGroup中的事件分發(fā)邏輯
ViewGroup中的事件分發(fā)邏輯可以用一段偽代碼來表述
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean consume = false;
if (onInterceptTouchEvent(ev)) {
consume = onTouchEvent();
}else {
consume = child.dispatchTouchEvent(ev);
}
return consume;
}
從上述的偽代碼中我們可以總結(jié)出ViewGroup中的事件分發(fā)流程:
(1)事件傳遞到ViewGroup時(shí),dispatchTouchEvent方法會被調(diào)用。如果這個(gè)ViewGroup的onInterceptTouchEvent方法返回true,則表示它要攔截事件,事件就會交給當(dāng)前ViewGroup的onTouchEvent方法處理。
(2)如果當(dāng)前ViewGroup的onInterceptTouchEvent返回false,即不攔截事件,則會調(diào)用子元素的dispatchTouchEvent方法,這樣就把事件傳遞給了子元素。
(3)如果子元素沒有消耗事件,也就是子元素的dispatchTouchEvent方法返回false,那事件會由當(dāng)前ViewGroup自己處理,當(dāng)前ViewGroup的onTouchEvent會被調(diào)用。如果當(dāng)前ViewGroup的dispatchTouchEvent方法也返回false,最后就會一層層往上,如果事件一直沒有被消耗,那么最后Activity的onTouchEvent方法會被調(diào)用
(4)這里需要理解一下的是ViewGroup繼承自View,ViewGroup中并沒有onTouchEvent方法。在所有子元素沒有消耗事件時(shí),ViewGroup會調(diào)用父類,也就是View的dispatchTouchEvent方法,從而調(diào)用到onTouchEvent方法來自己處理事件,如果自己沒有消耗事件,dispatchTouchEvent方法就會返回false,從而將事件反向往上層傳遞。
(5)如果ACTION_DOWN事件子元素沒處理(onTouchEvent返回false),那這個(gè)事件序列的其他事件(MOVE和UP事件)都不會再分派給子元素處理。
(6)ViewGroup默認(rèn)不攔截任何事件
(7)對于ACTION_DOWN事件,ViewGroup每次都會調(diào)用onInterceptTouchEvent方法來判斷是否需要攔截事件,一旦確定要攔截事件,后續(xù)的ACTION_MOVE和ACTION_UP事件都ViewGroup自己處理,不會傳遞給子View,也不會再調(diào)用onInterceptTouchEvent方法。所以onInterceptTouchEvent方法不是每次事件都會被調(diào)用的。
(8)子View可以通過requestDisallowInterceptTouchEvent方法來干預(yù)父元素的除了ACTION_DOWN意外的事件分發(fā)過程
View中的事件分發(fā)邏輯
requestDisallowInterceptTouchEvent方法
requestDisallowInterceptTouchEvent方法用于影響父元素的事件攔截策略,requestDisallowInterceptTouchEvent(true),表示不允許父元素?cái)r截事件,這樣事件就會傳遞給子View。一般這個(gè)方法子View用的多,可以用來處理滑動(dòng)沖突問題。
事件分發(fā)邏輯
(1)View中沒有onInterceptTouchEvent方法,所以一旦事件傳遞到View,那么View的dispatchTouchEvent方法就會被調(diào)用。
(2)dispatchTouchEvent方法中處理事件的邏輯順序是onTouchListener-->onTouchEvent-->onClickListener。
(3)也就是說如果View設(shè)置了onTouchListener,那onTouchListener的onTouch方法會被調(diào)用,如果onTouch方法返回true,那事件就被消耗了,事件分發(fā)結(jié)束,onTouchEvent不會被調(diào)用。
(4)如果onTouch方法返回false,那么onTouchEvent就會被調(diào)用。如果View設(shè)置了onClickListener,當(dāng)ACTION_UP事件到來時(shí),onTouchEvent中的onClickListener的onClick方法也會被調(diào)用。
(5)View一般都會消耗事件,如果View沒有消耗ACTION_DOWN事件,那后面ACTION_MOVE和ACTION_UP就都不會傳遞給View。
常用的滑動(dòng)沖突處理邏輯
(1)利用父布局的onInterceptTouchEvent方法
這個(gè)思路就是在父布局需要處理事件時(shí)攔截下來,其他時(shí)候不攔截。有幾個(gè)注意點(diǎn):
- 對于ACTION_DOWN事件,onInterceptTouchEvent方法必須返回false,因?yàn)橐坏┓祷豻rue,子元素永遠(yuǎn)也接收不到事件了,那還解決個(gè)毛線沖突。
- 主要的邏輯就在ACTION_MOVE的處理上,需不需要攔截的邏輯在這里根據(jù)需要來實(shí)現(xiàn)
- 對于ACTION_UP事件返回false,因?yàn)橐坏└冈胤祷豻rue,那子View就接受不到ACTION_UP事件了,也就無法觸發(fā)onClick事件。
(2)利用子View的requestDisallowInterceptTouchEvent方法
這個(gè)思路就是父布局默認(rèn)攔截除了ACTION_DOWN的所有事件,子View中在dispatchTouchEvent方法中根據(jù)需要來干預(yù)父布局的攔截策略。默認(rèn)不允許父布局?jǐn)r截事件,在需要父布局處理事件時(shí),通過requestDisallowInterceptTouchEvent(false)方法讓父布局處理事件,其他時(shí)候都由子View處理。
注意點(diǎn):
- 同樣的對于ACTION_DOWN事件,onInterceptTouchEvent方法必須返回false,其他事件默認(rèn)返回true
- 在子View的dispatchTouchEvent方法中,對于ACTION_DOWN事件,通過調(diào)用requestDisallowInterceptTouchEvent(true)默認(rèn)不允許父布局?jǐn)r截事件,這樣后續(xù)事件都交給子View處理
- 在子View的dispatchTouchEvent方法中,對于ACTION_MOVE事件,默認(rèn)是子View處理,在需要父布局處理時(shí),調(diào)用requestDisallowInterceptTouchEvent(false)方法來讓父布局?jǐn)r截事件,交給父布局處理。
歡迎關(guān)注我的微信公眾號,和我一起每天進(jìn)步一點(diǎn)點(diǎn)!
