首先針對上篇文章Android事件處理機(jī)制(1)-輸入事件做一個(gè)簡短的總結(jié)。
- onTouch方法優(yōu)先于onClick執(zhí)行
- 常見的MotionEvent的四種動作。
MotionEvent.ACTION_DOWN:手指按下屏幕的瞬間澜薄。
MotionEvent.ACTION_MOVE:手指在屏幕上移動
MotionEvent.ACTION_UP:手指離開屏幕瞬間
MotionEvent.ACTION_CANCEL:取消手勢 - onClick、onLongClick、onScroll等方法,是由多個(gè)Touch事件組成立镶。
用戶按下手指——>ACTION_DOWN——>用戶移動手指——>[ACTION_MOVE.. ACTION_MOVE]——>用戶抬起手指——>ACTION_UP。如果手指劃出View邊界還會出現(xiàn)ACTION_CANCEL类早。
Android中的事件處理一般包括事件分發(fā)->事件攔截->事件響應(yīng)三個(gè)步驟媚媒。其中上篇文章主要涉及的事件響應(yīng)的部分,本篇文章則主要講事件分發(fā)和事件攔截的流程涩僻。
事件分發(fā)
Android的視圖一般由Activity缭召、ViewGroup、和View3類組件構(gòu)成逆日,事件MotionEvent的傳遞順序是Activity->ViewGroup->View嵌巷。觸摸事件發(fā)生后,MotionEvent先傳到Activity室抽、再傳到ViewGroup搪哪、最終再傳到 View。Activity和ViewGroup通過dispatchTouchEvent(MotionEvent)方法分發(fā)觸摸事件坪圾,其內(nèi)部調(diào)用onTouchEvent(MotionEvent)方法處理點(diǎn)擊事件晓折。
Activity.dispatchTouchEvent解析
首先事件從Activity開始分發(fā),首先它讓W(xué)indow進(jìn)行事件分發(fā)兽泄,如果事件未被消費(fèi)漓概,則直接由Activity的onTouchEvent()消費(fèi)。
Window事件分發(fā)病梢,Window的實(shí)現(xiàn)類為PhoneWindow胃珍,PhoneWindow將事件直接傳遞給頂級DecorView進(jìn)行分發(fā)。
ViewGroup.dispatchTouchEvent解析
ViewGroup事件分發(fā)有兩種情況蜓陌。
- 不攔截事件觅彰,事件將沿著View層次嵌套結(jié)構(gòu)繼續(xù)向下分發(fā),直到事件被消費(fèi)护奈。如果向下分發(fā)過程中事件未被消費(fèi)缔莲,則事件將沿著原來的傳遞路徑向上傳遞,直到事件被消費(fèi)霉旗。
- 事件被攔截痴奏,將由攔截事件的ViewGroup來消費(fèi)事件,如果未消費(fèi)將事件向上傳遞厌秒,直到被消費(fèi)读拆。
這里需要注意的是,對于每次接收到ACTION_DOWN事件鸵闪,mFirstTouchTarget會置為空檐晕,F(xiàn)LAG_DISALLOW_INTERCEPT標(biāo)志位被重置,ViewGroup總是調(diào)用onInterceptTouchEvent()來判斷是否進(jìn)行攔截。因此如果ACTION_DOWN被攔截辟灰,那么后續(xù)其它事件都由它自身來處理个榕。如果ACTION_DOWN未被攔截,那么mFirstTouchTarget不為空芥喇,對于后續(xù)其它事件西采,子View通過調(diào)用ViewParent的requestDisallowInterceptTouchEvent()方法來控制對onInterceptTouchEvent()的調(diào)用。如果后續(xù)某個(gè)事件被攔截继控,子View會接收到ACTION_CANCEL事件械馆,該事件后續(xù)事件將由攔截該事件的ViewGroup來消費(fèi)。如果未攔截武通,則按照正常分發(fā)流程處理霹崎。
部分源碼如下:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
...//省略部分代碼
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
...//省略部分代碼
//關(guān)鍵點(diǎn)——ViewGroup進(jìn)行事件分發(fā)時(shí),需進(jìn)行條件判斷冶忱。判斷值1:disallowIntercept = 是否禁用事件攔截的功能(默認(rèn)是false)尾菇,子View可通過調(diào)用requestDisallowInterceptTouchEvent修改双仍。 判斷值2: !onInterceptTouchEvent(ev) = 對onInterceptTouchEvent()返回值取反
// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
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 {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
... //省略部分代碼
// 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);
} 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;
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;
}
/**
* Transforms a motion event into the coordinate space of a particular child view,
* filters out irrelevant pointer ids, and overrides its action if necessary.
* If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.
*/
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
...//省略部分代碼
//關(guān)鍵點(diǎn)——如果child為null崭倘,如點(diǎn)擊界面空白處,則調(diào)用父類即View.dispatchTouchEvent方法。如果child不為null眶拉,則調(diào)用child.dispatchTouchEvent。
// Perform any necessary transformations and dispatch.
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);
}
// Done.
transformedEvent.recycle();
return handled;
}
View.dispatchTouchEvent解析
如果OnTouchListener不為空并且enabled為true憔儿,事件由OnTouchListener消費(fèi)忆植,否則由onTouchEvent消費(fèi)。如果OnTouchListener的onTouch方法返回false谒臼,事件將也由onTouchEvent消費(fèi)朝刊。
具體可參考[Android事件處理機(jī)制(1)-輸入事件]
事件攔截
ViewGroup可通過onInterceptTouchEvent(MotionEvent)來監(jiān)視分派給ViewGroup的子視圖的事件,且可以進(jìn)行攔截蜈缤。
默認(rèn)情況下拾氓,ViewGroup的onInterceptTouchEvent方法返回false,則點(diǎn)擊事件優(yōu)先被分派給子試圖處理底哥;如果子試圖不能處理或者onInterceptTouchEvent方法返回true咙鞍,則交給父類View的dispatchTouchEvent來處理。
總結(jié)來說趾徽,Android事件處理機(jī)制由“由外到內(nèi)”分發(fā)续滋;“由內(nèi)到外”處理兩種形式構(gòu)成。
Android事件分發(fā)是先傳遞到ViewGroup孵奶,再由ViewGroup傳遞到View的疲酌。
在ViewGroup中可以通過onInterceptTouchEvent方法對事件傳遞進(jìn)行攔截,onInterceptTouchEvent方法返回true代表不允許事件繼續(xù)向子View傳遞,返回false代表不對事件進(jìn)行攔截朗恳,默認(rèn)返回false湿颅。
子View中如果將傳遞的事件消費(fèi)掉,ViewGroup中將無法接收到任何事件粥诫。
參考文檔:
Android事件分發(fā)機(jī)制完全解析肖爵,帶你從源碼的角度徹底理解(上)
Android事件分發(fā)機(jī)制完全解析,帶你從源碼的角度徹底理解(下)