自定義側(cè)滑刪除帶你詳解事件分發(fā)一二
android中的事件分發(fā)算是我第一次詳細的了解android的某種機制,那時我是一直查博客,打印日志胜臊,來詳細的分析了每個事件分發(fā)的流程,也得益于當時學的比較認真疗杉,所以我現(xiàn)在的記憶還是很清晰。記得當時為什么會學事件分發(fā)蚕礼,就是因為我們當時的一個項目里面烟具,商品詳情頁面是模仿京東的,我是從那個控件開始詳細了解android事件分發(fā)的奠蹬。今天就通過一個非常簡單的自定義側(cè)滑刪除控件來了解事件分發(fā)的機制朝聋。
先上效果:
側(cè)滑刪除大家都不陌生,實現(xiàn)的方式也是多種多樣囤躁,結(jié)合今天的主題冀痕,我們就以一個自定義的LinearLayout來實現(xiàn)側(cè)滑刪除。如果要自定義滑動相關(guān)的自定義控件狸演,就一定要對View的坐標關(guān)系有比較清晰的了解言蛇,比如調(diào)用了scrollTo(),scrollBy()后變化的是scrollX和scrollY,而left宵距,right腊尚,top,bottom满哪,x婿斥,y等坐標并沒有改變劝篷,因為根據(jù)官方文檔知道,scrollTo()和scrollBy()移動的是View的內(nèi)容受扳,而并沒有影響到View相對于父View的左上角的相對關(guān)系携龟。還有MotionEvent中的getRawX(),getRawY(),這些是相對整個屏幕左上角的坐標兔跌,至于該使用相對父View的還是使用相對的整個屏幕的左上角的就要視情況而定勘高。只要用的一套對應的就行。
我們想象一下坟桅,這個自定義View可以讓它繼承LinearLayout华望,相比于直接繼承View這樣我們就能省去很多事,省去Measure和Layout的步驟仅乓。這個時候我們就能專注于處理滑動和事件分發(fā)赖舟。學習View的事件分發(fā)一般都是用來處理滑動沖突的,并且一般都會結(jié)合Scroller和VelocityTracker夸楣。側(cè)滑刪除控件主要就是側(cè)滑嗎宾抓,我們想辦法把這個LinearLayout滑動起來就好了,這樣就把原先看不到View顯示出來了豫喧。好了基本的思路就是這樣石洗,很簡單。那么下面就來看一下事件分發(fā)吧紧显。
android中事件分發(fā)我們一般只考慮View和ViewGroup就可以了讲衫,先說一下大概的流程,首先事件分發(fā)一定會先傳到Activity孵班,然后這個Activity會持有一個Window涉兽,這時就會把這個事件傳遞給相應的Window,這個Window里面有一個DecorView篙程,這個時候這個事件就傳遞到了View層枷畏。一個事件傳遞到ViewGroup首先會進入DispatchEvent方法,這個時候首先會有一個標志位虱饿,這個標志位就是這個DispatchEvent的返回值拥诡,用來告訴上一層的View是否處理這個MotionEvent。首先這個標志位會先置為false郭厌,然后問這個ViewGroup是否需要攔截這個MotionEvent袋倔,這個時候就是進入了onInterceptEvent(),這個函數(shù)只有在ViewGroup內(nèi)會被調(diào)用,這個函數(shù)的返回值就是告訴這個ViewGroup是否把這個事件向下傳折柠,自己不處理宾娜。還是選擇自己處理不再向下傳。這個函數(shù)返回false的話扇售,就表示不攔截這個事件前塔,會把這個事件向下傳遞嚣艇。如果返回的是true,這個時候這個ViewGroup就會變成了View华弓,表示它自己會處理這個事件食零。不再向下傳遞。當一個事件傳遞到View的時候寂屏,首先會進入dispatchEvent()方法贰谣,然后會進入onTouchEvent()方法,在onTouchEvenet里面會調(diào)用onTouch和onClick方法迁霎,前提是你設置了這兩個方法吱抚。
好了,有了上面的基礎我們就開始動手吧考廉,側(cè)滑刪除嗎秘豹,我就讓這個LinearLayout滑動就行了。首先會判斷在什么時候我需要攔截這個事件昌粤,應該就是當用戶是在左右滑動的時候既绕,我們會認為用戶是想使用側(cè)滑刪除的功能,那么這時候應該攔截這個事件涮坐,也就是讓我們的LinearLayout來處理凄贩。
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
int action = ev.getAction();
boolean intercepted = false;
int x = (int) ev.getX();
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
return true;
}
switch (action) {
case MotionEvent.ACTION_DOWN:
mLastX = (int) ev.getX();
intercepted = false;
break;
case MotionEvent.ACTION_MOVE:
if (Math.abs(x - mLastX) > mTouchSlop) intercepted = true;
else intercepted = false;
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
intercepted = false;
break;
}
mLastX = x;
return intercepted;
}
mTouchSlop:是系統(tǒng)認定是滑動的最小值。
mScroller:是用來處理滑動的膊升,用這個處理滑動會有一個動畫的效果怎炊,沒那么突然,雖然它的內(nèi)部實現(xiàn)不是動畫廓译。
可以看到在這個方法里面只有當用戶在X軸方向的滑動分距離大于系統(tǒng)認為的最小滑動的 時候才會攔截事件评肆。事件攔截寫來以后就會交給onTouchEvent處理。那么這時我們就會在這個函數(shù)處理相關(guān)的滑動非区。
@Override
public boolean onTouchEvent(MotionEvent event) {
getParent().requestDisallowInterceptTouchEvent(false);
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
return true;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mLastX = (int) event.getX();
break;
case MotionEvent.ACTION_MOVE:
int dx = (int) (event.getX() - mLastX);
mLastX = (int) event.getX();
scrollBy(-dx, 0);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
int scrollX = getScrollX();
if (scrollX > MAXWIDTH / 3) {
mScroller.startScroll(scrollX, 0, MAXWIDTH - scrollX, 0, 200);
} else {
mScroller.startScroll(scrollX, 0, -scrollX, 0, 200);
}
invalidate();
break;
}
mLastX = (int) event.getX();
return true;
}
mScroller.startScroll(scrollX, 0, -scrollX, 0, 2000);這個函數(shù)表示
的意思就是從(scrollX瓜挽,0)點滑動到(scrollX + (-scrollX),0 + 0)點,并且
耗費的時間是200ms征绸,如果使用Scroller來處理滑動就一定需要重寫
computeScroll()久橙,并且一定別忘記了postInvalidate(),因為Scroller只是負
責計算在相應的時間這個x應該變化到什么值管怠。它只是這個功能淆衷,所以我們拿到這個值后需要自己去scrollTo,并且需要調(diào)用 postInvalidate();這樣他才會繼續(xù)調(diào)用
computeScroll()來完成滑動渤弛,這樣不停的重新繪制祝拯,顯示出來的就是動畫效果。
@Override
public void computeScroll() {
super.computeScroll();
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
}
}
上面就算側(cè)滑刪除的功能實現(xiàn)了,可以看出非常簡單佳头,整個文件就一百多行代碼鹰贵,項目非常簡單,但是也能體現(xiàn)我們對事件分發(fā)各個環(huán)節(jié)的掌握康嘉,把對的代碼寫在對的地方碉输。
下面總結(jié)一下事件分發(fā)的知識點
1.同一個事件序列是指從手指接觸屏幕的那一刻起到手指離開屏幕的那一刻結(jié)束,在這個過程中所產(chǎn)生的一系列事件亭珍,這個事件序列以DOWN事件開始敷钾,中間含有數(shù)量不定的MOVE事件,最終以UP事件結(jié)束块蚌。
2.正常情況下闰非,一個事件序列只能被一個View攔截且消耗膘格,因為一個元素攔截了某次事件那么同一個事件序列內(nèi)的所有事件都會直接交給它處理峭范。當然不包括父View從中間攔截。
3.某個View決定攔截瘪贱,那么這一個事件序列都只能由它來處理(如果事件序列能夠傳遞給它的話)纱控,并且它的onInterceptTouchEvent不會再被調(diào)用,這個很好理解菜秦,就是說甜害,當一個View決定攔截一個事件后,那么系統(tǒng)就會把同一個事件序列內(nèi)的其他方法都直接交給他來處理球昨。因此就不用再調(diào)用這個View的onInterceptTouchEvent去詢問它是否要攔截了尔店。
4.某個View一旦開始處理事件,如果它不消耗DOWN事件主慰,(onTouchEvent返回了fasle)嚣州,那么同一事件序列中的其他事件都不會再交給它處理,并且事件將重新交由它的父元素去處理共螺。即父元素的onTouchEvent會被調(diào)用该肴。
5.如果View不消耗除DOWN以外的其他事件,那么這個點擊事件會消失藐不,此時父元素的onTouchEvent并不會被調(diào)用匀哄,并且當前View可以持續(xù)收到后續(xù)的事件追蹤這些消失的點擊事件會傳遞給Activity處理。
6.ViewGroup默認不攔截任何事件雏蛮,android源碼中ViewGroup的onInterceptTouchEvent方法默認返回false涎嚼。
7.View沒有onInterceptTouchEvent方法,一旦有點擊事件傳遞給它挑秉,那么它的onTouchEvent方法就會被調(diào)用法梯。
8.View的onTouchEvent默認都會消耗事件(返回true),除非它是不可點擊的(clickable和longClickable同時為false)衷模,View的longClickable屬性默認都為false鹊汛,clickable屬性要分情況蒲赂,比如Button的clickable屬性默認為true,而TextView的clickable屬性默認為false刁憋。
9.View的enable屬性不影響onTouchEvent的默認返回值滥嘴,哪怕一個View是disable狀態(tài)的,只要它的clickable或者longclickable又一個為true至耻,那么它的onTouchEvent就返回true若皱。
10.onClick會發(fā)生的前提是當前View是可點擊的,并且他收到了down和up的事件尘颓。
11.事件傳遞過程是由外向內(nèi)的走触,即事件總是先傳遞給父元素,然后再由父元素分發(fā)子View疤苹,通過requestDisAllowInterceptTouchEvent方法可以再子元素中干預父元素的事件分發(fā)過程互广。當時DOWN事件除外。
12.CANCEL事件卧土,官方文檔講的是當前手勢被釋放惫皱,你將不會接收到其他事件,應該像UP一樣對待它尤莺,那到底什么情況會觸發(fā)這個事件呢旅敷?當前控件(子控件)收到前驅(qū)事件(DOWN或者MOVE)后,它的父控件突然插手攔截了事件的傳遞颤霎,這時當前控件就會收到CANCEL媳谁,收到此事件后,不管子控件此時返回true或者false友酱,都認為這一個動作已經(jīng)完成晴音,不會再回傳到父控件的onTouchEvent中處理,同時后續(xù)事件會通過dispatchEvent方法直接傳遞到父控件這里來處理粹污。
13.父控件不攔截DOWN事件段多,如果子控件的onTouchEvent返回了true,在MOVE時父控件攔截壮吩,此時CANCEL會傳遞到子控件进苍,并且子控件只會收到這一個CANCEL事件,后續(xù)的事件序列都會進入到父控件的onTouchEvent鸭叙,且不再走父控件的onInterceptTouchEvent方法觉啊,因為此時父控件相當于View,mTarget為空時直接進入onTouchEvent方法沈贝,所以此時在onTouchEvent可以監(jiān)聽到后續(xù)的事件是因為沒有控件會處理這個事件杠人。如果父控件的onTouchEvent返回false,此時會上傳到父控件的父控件,最終Activity會處理嗡善。如果父控件的onTouchEvent返回true表明后續(xù)事件會由這個父控件來處理辑莫。
14.最后一點時觸摸區(qū)域的問題,如果觸摸區(qū)域在子控件內(nèi)罩引,同時父控件沒有攔截事件傳遞各吨,則不管子控件是否攔截此事件都會傳遞到子控件的onTouchEvent中處理,可以看成一種責任吧袁铐,因為我點的就是你揭蜒,你父親沒有攔截說明它不想處理了,那就會傳遞到你這里剔桨。