序言
這篇博文不是對事件分發機制全面的介紹,只是從源碼的角度分析ACTION_DOWN、ACTION_MOVE、ACTION_UP事件在ViewGroup中的分發邏輯,瞭解各個事件在ViewGroup的分發邏輯對理解、解決滑動衝突問題很有幫助。
ViewGroup中事件分發流程
這裏我是用的是2.3.3版本的源碼,原因在於這個版本的源碼易讀,當你理順了整個分發流程,再去看其他更加高級版本的源碼,你會發現思想是一樣。如果一個事件傳遞給ViewGroup,那麼dispatchTouchEvent方法會被調用,以下是對dispatchTouchEvent的分析。
public boolean dispatchTouchEvent(MotionEvent ev) {
if (!onFilterTouchEventForSecurity(ev)) {
return false;
}
final int action = ev.getAction();
//獲取事件的座標
final float xf = ev.getX();
final float yf = ev.getY();
final float scrolledXFloat = xf + mScrollX;
final float scrolledYFloat = yf + mScrollY;
final Rect frame = mTempRect;
//disallowIntercept 默認是false,
//可以通過requestDisallowItercepctTouchEvent來設置參數
//被設置成true後,ViewGroup無法攔截除ACTION_DOWN以外的事件(只能攔截Down)
boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
//這裏是ACTION_DOWN的處理邏輯
if (action == MotionEvent.ACTION_DOWN) {
if (mMotionTarget != null) {
// this is weird, we got a pen down, but we thought it was
// already down!
//We should probably send an ACTION_UP to the current target
mMotionTarget = null;
}
// If we're disallowing intercept or if we're allowing and we didn't
// intercept
//默認情況下disallowIntercept爲false,表示允許攔截,
//默認情況ViewGroup的onInterceptTouchEvent返回false
if (disallowIntercept || !onInterceptTouchEvent(ev)) {
// reset this event's action (just to protect ourselves)
ev.setAction(MotionEvent.ACTION_DOWN);
// We know we want to dispatch the event down, find a child
// who can handle it, start with the front-most child.
final int scrolledXInt = (int) scrolledXFloat;
final int scrolledYInt = (int) scrolledYFloat;
final View[] children = mChildren;
final int count = mChildrenCount;
for (int i = count - 1; i >= 0; i--) {//遍歷子View
final View child = children[i];
//判斷子元素是否可以接收到事件,兩條件決定
//條件一:子View是VISIBLE或者在播動畫
//條件二:點擊座標落在子View區域內(體現在內嵌的if)
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
|| child.getAnimation() != null) {
child.getHitRect(frame);
//判斷是否點擊座標落在子控件區域內
if (frame.contains(scrolledXInt, scrolledYInt)) {
final float xc = scrolledXFloat - child.mLeft;
final float yc = scrolledYFloat - child.mTop;
ev.setLocation(xc, yc);
child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
//將事件派發給子View,返回true表示子View處理該事件,
if (child.dispatchTouchEvent(ev)) {
// Event handled, we have a target now.
mMotionTarget = child;//將處理事件的目標View保存在變量
return true;//返回true,表示消耗事件
}
// The event didn't get handled, try the next view.
// Don't reset the event's location, it's not
// necessary here.
}
}
}
}
}
boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
(action == MotionEvent.ACTION_CANCEL);
if (isUpOrCancel) {
//重置mGroupFlags,
//使得在下一個事件ACTION_DOWN來臨時disallowIntercept爲false
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
// The event wasn't an ACTION_DOWN, dispatch it to our target if
// we have one.
final View target = mMotionTarget;
if (target == null) {//沒有找到可以處理事件的子View
// We don't have a target, this means we're handling the
// event as a regular view.
ev.setLocation(xf, yf);
if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
ev.setAction(MotionEvent.ACTION_CANCEL);
mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
}
//子控件不處理,所以此處判斷一下自己是否處理
//此時ViewGroup調用的是父類View的dispatchTouchEvent
return super.dispatchTouchEvent(ev);
}
// if have a target, see if we're allowed to and want to intercept its
// events
//允許並且想要攔截事件
if (!disallowIntercept && onInterceptTouchEvent(ev)) {
final float xc = scrolledXFloat - (float) target.mLeft;
final float yc = scrolledYFloat - (float) target.mTop;
mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
ev.setAction(MotionEvent.ACTION_CANCEL);//設置ACTION_CANCEL
ev.setLocation(xc, yc);
if (!target.dispatchTouchEvent(ev)) {//告知目標子控件事件被攔截
// target didn't handle ACTION_CANCEL. not much we can do
// but they should have.
}
// clear the target
mMotionTarget = null;//重置爲null,使得下個事件來臨時target=null
// Don't dispatch this event to our own view, because we already
// saw it when intercepting; we just want to give the following
// event to the normal onTouchEvent().
return true;//返回true,表示事件被消耗
}
if (isUpOrCancel) {
mMotionTarget = null;
}
// finally offset the event to the target's coordinate system and
// dispatch the event.
final float xc = scrolledXFloat - (float) target.mLeft;
final float yc = scrolledYFloat - (float) target.mTop;
ev.setLocation(xc, yc);
if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
ev.setAction(MotionEvent.ACTION_CANCEL);
target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
mMotionTarget = null;
}
//將事件分發給目標子控件
return target.dispatchTouchEvent(ev);
}
我們知道一些操作會產生事件,比方說在屏幕上滑動一下,這樣會產生一系列事件,但這些事件是屬於同一序列,它以ACTION_DOWN事件開始,中間有若干個ACTION_MOVE事件,以ACTION_UP事件結束。
ACTION_DOWN事件分發過程
ACTION_DOWN事件被分發到ViewGroup的時候是如何進行邏輯判斷的呢,在第31行代碼可以看到,首先會通過條件:disallowIntercept||!onInterceptTouchEvent判斷是否攔截,disallowIntercept 默認是false,可以通過requestDisallowItercepctTouchEvent來設置參數,若這個條件不成立表示攔截則此時mMotionTarget = null,則來到第80行,那麼target = null,表示沒有找到可以處理事件的子控件,接下來執行第91行代碼,此時調用super.dispatchTouchEvent即View的dispatchTouchEvent方法表示ViewGroup嘗試自己處理事件;若條件成立表示不攔截,見第40行代碼首先是遍歷該ViewGroup的子控件,結合第45、49行代碼可以知道在每遍歷一個子控件的時候,首先判斷子控件是不是visiable或者正在播動畫並且點擊事件的座標落在子控件的區域內,假如兩個條件同時滿足則表明子控件可以接收事件,這時候會來到第55行代碼,通過調用child.dispatchTouchEvent將事件分發給子控件,如果child.dispatchTouchEvent返回true,則表示事件被該子控件消耗了,此時執行第57行,該子控件被視作消耗事件的目標View並將其保存mMotionTarget變量中,返回true結束循環遍歷;如果child.dispatchTouchEvent返回false,則表示該子控件沒有消耗事件,如果該子控件不是ViewGroup遍歷的最後一個子控件,則在繼續循環遍歷下一個子控件。如果此時該子控件是ViewGroup遍歷的最後一個子控件,則表明所有的ViewGroup的子控件均不能處理事件,此時循環遍歷結束,則mMotiononView = null,程序來到第80行,那麼target = null,接下來調用super.dispatchTouchEvent即View的dispatchTouchEvent方法表明ViewGroup嘗試自己處理事件.ACTION_DOWN事件用流程圖表示如下:
ACTION_MOVE事件分發過程
ACTION_MOVE事件被分發到ViewGroup的時候,從第81行可以看到首先會判斷target是否等於null,若等於null,表示子控件均不能消耗事件,則調用super.dispatchTouchEvent即View的dispatchTouchEvent來處理事件。若不等於空,此時會來到第97行,此時通過條件(!disallowIntercept&&onInterceptTouchEvent(ev))判斷是否攔截,若條件不成立表示不攔截則執行第131行代碼,調用target.dispatchTouchEvent將事件分發給目標子控件處理,如果攔截則首先生成ACTION_CANCEL事件(見第101行)並分發給目標子控件target(見第103行),告知事件已被攔截,之後執行第108行將mMotionTarget重置爲null,目的是讓接下來的ACTION_UP事件直接能給ViewGroup自己處理,最後在第112行返回true表示事件被消耗。
ACTION_MOVE事件用流程圖表示如下:
ACTION_UP事件分發過程
ACTION_UP分發到ViewGroup的時候,首先會通過執行mGrouFlags & = ~FALG_DISALLOW_INTERCEPT使得下一個ACTION_DOWN事件來臨時disallowIntecept重置爲默認的false,之後的處理邏輯和ACTION_MOVE基本一致,這裏不再重複
ACTION_UP事件用流程圖表示如下: