這是學(xué)習(xí) Android 一個很重要的知識點(diǎn)犀农,在日常工作和面試中都很經(jīng)常用到。下面我們好好來分析一下图贸。
1.點(diǎn)擊事件的傳遞規(guī)則
所謂的點(diǎn)擊事件的事件分發(fā)就是對 MotionEvent 事件的分發(fā)過程枉圃,即當(dāng)一個 MotionEvent 產(chǎn)生以后,系統(tǒng)需要把這個事件傳遞給一個具體的 View陨簇,這個傳遞過程就是分發(fā)過程。
這個過程有三個很重要的方法共同完成:
-
public boolean dispatchTouchEvent(MotionEvent event);
這個方法用來進(jìn)行事件的分發(fā),如果事件能夠傳遞給當(dāng)前 View河绽,那么此方法一定會被調(diào)用己单,返回結(jié)果受當(dāng)前 View 的
onTouchEvent()
和下級 View 的dispatchTouchEvent()
方法的影響,表示是否消耗當(dāng)前事件耙饰。 -
public boolean onInterceptTouchEvent(MotionEvent event);
這個方法用來攔截觸摸事件的傳遞纹笼,如果返回 true,則不再將這個事件傳遞下去苟跪,觸摸事件將會轉(zhuǎn)給當(dāng)前 View 進(jìn)行處理廷痘,回調(diào)這個 View 的
onTouchEvent()
。如果返回 false件已,則將這個觸摸事件繼續(xù)傳遞下去給子 View笋额。 -
public boolean onTouchEvent(MotionEvent event);
這個方法表示是否消耗這個觸摸事件,假如返回 true篷扩,那么這個事件就已經(jīng)被該 View 消耗了兄猩,觸摸事件的傳遞到此為止,否則將會傳給該 View 的父容器鉴未。
PS:當(dāng)一個 View 需要處理事件時枢冤,如果它設(shè)置了
OnTouchListener
(注意,不要看成OnTouchEvent
)铜秆,那么這個這個OnTouchListener
的onTouch()
方法將會被調(diào)用淹真,如果返回 true,那么OnTouchEvent(MotionEvent event)方法
將不會被調(diào)用连茧;如果返回 false趟咆,那么OnTouchEvent(MotionEvent event)方法
將會被調(diào)用。優(yōu)先級從高到低排序:OnTouchListener
>OnTouchEvent(MotionEvent event)
>OnClickListener
梅屉。當(dāng)一個點(diǎn)擊事件發(fā)生以后,它的傳遞順序就是:
Activity -> PhoneWindow -> (調(diào)用 DecorView 的方法:
mDecor.superDispatchTouchEvent())-> (由于 DecorView 繼承 FrameLayout鳞贷,所以這個觸摸事件會一直傳遞下去) View
坯汤。
2.滑動沖突及解決辦法
滑動沖突有三種場景:
1.外部滑動方向和內(nèi)部滑動方向不一樣
2.外部滑動方向和內(nèi)部滑動方向相同
3.上面兩種情況的嵌套
解決滑動沖突的套路:
1、對于場景 1搀愧,處理的規(guī)則是:當(dāng)用戶左右滑動時惰聂,需要讓外層 View 攔截點(diǎn)擊事件;當(dāng)用戶上下滑動時咱筛,讓內(nèi)層 View 攔截點(diǎn)擊事件搓幌。其實(shí)就是根據(jù)滑動方向來判斷應(yīng)該由內(nèi)層 View 還是外層 View 來攔截事件。根據(jù)豎直方向滑動的距離和水平方向滑動的距離差來判斷是豎直方向滑動還是水平方向滑動迅箩。
-
2溉愁、根據(jù)業(yè)務(wù)來確定什么狀態(tài)由外層 View 來處理滑動事件,什么狀態(tài)由內(nèi)層 View 處理滑動事件饲趋。
推薦使用外部攔截法拐揭,即修改父容器需要攔截的事件撤蟆。
示例代碼:@Override public boolean onInterceptTouchEvent(MotionEvent event) { boolean intercepted = false; int x = (int) event.getX(); int y = (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { intercepted = false; //滾動還沒完成的直接停止?jié)L動動畫 if (!mScroller.isFinished()) { mScroller.abortAnimation(); intercepted = true; } break; } case MotionEvent.ACTION_MOVE: { int deltaX = x - mLastXIntercept; int deltaY = y - mLastYIntercept; // 只攔截橫向的 MOVE 事件 if (Math.abs(deltaX) > Math.abs(deltaY)) { intercepted = true; } else { intercepted = false; } break; } case MotionEvent.ACTION_UP: { intercepted = false; break; } default: break; } Log.d(TAG, "intercepted=" + intercepted); mLastX = x; mLastY = y; mLastXIntercept = x; mLastYIntercept = y; return intercepted; }