??在android中事件分發(fā)很重要,也是比較難理解的。這里講事件分發(fā)記錄一下方便,方便以后溫故復習。本文我們就android中的view和viewgroup的事件分發(fā)機制進行剖析。
傳遞規(guī)則
??android中事件傳遞從activity 》window》doecorView 》ViewGroup》view。由此看出事件分發(fā)流程是由上而下,由外到內(nèi)。其核心就是ViewGroup和View 兩個層面的事件分發(fā)。事件分發(fā)的核心方法有三個dispatchTouchEvent()分發(fā)事件、onInterceptTouchEvent()是否攔截事件、onTouchEvent()處理事件。其中還穿插著onTouchListener的onTouch()事件處理和OnClickListener的onClick()點擊事件響應。,其中view因為沒有子View所以不涉及攔截事件,所以沒有onInterceptTouchEvent()方法;
事件分發(fā)流程
//事件分發(fā)偽代碼
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean result = false;
if (onInterceptTouchEvent(ev)) {
if (onTouchListener != null) {
result = onTouchListener.onTouch(view, ev);
}
if (!result) {
result = onTouchEvent(ev);
}
} else {
result = getChildAt(0).dispatchTouchEvent(ev);
}
return result;
}
//onTouchEvent偽代碼展示onClickListener觸發(fā)時機
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_UP:
if (onClickListener != null) {
onClickListener.onClick(view);
return true;
}
break;
default:
break;
}
return super.onTouchEvent(event);
}
通過以上源碼偽代碼我們可以看出在所有的實踐中onTouchListener的優(yōu)先級比較高,點擊事件的優(yōu)先級最低。優(yōu)先級順序為onTouchListener.onTouch() > onTouchEvent() >onClickListener.onClick();下面我們根據(jù)源碼解釋一下以上優(yōu)先級。
??源碼角度分析事件分發(fā)
ViewGroup事件分發(fā)
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Handle an initial down.初始化一個down事件
if (actionMasked == MotionEvent.ACTION_DOWN) {
//*******************標記***1*********************
//ACTION_DOWN是一個事件序列的起點,清空原有標記位
//清空標記的 mFirstTouchTarget(上一個事件處理子View)和清除FLAG_DISALLOW_INTERCEPT標記
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//*******************標記***2*********************
//如果是ACTION_DOWN或者子view已經(jīng)處理事件了 正常執(zhí)行事件分發(fā)邏輯。否則直接攔截返回true
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 {
intercepted = true;
}
// Check for cancelation.
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
//判斷是否取消和攔截事件
if (!canceled && !intercepted) {
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
//去過濾子控件是否處理事件
//*******************標記***3*********************
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
//*******************標記***4*********************
//如果child能夠接受事件,并且點擊事件在view的坐標區(qū)域內(nèi),交由子view處理,否則繼續(xù)查找其他子view
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
//子view分發(fā)處理事件
//*******************標記***5*********************
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
//子view處理事件將子view添加到mFirstTouchTarget中。
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
}
}
}
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
//*******************標記***6*********************
//沒有子view處理當前事件,則調(diào)用自身處理點擊事件。這里child參數(shù)為空,則執(zhí)行super.dispatchTouchEvent(event);自身處理觸摸事件
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
// 取消分發(fā)事件,排除新添加的TouchTarget和已經(jīng)分發(fā)過得,
.......
}
//*******************標記***7*********************
// 當取消事件、抬起事件等清空還原緩存觸摸標記
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);
}
}
return handled;
}
viewGroup事件分發(fā)流程解讀:
- 當一個事件到來如果是ACTION_DOWN事件,會復位之前的標記位(TouchTarget、FLAG_DISALLOW_INTERCEPT等)參照偽代碼中
標記1位置。 - 判斷如果不是ACTION_DOWN或者touchTarget為空,說明這不是一個新的事件序列或者該序列事件沒有子view處理,這直接攔截事件 intercepted = true;。否則則執(zhí)行
onInterceptTouchEvent()事件處理。參照偽代碼中標記2位置。 - 如果不攔截事件并且沒有取消,則當子view中查找,子view是否需要事件。參照偽代碼中
標記3位置。 - 子view判斷如果在事件坐標在view的 坐標區(qū)域內(nèi),則執(zhí)行dispatchTransformedTouchEvent()方法查看子view是否需要事件。參照偽代碼中
標記4位置。 - dispatchTransformedTouchEvent()方法如果child不為空,那么調(diào)用
child.dispatchTouchEvent(event);交由child處理事件,如果child為空調(diào)用super.dispatchTouchEvent(event);事件自己本身處理。 - 調(diào)用dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)后,如果返回ture說明子view處理事件。那么將child添加到touchTraget中。參照偽代碼中
標記5位置。 - mFirstTouchTarget 為空即沒有子view消費事件,那么直接調(diào)用dispatchTransformedTouchEvent()此時傳參child為null,那么執(zhí)行
super.dispatchTouchEvent(event);事件自己本身處理。參照偽代碼中標記6位置。 - 當取消事件、抬起事件等清空還原緩存觸摸標記參照偽代碼中
標記5位置。
ViewGroup的onTouchEvent繼承自View的不在單獨分析。onInterceptTouchEvent()一般默認false。其他控件根據(jù)自身需求自己定義,這里不做分析。
下面分析一下View的事件處理源碼:
View事件分發(fā)
public boolean dispatchTouchEvent(MotionEvent event) {
if (onFilterTouchEventForSecurity(event)) {
//如果設置了mOnTouchListener 則優(yōu)先執(zhí)行mOnTouchListener 的onTouch()方法。
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
//在執(zhí)行本身的onTouchEvent()方法
if (!result && onTouchEvent(event)) {
result = true;
}
}
return result;
}
//觸摸解析
public boolean onTouchEvent(MotionEvent event) {
//是否可點擊
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
//如果view不可用,直接返回點擊狀態(tài),如果可以點擊返回true,同樣會消耗事件。反之則返回false,不消費事件
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
//如果view不可用,但是同樣會消耗點擊事件
return clickable;
}
//只要是可點擊都會返回true
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
......
//響應點擊事件
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClick();
}
......
break;
......
}
return true;
}
return false;
}
view的dispatchTouchEvent()事件分發(fā)方法,因為沒有子view不存在傳遞事件,所以就很簡單。在view的時間分發(fā)中,如果view設置了onTouchListener那么先執(zhí)行onTouchListener.onTouch()方法先處理事件,如果沒有消費事件則執(zhí)行view的onTouchEvent()方法處理事件。
view的onTouchEvent()方法中
- 先判斷view是否可點擊(包含:點擊、長按等點擊標記)。
- 判斷view是否可用,如果view不可用,直接返回點擊狀態(tài),如果可以點擊返回true,同樣會消耗事件。反之則返回false,不消費事件。
- 在分析action中大部分都是在分析view的按壓等狀態(tài)。這里不做詳細描述。我們只看在MotionEvent.ACTION_UP中執(zhí)行了performClick()響應了設置的
mOnClickListener的onClick()方法。
事件分發(fā)結(jié)論:
- 一個事件序列是指當時手指按下到手指抬起中間的所有事件,也就是由down開始到up結(jié)束中間的所有事件。
- 正常情況下,同一個事件序列只能被一個view攔截且消費。
- 某個view一旦攔截(dispatchTouchEvent()方法返回true),這個事件序列都將由此view處理,并且就不會在執(zhí)行onInterceptTouchEvent()方法。
- 如果一個view攔截了某個事件,如果它不消費ACTION_DOWN事件(onTouchEvent()方法返回false),那么后續(xù)事件不會交給他處理,事件會向上返回交給父控件的onTouchEvent()處理。
- 如果view只消費ACTION_DOWN事件,那么其他的時間也不會交給父控件觸發(fā),并且當前view可以收到后續(xù)的事件,最終這些后續(xù)事件會返回給activity處理。
- View沒有分發(fā)事件方法,即沒有onInterceptTouchEvent()方法,會直接觸發(fā)onTouchEvent()方法。
- 事件分發(fā)是由外向內(nèi)的,有父控件向子控件傳遞,除非父控件攔截事件。子控件可以調(diào)用requestDisallowInterceptTouchEvent()方法請求父控件不要攔截事件。但是ACTION_DOWN除外。
- view事件分發(fā)的優(yōu)先級順序為onTouchListener.onTouch() > onTouchEvent() >onClickListener.onClick()