Android View 雖然不是四大組件,但其并不比四大組件的地位低。而View的核心知識(shí)點(diǎn)事件分發(fā)機(jī)制則是不少剛?cè)腴T同學(xué)的攔路虎。ScrollView嵌套R(shí)ecyclerView(或者ListView)的滑動(dòng)沖突這種老大難的問題的理論基礎(chǔ)就是事件分發(fā)機(jī)制。

事件分發(fā)機(jī)制面試也會(huì)經(jīng)常被提及,如果你能get到要領(lǐng),并跟面試官深入的靈魂交流一下,那么一定會(huì)讓面試官對(duì)你印象深刻,拋出愛的橄欖枝想想都有點(diǎn)小激動(dòng)呢。那么就讓我們從淺入深,由表及里的去看事件分發(fā)機(jī)制,全方位,立體式,去弄懂這個(gè)神秘的事件分發(fā)機(jī)制吧。

MotionEvent事件初探
我們對(duì)屏幕的點(diǎn)擊,滑動(dòng),抬起等一系的動(dòng)作都是由一個(gè)一個(gè)MotionEvent對(duì)象組成的。根據(jù)不同動(dòng)作,主要有以下三種事件類型:
1.ACTION_DOWN:手指剛接觸屏幕,按下去的那一瞬間產(chǎn)生該事件
2.ACTION_MOVE:手指在屏幕上移動(dòng)時(shí)候產(chǎn)生該事件
3.ACTION_UP:手指從屏幕上松開的瞬間產(chǎn)生該事件
從ACTION_DOWN開始到ACTION_UP結(jié)束我們稱為一個(gè)事件序列
正常情況下,無論你手指在屏幕上有多么騷的操作,最終呈現(xiàn)在MotionEvent上來講無外乎下面兩種。
1.點(diǎn)擊后抬起,也就是單擊操作:ACTION_DOWN -> ACTION_UP
2.點(diǎn)擊后再風(fēng)騷的滑動(dòng)一段距離,再抬起:ACTION_DOWN -> ACTION_MOVE -> ... -> ACTION_MOVE -> ACTION_UP

public class MotionEventActivity extends BaseActivity {
private Button mButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_motion_event);
mButton = (Button) findViewById(R.id.button);
mButton.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
e("MotionEvent: ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
e("MotionEvent: ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
e("MotionEvent: ACTION_UP");
break;
}
return false;
}
});
}
public void click(View v) {
e("點(diǎn)擊了按鈕");
}
}
注:e("xxx")是BaseActivity封裝的Log顯示方法,具體請(qǐng)看BaseProject
當(dāng)我們單擊按鈕:

當(dāng)我們?cè)诎粹o上風(fēng)騷走位(滑動(dòng)):

細(xì)心的同學(xué)一定發(fā)現(xiàn)了我們常用的按鈕的onclick事件都是在ACTION_UP以后才被調(diào)用的。這和View的事件分發(fā)機(jī)制是不是有某種不可告人的關(guān)系呢?!

上面代碼我們給button設(shè)置了OnTouchListener并重寫了onTouch方法,方法返回值默認(rèn)為false。如果這里我們返回true,那么你會(huì)發(fā)現(xiàn)onclick方法不執(zhí)行了?。?!What?
這些隨著我們的深入探討,結(jié)論就會(huì)浮出水面!針對(duì)MotionEvent,我們先說這么多。
MotionEvent事件分發(fā)
當(dāng)一個(gè)MotionEvent產(chǎn)生了以后,就是你的手指在屏幕上做一系列動(dòng)作的時(shí)候,系統(tǒng)需要把這一系列的MotionEvent分發(fā)給一個(gè)具體的View。我們重點(diǎn)需要了解這個(gè)分發(fā)的過程,那么系統(tǒng)是如何去判斷這個(gè)事件要給哪個(gè)View,也就是說是如何進(jìn)行分發(fā)的呢?
事件分發(fā)需要View的三個(gè)重要方法來共同完成:
- public boolean dispatchTouchEvent(MotionEvent event)
通過方法名我們不難猜測(cè),它就是事件分發(fā)的重要方法。那么很明顯,如果一個(gè)MotionEvent傳遞給了View,那么dispatchTouchEvent方法一定會(huì)被調(diào)用!
返回值:表示是否消費(fèi)了當(dāng)前事件。可能是View本身的onTouchEvent方法消費(fèi),也可能是子View的dispatchTouchEvent方法中消費(fèi)。返回true表示事件被消費(fèi),本次的事件終止。返回false表示View以及子View均沒有消費(fèi)事件,將調(diào)用父View的onTouchEvent方法
- public boolean onInterceptTouchEvent(MotionEvent ev)
事件攔截,當(dāng)一個(gè)ViewGroup在接到MotionEvent事件序列時(shí)候,首先會(huì)調(diào)用此方法判斷是否需要攔截。特別注意,這是ViewGroup特有的方法,View并沒有攔截方法
返回值:是否攔截事件傳遞,返回true表示攔截了事件,那么事件將不再向下分發(fā)而是調(diào)用View本身的onTouchEvent方法。返回false表示不做攔截,事件將向下分發(fā)到子View的dispatchTouchEvent方法。
- public boolean onTouchEvent(MotionEvent ev)
真正對(duì)MotionEvent進(jìn)行處理或者說消費(fèi)的方法。在dispatchTouchEvent進(jìn)行調(diào)用。
返回值:返回true表示事件被消費(fèi),本次的事件終止。返回false表示事件沒有被消費(fèi),將調(diào)用父View的onTouchEvent方法
上面的三個(gè)方法可以用以下的偽代碼來表示其之間的關(guān)系。
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean consume = false;//事件是否被消費(fèi)
if (onInterceptTouchEvent(ev)){//調(diào)用onInterceptTouchEvent判斷是否攔截事件
consume = onTouchEvent(ev);//如果攔截則調(diào)用自身的onTouchEvent方法
}else{
consume = child.dispatchTouchEvent(ev);//不攔截調(diào)用子View的dispatchTouchEvent方法
}
return consume;//返回值表示事件是否被消費(fèi),true事件終止,false調(diào)用父View的onTouchEvent方法
}
通過上面的介紹相信我們已經(jīng)初步了解了View事件分發(fā)的機(jī)制

接下來我們來看一下View 和ViewGroup 在事件分發(fā)的時(shí)候有什么不一樣的地方
ViewGroup是View的子類,也就是說ViewGroup本身就是一個(gè)View,但是它可以包含子View(當(dāng)然子View也可能是一個(gè)ViewGroup),所以不難理解,上面所展示的偽代碼表示的是ViewGroup 處理事件分發(fā)的流程。而View本身是不存在分發(fā),所以也沒有攔截方法(onInterceptTouchEvent),它只能在onTouchEvent方法中進(jìn)行處理消費(fèi)或者不消費(fèi)。
上面結(jié)論先簡(jiǎn)單的理解一下,通過下面的流程圖,會(huì)更加清晰的幫助我們梳理事件分發(fā)機(jī)制


可以看出事件的傳遞過程都是從父View到子View。
但是這里有三點(diǎn)需要特別強(qiáng)調(diào)一下
- 子View可以通過requestDisallowInterceptTouchEvent方法干預(yù)父View的事件分發(fā)過程(ACTION_DOWN事件除外),而這就是我們處理滑動(dòng)沖突常用的關(guān)鍵方法。關(guān)于處理滑動(dòng)沖突,我們下一篇文章會(huì)專門去分析,這里就不做過多解釋。
- 對(duì)于View(注意!ViewGroup也是View)而言,如果設(shè)置了onTouchListener,那么OnTouchListener方法中的onTouch方法會(huì)被回調(diào)。onTouch方法返回true,則onTouchEvent方法不會(huì)被調(diào)用(onClick事件是在onTouchEvent中調(diào)用)所以三者優(yōu)先級(jí)是onTouch->onTouchEvent->onClick
- View 的onTouchEvent 方法默認(rèn)都會(huì)消費(fèi)掉事件(返回true),除非它是不可點(diǎn)擊的(clickable和longClickable同時(shí)為false),View的longClickable默認(rèn)為false,clickable需要區(qū)分情況,如Button的clickable默認(rèn)為true,而TextView的clickable默認(rèn)為false。
View事件分發(fā)源碼
作為程序猿,最不想看的但是也不得不去看的就是源碼!所謂知其然也要知其所以然,神秘的大佬曾經(jīng)說過提高的方法就是READ THE FUCKING CODE!那么我們就帶大家來看一下Android對(duì)事件分發(fā)的處理方式,看是否與我們上面說的結(jié)論一致!(為方便閱讀,以下都只給出了關(guān)鍵代碼并額外添加上一些簡(jiǎn)單注釋,全部代碼請(qǐng)自行閱讀源碼)

點(diǎn)擊事件產(chǎn)生最先傳遞到當(dāng)前的Activity,由Acivity的dispatchTouchEvent方法來對(duì)事件進(jìn)行分發(fā)。那么很明顯我們先看Activity的dispatchTouchEvent方法
Class Activity:
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {//事件分發(fā)并返回結(jié)果
return true;//事件被消費(fèi)
}
return onTouchEvent(ev);//沒有View可以處理,調(diào)用Activity onTouchEvent方法
}
通過上面的代碼我們可以發(fā)現(xiàn),事件會(huì)給Activity附屬的Window進(jìn)行分發(fā)。如果返回true,那么事件被消費(fèi)。如果返回false表示事件發(fā)下去卻沒有View可以進(jìn)行處理,則最后return Activity自己的onTouchEvent方法。
跟進(jìn)getWindow().superDispatchTouchEvent(ev)方法發(fā)現(xiàn)是Window類當(dāng)中的一個(gè)抽象方法
Window類說明
/**
* Abstract base class for a top-level window look and behavior policy. An
* instance of this class should be used as the top-level view added to the
* window manager. It provides standard UI policies such as a background, title
* area, default key processing, etc.
*
* <p>The only existing implementation of this abstract class is
* android.view.PhoneWindow, which you should instantiate when needing a
* Window.
*/
Class Window:
//抽象方法,需要看PhoneWindow的實(shí)現(xiàn)
public abstract boolean superDispatchTouchEvent(MotionEvent event);
Window的源碼有說明The only existing implementation of this abstract class is
android.view.PhoneWindow,Window的唯一實(shí)現(xiàn)類是PhoneWindow。那么去看PhoneWindow對(duì)應(yīng)的代碼。

class PhoneWindow
// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
PhoneWindow又調(diào)用了DecorView的superDispatchTouchEvent方法。而這個(gè)DecorView就是Window的頂級(jí)View,我們通過setContentView設(shè)置的View是它的子View(Activity的setContentView,最終是調(diào)用PhoneWindow的setContentView,有興趣同學(xué)可以去閱讀,這塊不是我們討論重點(diǎn))
到這里事件已經(jīng)被傳遞到我們的頂級(jí)View中,一般是ViewGroup。
那么接下來重點(diǎn)將放到ViewGroup的dispatchTouchEvent方法中。我們之前說過,事件到達(dá)View會(huì)調(diào)用dispatchTouchEvent方法,如果View是ViewGroup那么會(huì)先判斷是否攔截該事件。
class ViewGroup:
public boolean dispatchTouchEvent(MotionEvent ev) {
...
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Handle an initial down.
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Throw away all previous state when starting a new touch gesture.
// The framework may have dropped the up or cancel event for the previous gesture
// due to an app switch, ANR, or some other state change.
cancelAndClearTouchTargets(ev);
//清除FLAG_DISALLOW_INTERCEPT設(shè)置并且mFirstTouchTarget 設(shè)置為null
resetTouchState();
}
// Check for interception.
final boolean intercepted;//是否攔截事件
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//FLAG_DISALLOW_INTERCEPT是子View通過
//requestDisallowInterceptTouchEvent方法進(jìn)行設(shè)置的
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
//調(diào)用onInterceptTouchEvent方法判斷是否需要攔截
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
...
}
我們前面說過子View可以通過requestDisallowInterceptTouchEvent方法干預(yù)父View的事件分發(fā)過程(ACTION_DOWN事件除外)
為什么ACTION_DOWN除外?通過上述代碼我們不難發(fā)現(xiàn)。如果事件是ACTION_DOWN,那么ViewGroup會(huì)重置FLAG_DISALLOW_INTERCEPT標(biāo)志位并且將mFirstTouchTarget 設(shè)置為null。對(duì)于mFirstTouchTarget 我們可以先這么理解,如果事件由子View去處理時(shí)mFirstTouchTarget 會(huì)被賦值并指向子View。
所以當(dāng)事件為ACTION_DOWN 或者 mFirstTouchTarget !=null(即事件由子View處理)時(shí)會(huì)進(jìn)行攔截判斷。具體規(guī)則是如果子View設(shè)置了FLAG_DISALLOW_INTERCEPT標(biāo)志位,那么intercepted =false。否則調(diào)用onInterceptTouchEvent方法。
如果事件不為ACTION_DOWN 且事件為ViewGroup本身處理(即mFirstTouchTarget ==null)那么intercepted = true,很顯然事件已經(jīng)交給自己處理根本沒必要再調(diào)用onInterceptTouchEvent去判斷是否攔截。
結(jié)論:
當(dāng)ViewGroup決定攔截事件后,后續(xù)事件將默認(rèn)交給它處理并且不會(huì)再調(diào)用onInterceptTouchEvent方法來判斷是否攔截。子View可以通過設(shè)置FLAG_DISALLOW_INTERCEPT標(biāo)志位來不讓ViewGroup攔截除ACTION_DOWN以外的事件。
所以我們知道了onInterceptTouchEvent并非每次都會(huì)被調(diào)用。如果要處理所有的點(diǎn)擊事件那么需要選擇dispatchTouchEvent方法
而FLAG_DISALLOW_INTERCEPT標(biāo)志位可以幫助我們?nèi)ビ行У奶幚砘瑒?dòng)沖突
當(dāng)ViewGroup不攔截事件,那么事件將下發(fā)給子View進(jìn)行處理。
class ViewGroup:
public boolean dispatchTouchEvent(MotionEvent ev) {
final View[] children = mChildren;
//對(duì)子View進(jìn)行遍歷
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
// If there is a view that has accessibility focus we want it
// to get the event first and if not handled we will perform a
// normal dispatch. We may do a double iteration but this is
// safer given the timeframe.
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}
//判斷1,View可見并且沒有播放動(dòng)畫。2,點(diǎn)擊事件的坐標(biāo)落在View的范圍內(nèi)
//如果上述兩個(gè)條件有一項(xiàng)不滿足則continue繼續(xù)循環(huán)下一個(gè)View
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
newTouchTarget = getTouchTarget(child);
//如果有子View處理即newTouchTarget 不為null則跳出循環(huán)。
if (newTouchTarget != null) {
// Child is already receiving touch within its bounds.
// Give it the new pointer in addition to the ones it is handling.
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
//dispatchTransformedTouchEvent第三個(gè)參數(shù)child這里不為null
//實(shí)際調(diào)用的是child的dispatchTouchEvent方法
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
// childIndex points into presorted list, find original index
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
//當(dāng)child處理了點(diǎn)擊事件,那么會(huì)設(shè)置mFirstTouchTarget 在addTouchTarget被賦值
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
//子View處理了事件,然后就跳出了for循環(huán)
break;
}
}
}
上面代碼是將事件分發(fā)給子View的關(guān)鍵代碼,需要關(guān)注的地方都加了注釋。分發(fā)過程首先需要遍歷ViewGroup的所有子View,可以接收點(diǎn)擊事件的View需要滿足下面條件。
1.如果View可見并且沒有播放動(dòng)畫canViewReceivePointerEvents方法判斷
/**
* Returns true if a child view can receive pointer events.
* @hide
*/
private static boolean canViewReceivePointerEvents(@NonNull View child) {
return (child.mViewFlags & VISIBILITY_MASK) == VISIBLE
|| child.getAnimation() != null;
}
2.點(diǎn)擊事件的坐標(biāo)落在View的范圍內(nèi)isTransformedTouchPointInView方法判斷
/**
* Returns true if a child view contains the specified point when transformed
* into its coordinate space.
* Child must not be null.
* @hide
*/
protected boolean isTransformedTouchPointInView(float x, float y, View child,
PointF outLocalPoint) {
final float[] point = getTempPoint();
point[0] = x;
point[1] = y;
transformPointToViewLocal(point, child);
//調(diào)用View的pointInView方法進(jìn)行判斷坐標(biāo)點(diǎn)是否在View內(nèi)
final boolean isInView = child.pointInView(point[0], point[1]);
if (isInView && outLocalPoint != null) {
outLocalPoint.set(point[0], point[1]);
}
return isInView;
}
如果滿足上面兩個(gè)條件,接著我們看后面的代碼newTouchTarget = getTouchTarget(child);
/**
* Gets the touch target for specified child view.
* Returns null if not found.
*/
private TouchTarget getTouchTarget(@NonNull View child) {
for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
if (target.child == child) {
return target;
}
}
return null;
}
可以看到當(dāng)mFirstTouchTarget不為null的時(shí)候并且target.child就為我們當(dāng)前遍歷的child的時(shí)候,那么返回的newTouchTarget 就不為null,則跳出循環(huán)。我們前面說過,當(dāng)子View處理了點(diǎn)擊事件那么mFirstTouchTarget就不為nulll。事實(shí)上此時(shí)我們還沒有將事件分發(fā)給子View,所以正常情況下我們的newTouchTarget 此時(shí)為null
接下來關(guān)鍵來了
dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)方法。為方便我們將代碼再一次貼到后面來

if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
// childIndex points into presorted list, find original index
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
//當(dāng)child處理了點(diǎn)擊事件,那么會(huì)設(shè)置mFirstTouchTarget 在addTouchTarget被賦值
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
//子View處理了事件,然后就跳出了for循環(huán)
break;
}
可以看到它被最后一個(gè)if包圍,如果它返回為true,那么就break跳出循環(huán),如果返回為false則繼續(xù)遍歷下一個(gè)子View。
我們跟進(jìn)dispatchTransformedTouchEvent方法可以看到這樣的關(guān)鍵邏輯
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
這里child是我們遍歷傳入的子View此時(shí)不為null,則調(diào)用了child.dispatchTouchEvent(event);
我們子View的dispatchTouchEvent方法返回true,表示子View處理了事件,那么我們一直提到的,mFirstTouchTarget 會(huì)被賦值,是在哪里完成的呢?
再回頭看dispatchTransformedTouchEvent則為true進(jìn)入最后一個(gè)if語(yǔ)句,有這么一句newTouchTarget = addTouchTarget(child, idBitsToAssign);
/**
* Adds a touch target for specified child to the beginning of the list.
* Assumes the target child is not already present.
*/
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
沒錯(cuò),mFirstTouchTarget 就是在addTouchTarget中被賦值!到此子View遍歷結(jié)束
如果在遍歷完子View以后ViewGroup仍然沒有找到事件處理者即ViewGroup并沒有子View或者子View處理了事件,但是子View的dispatchTouchEvent返回了false(一般是子View的onTouchEvent方法返回false)那么ViewGroup會(huì)去處理這個(gè)事件。
從代碼上看就是我們遍歷的dispatchTransformedTouchEvent方法返回了false。那么mFirstTouchTarget 必然為null;
在ViewGroup的dispatchTouchEvent遍歷完子View后有下面的處理。
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
}
上面的dispatchTransformedTouchEvent方法第三個(gè)child參數(shù)傳null
我們剛看了這個(gè)方法。當(dāng)child為null時(shí),handled = super.dispatchTouchEvent(event);所以此時(shí)將調(diào)用View的dispatchTouchEvent方法,點(diǎn)擊事件給了View。到此事件分發(fā)過程全部結(jié)束!
結(jié)論:
ViewGroup會(huì)遍歷所有子View去尋找能夠處理點(diǎn)擊事件的子View(可見,沒有播放動(dòng)畫,點(diǎn)擊事件坐標(biāo)落在子View內(nèi)部)最終調(diào)用子View的dispatchTouchEvent方法處理事件
當(dāng)子View處理了事件則mFirstTouchTarget 被賦值,并終止子View的遍歷。
如果ViewGroup并沒有子View或者子View處理了事件,但是子View的dispatchTouchEvent返回了false(一般是子View的onTouchEvent方法返回false)那么ViewGroup會(huì)去處理這個(gè)事件(本質(zhì)調(diào)用View的dispatchTouchEvent去處理)
通過ViewGroup對(duì)事件的分發(fā),我們知道事件最終是調(diào)用View的dispatchTouchEvent來處理

View最終是怎么去處理事件的
class View:
public boolean dispatchTouchEvent(MotionEvent ev) {
// If the event should be handled by accessibility focus first.
if (event.isTargetAccessibilityFocus()) {
// We don't have focus or no virtual descendant has it, do not handle the event.
if (!isAccessibilityFocusedViewOrHost()) {
return false;
}
// We have focus and got the event, then use normal event dispatch.
event.setTargetAccessibilityFocus(false);
}
boolean result = false;
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Defensive cleanup for new gesture
stopNestedScroll();
}
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
}
if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
// Clean up after nested scrolls if this is the end of a gesture;
// also cancel it if we tried an ACTION_DOWN but we didn't want the rest
// of the gesture.
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}
return result;
}
上面是View的dispatchTouchEvent方法的全部代碼。相比ViewGroup我們需要好幾段去拆開看的長(zhǎng)篇大論而言,它就簡(jiǎn)潔多了。很明顯View是單獨(dú)的一個(gè)元素,它沒有子View,所以也沒有分發(fā)的代碼。我們需要關(guān)注的也只是上面當(dāng)中的一部分代碼。
//如果窗口沒有被遮蓋
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
//當(dāng)前監(jiān)聽事件
ListenerInfo li = mListenerInfo;
//需要特別注意這個(gè)判斷當(dāng)中的li.mOnTouchListener.onTouch(this, event)條件
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
//result為false調(diào)用自己的onTouchEvent方法處理
if (!result && onTouchEvent(event)) {
result = true;
}
}
通過上面代碼我們可以看到View會(huì)先判斷是否設(shè)置了OnTouchListener,如果設(shè)置了OnTouchListener并且onTouch方法返回了true,那么onTouchEvent不會(huì)被調(diào)用。
當(dāng)沒有設(shè)置OnTouchListener或者設(shè)置了OnTouchListener但是onTouch方法返回false則會(huì)調(diào)用View自己的onTouchEvent方法。接下來看onTouchEvent方法:
class View:
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
//1.如果View是設(shè)置成不可用的(DISABLED)仍然會(huì)消費(fèi)點(diǎn)擊事件
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
// 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)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
}
...
//2.CLICKABLE 和LONG_CLICKABLE只要有一個(gè)為true就消費(fèi)這個(gè)事件
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
(viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
switch (action) {
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
// take focus if we don't have it already and we should in
// touch mode.
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (prepressed) {
// The button is being released before we actually
// showed it as pressed. Make it show the pressed
// state now (before scheduling the click) to ensure
// the user sees it.
setPressed(true, x, y);
}
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// This is a tap, so remove the longpress check
removeLongPressCallback();
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
//3.在ACTION_UP方法發(fā)生時(shí)會(huì)觸發(fā)performClick()方法
performClick();
}
}
}
...
break;
}
...
return true;
}
return false;
}
上述代碼有三個(gè)關(guān)鍵點(diǎn)分別在注釋處標(biāo)出??梢钥闯黾幢鉜iew是disabled狀態(tài),依然不會(huì)影響事件的消費(fèi),只是它看起來不可用。只要CLICKABLE和LONG_CLICKABLE有一個(gè)為true,就一定會(huì)消費(fèi)這個(gè)事件,就是onTouchEvent返回true。這點(diǎn)也印證了我們前面說的View 的onTouchEvent 方法默認(rèn)都會(huì)消費(fèi)掉事件(返回true),除非它是不可點(diǎn)擊的(clickable和longClickable同時(shí)為false),View的longClickable默認(rèn)為false,clickable需要區(qū)分情況,如Button的clickable默認(rèn)為true,而TextView的clickable默認(rèn)為false。
(沒錯(cuò)這是復(fù)制前面的?。。。?/p>
ACTION_UP方法中有performClick();接下來看一下它:
class View:
/**
* Call this view's OnClickListener, if it is defined. Performs all normal
* actions associated with clicking: reporting accessibility event, playing
* a sound, etc.
*
* @return True there was an assigned OnClickListener that was called, false
* otherwise is returned.
*/
public boolean performClick() {
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
return result;
}
很明顯,如果View設(shè)置了OnClickListener,那么會(huì)回調(diào)onClick方法。到這里相信大家對(duì)一開始的例子已經(jīng)沒有什么疑惑了吧。

最后再?gòu)?qiáng)調(diào)一點(diǎn),我們剛說過View的longClickable默認(rèn)為false,clickable需要區(qū)分情況,如Button的clickable默認(rèn)為true,而TextView的clickable默認(rèn)為false。
這是默認(rèn)情況,我們可以單獨(dú)給View設(shè)置clickable屬性,但有時(shí)候會(huì)發(fā)現(xiàn)View的setClickable方法失效了。假如我們想讓View默認(rèn)不可點(diǎn)擊,將View的clickable設(shè)置成false,在合適的時(shí)候需要可點(diǎn)擊所以我們又給View設(shè)置了OnClickListener,那么你會(huì)發(fā)現(xiàn)View默認(rèn)依然可以點(diǎn)擊,也就是說setClickable失效了。關(guān)于setClickable失效問題
class View:
public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
public void setOnLongClickListener(@Nullable OnLongClickListener l) {
if (!isLongClickable()) {
setLongClickable(true);
}
getListenerInfo().mOnLongClickListener = l;
}
View的setOnClickListener會(huì)默認(rèn)將View的clickable設(shè)置成true。
View的setOnLongClickListener同樣會(huì)將View的longClickable設(shè)置成true。
至此,MotionEvent事件分發(fā)機(jī)制與源碼的分析已經(jīng)搞定,大家是否有g(shù)et到技能+1的感覺?

接下來一篇文章將講述如何解決View滑動(dòng)沖突。
歡迎轉(zhuǎn)發(fā),請(qǐng)附帶原文鏈接
喜歡就點(diǎn)個(gè)贊吧~
如果這篇文章對(duì)你有幫助,就刷個(gè)飛機(jī)游艇,點(diǎn)個(gè)喜歡關(guān)注雙擊一波666吧。
任何疑問都?xì)g迎在下方評(píng)論區(qū)討論。
關(guān)于我
本人,Android開發(fā)蕓蕓眾生當(dāng)中的一個(gè)新手,正在學(xué)習(xí)的路上不斷爬坑!
不論文章還是代碼,當(dāng)然肯定有很多不夠好的地方,希望各位大神不吝賜教,我一定虛心學(xué)習(xí),可以評(píng)論或者在github上提issue,歡迎關(guān)注轉(zhuǎn)發(fā)!轉(zhuǎn)發(fā)請(qǐng)帶上原文鏈接!謝謝