Android事件分發(fā)機(jī)制?ViewGroup分析
前言:
事件分發(fā)從手指觸摸屏幕開(kāi)始,即產(chǎn)生了觸摸信息,被底層系統(tǒng)捕獲后會(huì)傳遞給Android的輸入系統(tǒng)服務(wù)IMS,通過(guò)Binder把消息發(fā)送到activity,activity會(huì)通過(guò)phoneWindow、DecorView最終發(fā)送給ViewGroup。這里就直接分析ViewGroup的事件分發(fā)
整體流程

配合圖在看一段偽代碼:
public boolean dispatchTouchEvent(MotionEvent ev) :Boolean{
val result = false //處理結(jié)果,默認(rèn)是沒(méi)消費(fèi)過(guò)的
if (!onInterceptTouchEvent(ev)){ //是否攔截
result = child.dispatchTouchEvent(ev) // 分發(fā)給子view處理
}
if (!result){ //事件沒(méi)有消費(fèi)
if (onTouchListener != null) { //先詢問(wèn)是否設(shè)置了onTouchListener
result = onTouchListener.onTouch(ev)
}
if (!result) { //還是沒(méi)有消費(fèi)就交給onTouchEvent處理
result = onTouchEvent(ev)
}
}
return result
}這張圖和這段偽代碼實(shí)際上已經(jīng)概括了ViewGroup和View對(duì)事件處理的整個(gè)流程,注意只有ViewGroup有攔截機(jī)制即onInterceptTouchEvent
源碼分析
在分析源碼之前先了解個(gè)基本概念 同一事件序列:同一個(gè)事件序列是指從手指接觸屏幕的那一刻起,到手指離開(kāi)屏幕的那一刻結(jié)束,在這個(gè)過(guò)程中所產(chǎn)生的一系列事件,這個(gè)事件序列以down事件開(kāi)始,中間含有數(shù)量不定的move事件,最終以u(píng)p事件結(jié)束
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
/**
* step1
* ACTION_DOWN是一個(gè)系列事件的起點(diǎn),終點(diǎn)是ACTION_UP
* 如果是ACTION_DOWN會(huì)重置一些flag并且會(huì)把mFirstTouchTarget置空
*/
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
resetTouchState();
}
final boolean intercepted;//變量判斷消息是否被攔截
/**
* step2
* 從以下代碼可以看出如果事件不是ACTION_DOWN并且mFirstTouchTarget為空的話那么ViewGroup是不能再攔截同一系列的事件了
* mFirstTouchTarget 代表的就是一個(gè)單鏈表,它會(huì)把處理當(dāng)前這一系列事件的view保存下來(lái)
* 假如當(dāng)前事件是ACTION_MOVE,并攔截了該事件那么會(huì)在step9中把mFirstTouchTarget置空
*
* 結(jié)論1:
* 如果View決定攔截一個(gè)事件那么該View的 onInterceptTouchEvent 方法不會(huì)再被調(diào)用了,
* 同一序列事件后續(xù)的所有事件都只能由該View處理(當(dāng)然前提是事件能分發(fā)到該view,有可能在上層被攔截了)
*/
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
/**
* disallowIntercept表示是否禁用攔截功能,子view通過(guò) requestDisallowInterceptTouchEvent 方法
* 可以要求父view不準(zhǔn)攔截事件,不過(guò)該方法在MotionEvent.ACTION_DOWN事件中不起作用,因?yàn)樵趕tep1中會(huì)把所有標(biāo)志位重置
*
*/
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
/**
* 如果進(jìn)不到上面的if判斷則表示當(dāng)前系列事件viewGroup已經(jīng)攔截過(guò)某個(gè)事件了
* intercepted 直接置為true
*/
intercepted = true;
}
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
final boolean isMouseEvent = ev.getSource() == InputDevice.SOURCE_MOUSE;
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0
&& !isMouseEvent;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
/**
* step3
* 看這里如果ViewGroup攔截了該事件則不會(huì)進(jìn)入step3里面了,而是直接走到step9中
*/
if (!canceled && !intercepted) {
/**
* step4
* 這里我們只考慮單指的點(diǎn)擊、移動(dòng)和抬起
* ACTION_POINTER_DOWN和多點(diǎn)觸控有關(guān),ACTION_HOVER_MOVE和鼠標(biāo)有關(guān)
* 所以如果當(dāng)前事件是MOVE也不會(huì)走step4也是直接走到step9中找到對(duì)應(yīng)的子view繼而分發(fā)事件
* 結(jié)論2:如果DOWN事件被某個(gè)view消耗那么后續(xù)的事件都會(huì)直接交給這個(gè)view(前提是父view沒(méi)有攔截)
*/
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;
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final float x =
isMouseEvent ? ev.getXCursorPosition() : ev.getX(actionIndex);
final float y =
isMouseEvent ? ev.getYCursorPosition() : ev.getY(actionIndex);
// Find a child that can receive the event.
// Scan children from front to back.
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
/**
* step5
* 遍歷所有的子view
*/
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
//省略部分代碼。。。
/**
* step6
* 當(dāng)找到一個(gè)合適的子view時(shí),在 dispatchTransformedTouchEvent 中會(huì)調(diào)用子view的dispatchTouchEvent
* 如果該子view消耗了事件,會(huì)把子view保存到mFirstTouchTarget對(duì)應(yīng)的鏈表中,并結(jié)束for循環(huán)
*
* 結(jié)論3:
* 如果一個(gè)view沒(méi)有消耗DOWN事件那么后續(xù)的事件都不會(huì)再分發(fā)給該view
* 該結(jié)論和結(jié)論2呼應(yīng)上了,因?yàn)樵谶@個(gè)for循環(huán)中只有子view的 dispatchTransformedTouchEvent返回true才會(huì)被加入到鏈表中
* 下一次的事件并不會(huì)再到step4中來(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();
/**
* step7
* 把子view保存到鏈表中,mFirstTouchTarget指向表頭
* alreadyDispatchedToNewTouchTarget置為true
* 結(jié)束for循環(huán)
*/
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);
}
}
}
}
/**
* step8
* 如果攔截了事件會(huì)把 mFirstTouchTarget 置空這個(gè)時(shí)候就直接調(diào)用viewGroup的super.dispatchTouchEvent
* 即view中的dispatchTouchEvent
*/
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
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;
/**
* step9
* 如果攔截了就把mFirstTouchTarget置空,沒(méi)有攔截就找到對(duì)應(yīng)的childView把事件分發(fā)下去
*/
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
//注意這里cancelChild如果為true,并且target.child不為空的話,dispatchTransformedTouchEvent會(huì)把事件轉(zhuǎn)成CANCEL分發(fā)給target.child
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;
}
}
}
return handled;
}看下cancel事件的由來(lái),這里需要結(jié)合上文代碼step9看
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
final int oldAction = event.getAction();
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
/**
* 把事件轉(zhuǎn)換成ACTION_CANCEL
*/
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
/**
* 如果child不為空就分發(fā)給它
*/
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
return handled;
}到此這篇關(guān)于Android事件分發(fā)機(jī)制 ViewGroup分析的文章就介紹到這了,更多相關(guān)Android ViewGroup內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android studio編寫(xiě)簡(jiǎn)單的手電筒APP
一個(gè)簡(jiǎn)單的APP控制的手電筒代碼,android studio編寫(xiě)一個(gè)手電筒app,調(diào)用手機(jī)的閃光等實(shí)現(xiàn)手電筒的功能,感興趣的小伙伴們可以參考一下2016-08-08
Android使用MulticastSocket實(shí)現(xiàn)多點(diǎn)廣播圖片
這篇文章主要為大家詳細(xì)介紹了Android使用MulticastSocket實(shí)現(xiàn)多點(diǎn)廣播圖片,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-01-01
Android開(kāi)發(fā)實(shí)現(xiàn)生成excel的方法詳解
這篇文章主要介紹了Android開(kāi)發(fā)實(shí)現(xiàn)生成excel的方法,結(jié)合實(shí)例形式詳細(xì)分析了Android生成Excel的具體步驟與存儲(chǔ)、導(dǎo)入、添加等相關(guān)操作技巧,需要的朋友可以參考下2017-10-10
Android自定義View簡(jiǎn)易折線圖控件(二)
這篇文章主要為大家詳細(xì)介紹了Android自定義View簡(jiǎn)易折線圖控件的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-03-03

