用戶在操作的時候,不可避免地就會觸發(fā)觸摸事件。Android把觸摸過程分成很多個動作(Action),而開發(fā)中最常見也最主要考慮的觸摸過程就是:從ACTION_DOWN(觸摸落下)開始、到ACTION_UP(觸摸彈起)/ACTION_CANCEL(觸摸取消,譬如在按下控件,將控件移動到外層控件的時候,就會觸發(fā),而不是UP)結束,在這之間的是ACTION_MOVE(觸摸移動,非必然存在)。
同時,我們在界面上觸發(fā)觸摸事件的時候,同樣不可避免地會涉及到這三部分:Activity(可視界面當然還有Fragment,Dialog這些,但它們都依附著Activity),ViewGroup(通常來說做布局容器的LinearLayout、RelativeLayout等都算是),View(Button、ImageView等等)。一個簡單的情況就是我們在點擊界面(Activity)上在布局(ViewGroup)中的Button(View)的時候,這必然觸發(fā)了觸摸事件,但這具體是怎樣的一個過程?我們可以在這些過程中做些什么?這就涉及到了Android觸摸事件分發(fā)機制,先看一張簡略的分發(fā)流程圖(圖片來源于從Android源碼的角度理解應用開發(fā)(1)-Touch機制,感謝,侵刪):

從流程圖可以得知,Touch事件的分發(fā)情況是這樣的,Activity將事件分發(fā)到ViewGroup中,而ViewGroup層層分發(fā)直到找到需要處理Touch事件的子元素(可能是View也可能是ViewGroup),將事件傳遞下去。這里也可以提前告知一個邏輯,就算傳遞到了,但如果子元素不能處理觸摸事件,會將事件交回上一級處理,最后可以到Activity去處理,總之觸摸事件最終會被消費掉。
先讓我們來了解一下Touch事件分發(fā)和處理的三個重要方法。
1.public boolean dispatchTouchEvent(MotionEvent ev)
MotionEvent-手勢事件,它里面就包含了上面說的Action。這個方法從字面上的意思都很好理解,調(diào)度觸摸事件,這個方法是Activity、ViewGroup、View都有的,Touch事件都從它開始,也就是說Touch事件的分發(fā)和處理過程中,dispatchTouchEvent()是第一個被調(diào)用的方法。
2.public boolean onInterceptTouchEvent(MotionEvent ev)
也是從字面意思上就很好理解了,攔截觸摸事件。如果返回值為true,就攔截當前事件,不分發(fā)給子元素。很明顯這個方法View是沒有的,可以說三者中只有ViewGroup才有,因為Activity肯定要把事件分發(fā)下去(后面有說到),而View下面是沒有子元素的,要么處理觸摸事件要么交回ViewGroup處理。
3.public boolean onTouchEvent(MotionEvent ev)
這個方法表示對事件進行處理,在dispatchTouchEvent方法內(nèi)部調(diào)用,如果返回true表示消耗當前事件,如果返回false表示不消耗當前事件。
為了更好地理解整個流程,我們從View——>ViewGroup——>Activity的順序展開,讓我們先來看看是怎么處理Touch事件的,再看具體是怎么分發(fā)的。就以上面說到的按鈕點擊的情況來說一說。
View
上面的按鈕點擊中,View就是Button,剛剛說到整個過程最先被調(diào)用的就是dispatchTouchEvent(),Button的dispatchTouchEvent()是調(diào)用View的,我們來看一下這個方法:

可以看到,if語句的判斷條件有三個部分,只要有一部分條件是false,那么就會執(zhí)行onTouchEvent()。
1.判斷是否有注冊觸摸監(jiān)聽事件;
2.判斷控件是否可用(ENABLE),一般控件是默認可用的,除非通過setEnable(false)禁用控件,不然這部分條件一般為true;
3.監(jiān)聽事件里面的onTouch()的返回值。
所以很簡單的,控件監(jiān)聽觸摸事件讓onTouch()返回true,就不會再執(zhí)行下去了,當然我們是要看下去的,看一下onTouchEvent()里面實現(xiàn)了什么?
public boolean onTouchEvent(MotionEvent event) {
final int viewFlags = mViewFlags;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
}
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
...
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClick();
}
...
break;
case MotionEvent.ACTION_DOWN:
...
mHasPerformedLongPress = false;
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
break;
case MotionEvent.ACTION_CANCEL:
...
break;
case MotionEvent.ACTION_MOVE:
...
break;
}
return true;
}
return false;
}
這里我們可以簡單分析一下,這里面主要有三塊if語句:
1.如果控件是禁用(DISABLED)的,控件可以點擊(CLICKABLE)或長按點擊(LONG_CLICKABLE),返回true,消費事件但不做任何操作,如果控件不可點擊,onTouchEvent()返回false;
2.如果存在觸摸委托對象(TouchDelegate),交由其onTouchEvent()處理;
3.首先可以看到,只要控件是不可點擊的,不滿足if語句條件,直接返回false;反之,再處理不同的ACTION,都會返回true。
有意思的是,繼續(xù)深入查看源碼可以發(fā)現(xiàn),控件的onLongClick事件是在ACTION_DOWN里面觸發(fā)的(postDelayed())、onClick事件是在ACTION_UP里面觸發(fā)的(performClick()),這里不展開說。
上面說了那么多,好像和分發(fā)沒什么關系???別急,我們可以知道View的Touch事件其實都是為了知道View的dispatchTouchEvent()的返回值是什么,而這個值與分發(fā)事件大有關聯(lián),請看ViewGroup。
ViewGroup
從上面簡略的流程圖可以看出,觸摸事件由ViewGroup傳遞給View,很顯然,這就是一個事件分發(fā)過程,那么,這個過程是怎么做的呢?
ViewGroup首先調(diào)用dispatchTouchEvent(),讓我們來看一看具體的實現(xiàn):
public boolean dispatchTouchEvent(MotionEvent ev) {
...
boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (action == MotionEvent.ACTION_DOWN) {
if (mMotionTarget != null) {
mMotionTarget = null;
}
if (disallowIntercept || !onInterceptTouchEvent(ev)) {
...
for (int i = count - 1; i >= 0; i--) {
final View child = children[i];
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;
if (child.dispatchTouchEvent(ev)) {
mMotionTarget = child;
return true;
}
}
}
}
}
}
...
if (target == null) {
...
return super.dispatchTouchEvent(ev);
}
if (!disallowIntercept && onInterceptTouchEvent(ev)) {
...
return true;
}
if (isUpOrCancel) {
mMotionTarget = null;
}
...
return target.dispatchTouchEvent(ev);
}
我們可以看到,首先對ACTION_DOWN做了一大堆處理,來看一下具體在干嘛。
首先清除手勢目標;
第二個是重點:
disallowIntercept-禁止攔截,這個變量是一個Boolean值,默認值是false,意味著一般情況下是不禁止攔截功能的;
onIntercepTouchEvent()-這個方法在上面也提到過,攔截事件分發(fā)下去,將觸摸事件交給ViewGroup自己處理。
很好,那么
if (disallowIntercept || !onInterceptTouchEvent(ev))
這個條件語句的意思是只要不攔截,就進入條件判斷內(nèi)部,它的邏輯運算符是||,只要disallIntercept為true或者!onInterceptTouchEvent(ev)為true,也就是onInterceptTouchEvent(ev)為false就好了。
第一個條件,disallIntercept默認為false,但我們可以通過requestDisallowInterceptTouchEvent(Boolean)使得它的值為true;
第二個條件,看一下onInterceptTouchEvent(ev)的源碼:
public boolean onInterceptTouchEvent(MotionEvent ev) {
return false;
}
看!它的返回值是默認false的!也就是!onInterceptTouchEvent(ev)為true,那么也就是默認情況下,并不攔截事件,進入條件判斷內(nèi)部。
這一大塊代碼具體做了什么?
1.獲得ViewGroup下的子View
2.for循環(huán)判斷,手指落下的范圍在哪個View之中,接著!
if (child.dispatchTouchEvent(ev)) {
mMotionTarget = child;
return true;
}
上面說了那么多View里面獲取dispatchTouchEvent()的返回值,在這里用上了,確定分發(fā)目標為dispatchTouchEvent()返回值為true的子View!同時,ViewGroup的dispatchTouchEvent()的返回值也是true,那么ACTION_DOWN就結束了,再次進來就是其它ACTION的執(zhí)行邏輯了。
所以如果,View的dispatchTouchEvent()返回值為false,target為空,那么可以看到往下的邏輯里面,將自身作為一個View設為target:
if (target == null) {
···
return super.dispatchTouchEvent(ev);
}
也就是View的onTouch()返回值為true,View禁用,View的onTouchEvent()返回false等種種情況都會導致把事件重新交回給ViewGroup,然后ViewGroup執(zhí)行super.dispatchTouchEvent(ev),ViewGroup的超類是View,又回到了前面說的View的內(nèi)容了。
總結(來自于從Android源碼的角度理解應用開發(fā)(1)-Touch機制,很精準簡練的總結,感謝,侵刪):
1.在Down并且不攔截的時候會多出一個尋找Target的過程,在這個過程中遍歷子View,如果子View的dispatchTouch為true,則這個子View就是當前ViewGroup的Target。找Target是處理Down事件時候特有的,其他事件不會觸發(fā)找Target;
2.如果沒有Target,則發(fā)送把自己當做一個View去處理這個事件(super.dispatchTouch());
3.如果有Target并且攔截,則發(fā)送Cancel給子View ;
4.如果有Target并且不攔截,則調(diào)用Target的dispatchTouch;
5.可以利用requestDisallowInterceptTouchEvent(boolean)來強制viewparent不攔截事件。但是作用域限于一個Touch的過程(Down->Up/Cancel)。
接著就剩下最后一塊了,我們來看一下Activity又是怎么樣把事件分發(fā)給ViewGroup的。
Activity
還是先看dispatchTouchEvent():
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
代碼很簡單,但是其實經(jīng)過一系列的步驟之后,傳遞過程是這樣的:
Activity->Window->DecorView->ViewGroup,同理,如果最后Touch事件沒有被消費,也會交回由Activity的onTouchEvent()里面去處理。
補充: ACTION的傳遞過程:
對于在onTouchEvent消費事件的情況:
在哪個View的onTouchEvent 返回true,那么ACTION_MOVE和ACTION_UP的事件從上往下傳到這個View后就不再往下傳遞了,而直接傳給自己的onTouchEvent 并結束本次事件傳遞過程。
對于ACTION_MOVE、ACTION_UP總結:
ACTION_DOWN事件在哪個控件消費了(return true), 那么ACTION_MOVE和ACTION_UP就會從上往下(通過dispatchTouchEvent)做事件分發(fā)往下傳,就只會傳到這個控件,不會繼續(xù)往下傳,如果ACTION_DOWN事件是在dispatchTouchEvent消費,那么事件到此為止停止傳遞,如果ACTION_DOWN事件是在onTouchEvent消費的,那么會把ACTION_MOVE或ACTION_UP事件傳給該控件的onTouchEvent處理并結束傳遞。