老規(guī)矩,看源碼一定要帶著問(wèn)題、推測(cè)或結(jié)論去看,不能看到太深,要能剎得車(chē)~不然會(huì)陷在源碼中。像我這樣的小菜鳥(niǎo),總是在看過(guò)源碼之后才知道Android源碼設(shè)計(jì)的強(qiáng)大,所以在此總結(jié)一下源碼中是如何處理事件分發(fā)的。
注意:本文中所有源碼分析部分均基于 API25 版本,由于安卓系統(tǒng)源碼改變很多,可能與之前版本有所不同,但基本流程都是一致的。
本文將對(duì)ViewGroup的dispatchTouchEvent()做了一個(gè)比較全面的注釋?zhuān)⑿纬闪撕?jiǎn)化后的偽代碼輔助理解。
單個(gè)View分析
首先我們需要先引發(fā)一個(gè)問(wèn)題。需要自定義一個(gè)View,重載構(gòu)造方法,并在Activity中實(shí)現(xiàn)兩個(gè)接口,然后來(lái)看一下他都點(diǎn)擊處理情況.
button= (WidgetButton) findViewById(R.id.btn_content);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.i(TAG,"事件");
}
});
button.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.i(TAG,"Touch");
return false;
}
});
當(dāng)我們點(diǎn)擊這個(gè)自定義控件時(shí)
I/widget: Touch
I/widget: Touch
I/widget: Touch
I/widget: 事件
但是如果我們將OnTouchListener中的OnTouch返回true
button.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.i(TAG,"Touch");
return true;
}
});
點(diǎn)擊控件的結(jié)果是
I/widget: Touch
I/widget: Touch
I/widget: Touch
好了問(wèn)題出現(xiàn)
為什么setOnTouchListener中的onTouch返回true后OnClick就不執(zhí)行了呢?我們點(diǎn)進(jìn)去看setOnTouchListener和setOnClickListener做了什么?
public void setOnTouchListener(OnTouchListener l) {
getListenerInfo().mOnTouchListener = l;
}
public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
這里發(fā)現(xiàn)將監(jiān)聽(tīng)賦給了mOnTouchListener和mOnClickListener,我們暫時(shí)記住這兩個(gè)變量名。
我們要知道當(dāng)我們點(diǎn)擊自定義View控件時(shí),首先會(huì)調(diào)用父類(lèi)(View)的dispatchTouchEvent方法。然后看一下這里邊幾句重要的代碼。
9999 boolean result = false;
...
10017 if (li != null && li.mOnTouchListener != null
10018 && (mViewFlags & ENABLED_MASK) == ENABLED
10019 && li.mOnTouchListener.onTouch(this, event)) {
10020 result = true;
10021 }
10023 if (!result && onTouchEvent(event)) {
result = true;
10025 }
這里我們可以看到之前記住的那個(gè)變量名mOnTouchListener,這里判斷如果它的onTouch返回true的時(shí)候result賦值為ture。(在源碼中如果有些變量不太明白干什么的,千萬(wàn)不要糾結(jié),這里判斷的這幾個(gè)條件都會(huì)滿(mǎn)足)繼續(xù)往下看,如過(guò)result為false,因?yàn)橛眠@里用的是&&所以在第一個(gè)條件未滿(mǎn)足的情況下是不會(huì)調(diào)用第二的條件的,但是重點(diǎn)就在這個(gè)第二個(gè)條件中(onTouchEvent方法)。
11185 case MotionEvent.ACTION_UP:
...
11216 performClick();
在performClick方法的5637行會(huì)調(diào)用 li.mOnClickListener.onClick(this); 是不是之前賦值的那個(gè)變量名。所以這里可以知道,當(dāng)mOnTouchListener.onTouch()為true時(shí)就不會(huì)調(diào)用onTouchEvent()方法,但是mOnClickListener.onClick()在onTouchEvent() - up事件 - performClick()方法中,所以也不會(huì)調(diào)用。
Activity - ViewGroup 分析
為什么要從Activity說(shuō)起呢。事件收集之后最先傳遞給 Activity, 然后依次向下傳遞。
首先自定義ViewGroup,我這里繼承的是RelativeLayout因?yàn)樗彩抢^承自ViewGroup并且沒(méi)有對(duì)觸摸事件進(jìn)行處理,然后重載構(gòu)造方法,并重寫(xiě)三個(gè)與觸摸事件有關(guān)的方法
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i(TAG," WidgetViewGroup onTouchEvent "+event.getAction());
return super.onTouchEvent(event);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.i(TAG," WidgetViewGroup onInterceptTouchEvent "+ev.getAction());
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.i(TAG," WidgetViewGroup dispatchTouchEvent"+ ev.getAction());
return super.dispatchTouchEvent(ev);
}
在MainActivity中重寫(xiě)兩個(gè)與觸摸事件有關(guān)的方法,為什么是兩個(gè)呢,因?yàn)樵贏ctivity中沒(méi)有Intercept事件,因?yàn)闆](méi)有意義,如果攔截了會(huì)導(dǎo)致點(diǎn)擊什么效果都沒(méi)有。
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.i(TAG," MainActivity dispatchTouchEvent "+ ev.getAction());
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i(TAG," MainActivity onTouchEvent "+ event.getAction());
return super.onTouchEvent(event);
}
點(diǎn)擊屏幕時(shí)輸出如下 這里0表示Down 2表示Move 1表示Up
I/widget: MainActivity dispatchTouchEvent 0
I/widget: WidgetViewGroup dispatchTouchEvent0
I/widget: WidgetViewGroup onInterceptTouchEvent 0
I/widget: WidgetViewGroup onTouchEvent 0
I/widget: MainActivity onTouchEvent 0
I/widget: MainActivity dispatchTouchEvent 2
I/widget: MainActivity onTouchEvent 2
I/widget: MainActivity dispatchTouchEvent 1
I/widget: MainActivity onTouchEvent 1
這樣就知道了觸摸事件之間執(zhí)行順序的情況。
好了問(wèn)題出現(xiàn)
為什么會(huì)這樣調(diào)用呢?那么先從Activity的dispatchTouchEvent看起。
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();//這個(gè)方法是個(gè)空方法,是給用戶(hù)進(jìn)行重寫(xiě)的
}
//這里說(shuō)明如果這個(gè)判斷返回true就不執(zhí)行Activity的onTouchEvent()方法
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
這里有簡(jiǎn)單的注釋?zhuān)桥袛嘀械姆椒ㄊ鞘裁茨??進(jìn)去看一下。
public abstract boolean superDispatchTouchEvent(MotionEvent event);
可以發(fā)現(xiàn)他是Window抽象類(lèi)的一個(gè)抽象方法,在文件開(kāi)始的注釋中說(shuō)明了Window抽象類(lèi)僅有一個(gè)實(shí)現(xiàn)類(lèi)PhoneWindow,那么就得去PhoneWidow找這個(gè)方法的實(shí)現(xiàn)。
PhoneWindow
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
這里調(diào)用的DecorView的方法
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
這里調(diào)用的父類(lèi)的方法,但是父類(lèi)FrameLayout并沒(méi)有這個(gè)方法,只好再去FrameLayout的類(lèi)ViewGroup中去找。最終在ViewGroup中找到了相應(yīng)的方法(dispatchTouchEvent),并做了很多操作,那么我們看一下做了什么操作。2145行開(kāi)始
//判斷是否有觸摸設(shè)備 比如觸摸筆一類(lèi)的
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
// If the event targets the accessibility focused view and this is it, start
// normal event dispatch. Maybe a descendant is what will handle the click.
//這是一個(gè)賦值功能 AccessibilityService 可以不用手指進(jìn)行點(diǎn)擊 比如搶紅包
if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
ev.setTargetAccessibilityFocus(false);
}
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {//事件安全檢查
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
/**
* 第一步:對(duì)于ACTION_DOWN進(jìn)行處理(Handle an initial down)
* 因?yàn)锳CTION_DOWN是一系列事件的開(kāi)端,當(dāng)是ACTION_DOWN時(shí)進(jìn)行一些初始化操作.
* 從源碼的注釋也可以看出來(lái):清除以往的Touch狀態(tài)(state)開(kāi)始新的手勢(shì)(gesture)
* cancelAndClearTouchTargets(ev)中有一個(gè)非常重要的操作:
* 將mFirstTouchTarget設(shè)置為null!!!!
* 隨后在resetTouchState()中重置Touch狀態(tài)標(biāo)識(shí)
* */
// Handle an initial down.
//Down為事件的開(kāi)始,所以這里判斷是不是事件的開(kāi)始
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.
//這個(gè)方法用于清理標(biāo)志,里邊有一個(gè)TouchTarget判讀是否是第一次觸發(fā)Down,如果是什么都不處理,如果不是將一些標(biāo)志全部置為初始化
cancelAndClearTouchTargets(ev);
//重置所有接觸狀態(tài),準(zhǔn)備一個(gè)新的觸摸循環(huán)
resetTouchState();
}
/**
* 第二步:檢查是否要攔截(Check for interception)
* 在dispatchTouchEvent(MotionEventev)這段代碼中
* 使用變量intercepted來(lái)標(biāo)記ViewGroup是否攔截Touch事件的傳遞.
* 該變量在后續(xù)代碼中起著很重要的作用.
*
* 攔截 intercepted =true
*/
// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {//正因?yàn)檫@個(gè)||所以在move事件是也能夠攔截
//這里的 (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0是否為true取決于 requestDisallowInterceptTouchEvent(?)
// 這個(gè)方法是用于是否允許父類(lèi)進(jìn)行攔截 true 為不讓父控件攔截。這里個(gè)人理解為判斷子View是否有調(diào)用這個(gè)方法。
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
//這里的攔截并沒(méi)有返回 只是將intercepted 設(shè)為 實(shí)現(xiàn)方法中返回的值 標(biāo)示是否做攔截
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;
}
/**
* 第三步:檢查cancel(Check for cancelation)
*
*/
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
// Update list of touch targets for pointer down, if needed.
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
/**
* 第四步:事件分發(fā)(Update list of touch targets for pointer down, if needed)
*/
//不是ACTION_CANCEL并且ViewGroup的攔截標(biāo)志位intercepted為false(不攔截)
//intercepted 未被攔截 如果攔截了會(huì)跳過(guò)這里邊的方法
if (!canceled && !intercepted) {
//之前說(shuō)的那個(gè)賦值功能
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
? findChildWithAccessibilityFocus() : null;
//判斷是否是Down事件 還有一個(gè) 多手指的判斷
//第二次Move事件時(shí)不會(huì)執(zhí)行這里的代碼 所以不會(huì)遍歷子控件,由于move事件頻繁調(diào)用 這是對(duì)move事件的一個(gè)優(yōu)化
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int actionIndex = ev.getActionIndex(); // always 0 for down
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
// Clean up earlier touch targets for this pointer id in case they
// have become out of sync.
// 移除所有的多手指操作
removePointersFromTouchTargets(idBitsToAssign);
final int childrenCount = mChildrenCount;
//如果當(dāng)前含有子控件
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
// Find a child that can receive the event.
// Scan children from front to back.
//重排序 按找Z來(lái)排序
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
//是否是用戶(hù)自己排序
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
//事件分發(fā) 倒序 遍歷 重排序后的集合 后添加的先接收
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
//拿到第i個(gè)View 相當(dāng)于 preorderedList.get(childIndex )
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;
}
//這里判斷這個(gè)View能不能夠接收事件
//clickable Invisiable 點(diǎn)擊事件不在 view范圍內(nèi)(通過(guò)pointInView) 正在動(dòng)畫(huà)
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
/**
* 執(zhí)行到了下面
* child 絕對(duì)會(huì)接受到事件
*/
//如果只分析Down操作 這里返回空 如果是move它就不為null
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
// Child is already receiving touch within its bounds.
// Give it the new pointer in addition to the ones it is handling.
//如果能夠找到接收事件的target直接break
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
/**
* 真正做事件分發(fā)
* child 不為空
*
*
* 如果子類(lèi) onTouch 返回true (根據(jù)子類(lèi)onTouchEvent 來(lái)返回)
*/
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();
//將child添加到Target 并賦給mFirstTouchTarget 做為將要接收move事件的view
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
// The accessibility focus didn't handle the event, so clear
// the flag and do a normal dispatch to all children.
ev.setTargetAccessibilityFocus(false);
}
if (preorderedList != null) preorderedList.clear();
}
if (newTouchTarget == null && mFirstTouchTarget != null) {
// Did not find a child to receive the event.
// Assign the pointer to the least recently added target.
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
// Dispatch to touch targets.
//如果該ViewGroup攔截事件為true 那么mFirstTouchTarget 為null (會(huì)跳過(guò)上面的方法直接到這)
//如果沒(méi)有攔截并且點(diǎn)擊鎖定子view那么mFilrstTouchTarget不為null
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
/**
* 真正做事件分發(fā)
* child 為空
*/
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
// Dispatch to touch targets, excluding the new touch target if we already
// dispatched to it. Cancel touch targets if necessary.
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
//如果第三個(gè)參數(shù)為null將會(huì)調(diào)用ViewGroup的onTouchEvent(),如果不為null將不會(huì)調(diào)用ViewGroup的onTouchEvent()調(diào)用的是子View的onTouchEvent()
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
// Update list of touch targets for pointer up or cancel, if needed.
if (canceled
|| actionMasked == MotionEvent.ACTION_UP
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
resetTouchState();
} else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
final int actionIndex = ev.getActionIndex();
final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
removePointersFromTouchTargets(idBitsToRemove);
}
}
if (!handled && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
}
return handled;
這就是整個(gè)dispatchTouchEvent()的代碼,重要的都做了注釋。還有一部分重要的代碼是
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
...
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
transformedEvent.offsetLocation(offsetX, offsetY);
if (! child.hasIdentityMatrix()) {
transformedEvent.transform(child.getInverseMatrix());
}
handled = child.dispatchTouchEvent(transformedEvent);
}
}
之前一直提到的真正的事件分布,傳view和不傳view 的情況,如過(guò)view為Null調(diào)用ViewGroup的super.dispatchTouchEvent(),相當(dāng)于調(diào)用ViewGroup的onTouchEvent(),如果不為null這調(diào)用子view的dispatchTouchEvent()。
經(jīng)過(guò)源碼的分析,可以簡(jiǎn)化一個(gè)偽源碼,如下:
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
if (!onInterceptTouchEvent(ev)) {//如果沒(méi)有攔截
//之前會(huì)判斷是否存在這個(gè) 符合要求的child
//這個(gè)返回結(jié)果相當(dāng)于子View的onTouchEvent的返回結(jié)果
if(child.dispatchTouchEvent(transformedEvent)){
//target是鎖定事件的那個(gè)view之后的move事件就發(fā)生在它身上 mFirstTouchTarget 默認(rèn)為null
mFirstTouchTarget = target;
}
}
//這個(gè)判斷相當(dāng)于沒(méi)有攔截或不存在符合條件的子view
if(mFirstTouchTarget == null){
handled = onTouchEvent();//調(diào)用自身的onTouchEvent();
}else{
handled = true;
}
return handled;
}
通過(guò)源碼可以直接的了解出 Activity - ViewGroup - View 的一個(gè)事件傳遞情況。這樣也就解釋了上面的問(wèn)題(個(gè)人理解,如果是ViewGroup - ViewGroup 將會(huì)是一個(gè)遞歸的情況)
總結(jié)
- 事件分發(fā)中用到了責(zé)任鏈模式,上層View可以攔截事件自己處理,也可以發(fā)布給子View,如果子View處理不了還可以返回到上層View進(jìn)行處理,既保證了事件的有序性,又非常的靈活。
- View 的 dispatchTouchEvent 主要用于調(diào)度自身的監(jiān)聽(tīng)器和 onTouchEvent。
- View的事件的調(diào)度順序是 onTouchListener > onTouchEvent > onLongClickListener > onClickListener 。
- 不論 View 自身是否注冊(cè)點(diǎn)擊事件,只要 View 是可點(diǎn)擊的就會(huì)消費(fèi)事件。
- 事件是否被消費(fèi)由返回值決定,true 表示消費(fèi),false 表示不消費(fèi),與是否使用了事件無(wú)關(guān)。
- ViewGroup 中可能有多個(gè) ChildView 時(shí),將事件分配給包含點(diǎn)擊位置的 ChildView。
- 只要接受 ACTION_DOWN 就意味著接受所有的事件,拒絕 ACTION_DOWN 則不會(huì)收到后續(xù)內(nèi)容。
- (源碼理解)ViewGroup的dispatchTouchEvent 方法處理了所有觸摸操作,onInterceptTouchEvent和onTouchEvent這兩個(gè)方法并沒(méi)有返回結(jié)果,只是返回true、false告訴dispatchTouchEvent應(yīng)該做啥。
這篇文章是在我學(xué)習(xí)的基礎(chǔ)上進(jìn)行了總結(jié),可想而知我還是個(gè)很小的菜鳥(niǎo),如果其中有錯(cuò)誤還請(qǐng)指出,我會(huì)盡快修改文章,并改正自己的理解,謝謝。