觸摸反饋,事件分發(fā)機(jī)制
觸摸反饋是事件分發(fā)機(jī)制是永遠(yuǎn)都繞不開的話題,也是一切的基礎(chǔ)和理論。網(wǎng)上也有講的很好的,大家多多少少也有自己的理解。我這邊也就寫一些的我理解,和一些別人總結(jié)比較好的東西。
總結(jié)(如果你能全部想通下面所有的總結(jié),說明對(duì)事件分發(fā)比較了解,總結(jié)之后的內(nèi)容就不需要看)
- 同一個(gè)事件序列是指從手指接觸屏幕的那一刻起,到手指離開屏幕的那一刻結(jié)束,在這個(gè)過程中所產(chǎn)生的一系列事件,這個(gè)事件序列從down事件開始,中間含有數(shù)量不定的move事件,最終以u(píng)p事件結(jié)束
- 正常情況下,一個(gè)事件序列只能被一個(gè)View攔截且消耗。因?yàn)橐坏┮粋€(gè)元素?cái)r截了某此事件,那么同一個(gè)事件序列內(nèi)的所有事件都會(huì)直接交個(gè)它處理,因此同一個(gè)事件序列中的事件不能分別由兩個(gè) View同時(shí)處理,但是通過特殊手段可以做到(利用傳遞序列中上一個(gè)ViewGroup的onInterceptTouchEvent),比如一個(gè)View將本該自己處理的事件通過onTouchEvent強(qiáng)行傳遞給其他View處理。
- 某個(gè)View一旦決定攔截,那么這一個(gè)事件序列都只能由它來處理(如果事件序列能傳遞到它的話),并且它的onInterceptTouchEvent不會(huì)再被調(diào)用。這條也很好理解,就是說當(dāng)一個(gè)View決定攔截一個(gè)事件后,那么系統(tǒng)會(huì)把同一個(gè)事件序列內(nèi)的其他方法都直接交給它來處理,因此就不用再調(diào)用這個(gè)View的onInterceptTouchEvent去詢問他是否要攔截。
- 某個(gè)View一旦開始處理事件,如果它不消耗ACTION_DOWN事件(onTouchEvent返回false),那么同一事件序列中的其他事件都不會(huì)再交給它處理。并且事件將重新交由它的父元素去處理,即父元素的onTouchEvent會(huì)被調(diào)用。意思是事件一旦交給一個(gè)View處理,它必須消耗掉,否則同一事件序列中剩下的事件就不再交個(gè)它來處理了。
- ViewGroup默認(rèn)不攔截任何事件。Android源碼中ViewGroup的onInterceptTouchEvent方法就會(huì)默認(rèn)返回false。
- View沒有onInterceptTouchEvent方法,一旦有點(diǎn)擊事件傳遞給它,那么它的onTouchEvent方法就會(huì)被調(diào)用。
- View的enable屬性不影響onTouchEvent的默認(rèn)返回值。哪怕一個(gè)View是diable狀態(tài),只要它的clickable或者longClickable有一個(gè)為true,那么它的onTouchEvent就會(huì)返回true。
- onClick會(huì)發(fā)生的前提是當(dāng)前View是可點(diǎn)擊的,并且它收到了down和up的事件。
- 事件傳遞過程由外向內(nèi)的,即事件總是先傳遞給父元素,然后再由父元素分發(fā)給子View,通過requestDisallowInterceptTouchEvent方法可以在子元素中干預(yù)父元素的事件分發(fā)過程,但是ACTION_DOWN事件除外。
上面的很多結(jié)論會(huì)在下面的分發(fā)流程,源碼解析中出現(xiàn)。
當(dāng)手指按下ACTION_DOWN,會(huì)進(jìn)行事件分發(fā)的流程
下面么有3張圖,從簡(jiǎn)單到詳細(xì)的表描述事件的分發(fā)流程。
其實(shí)我們常常說事件分發(fā)有三個(gè)事件,其實(shí)dispatchTouchEvent事件中包含onInterceptTouchEvent和onTouchEvent。當(dāng)然view中是沒有onInterceptTouchEvent事件的。

可以通過代碼來概括這個(gè)流程:
public boolean dispatchTouchEvent(){
boolean result = false;
if(onInterceptTouchEvent()){
result = onTouchEvent();
}else{
result = child.dispatchTouchEvent();
}
return result;
}
圖二先對(duì)第一幅更加詳細(xì),整個(gè)流程從Activity的dispatchTouchEvent開始,總體東西是差不多的。

圖三是圖二中各個(gè)流程中用到源碼方法的詳情的介紹。

ACTION_DOWN后事件系列何去何從
ACTION_DOWN事件在哪個(gè)控件消費(fèi)了(return true), 那么ACTION_MOVE和ACTION_UP就會(huì)從上往下(通過dispatchTouchEvent)做事件分發(fā)往下傳,就只會(huì)傳到這個(gè)控件,不會(huì)繼續(xù)往下傳,如果ACTION_DOWN事件是在dispatchTouchEvent消費(fèi),那么事件到此為止停止傳遞,如果ACTION_DOWN事件是在onTouchEvent消費(fèi)的,那么會(huì)把ACTION_MOVE或ACTION_UP事件傳給該控件的onTouchEvent處理并結(jié)束傳遞。
紅色的箭頭代表ACTION_DOWN 事件的流向
藍(lán)色的箭頭代表ACTION_MOVE 和 ACTION_UP 事件的流向


某個(gè)View一旦決定攔截,那么這一個(gè)事件序列都只能由它來處理(如果事件序列能傳遞到它的話),并且它的onInterceptTouchEvent不會(huì)再被調(diào)用。這條也很好理解,就是說當(dāng)一個(gè)View決定攔截一個(gè)事件后,那么系統(tǒng)會(huì)把同一個(gè)事件序列內(nèi)的其他方法都直接交給它來處理,因此就不用再調(diào)用這個(gè)View的onInterceptTouchEvent去詢問他是否要攔截。

View.dispatchTouchEvent
dispatchTouchEvent中這段代代碼,可以知道會(huì)先去檢查onTouch()的返回值,如果為false,再去給機(jī)會(huì)調(diào)用onTouchEvent(),所以onTouch方法的優(yōu)先級(jí)大于onTouchEvent()。
public boolean dispatchTouchEvent(MotionEvent event) {
......
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;
}
}
......
return result;
}
View.onTouchEvent
首先介紹一個(gè)在下面會(huì)用的TouchTarget(記錄每個(gè)子 View 是被哪些 pointer(?指)按下的,是單向鏈表)
第一步:判斷view是否clickable,無論是CLICKABLE,LONG_CLICKABL,ECONTEXT_CLICKABLE都是clickable
第二步:如果view是clickable的,及時(shí)是disable,只是不做出響應(yīng),但是依然會(huì)消耗事件。
第三步:如果view是clickable以及設(shè)置tooltip,則進(jìn)入下面判斷點(diǎn)擊,長(zhǎng)按的監(jiān)聽檢測(cè)以及處理。下面的分析中我就不分析tooltip的情況,源碼中有很多代碼是因?yàn)榧嫒輙ooltip添加。
- ACTION_DOWN: isInScrollingContainer()方法判斷是否在滑動(dòng)組件中,如果在滑動(dòng)組件isInScrollingContainer置為true。如果在滑動(dòng)組件中,延遲設(shè)置按下的反饋100毫秒,檢查是否要滑動(dòng);如果不在滑動(dòng)組件中,立即顯示按下的反饋并檢查長(zhǎng)按。
- ACTION_MOVE:這里相對(duì)簡(jiǎn)單就是如果手指移到外面就會(huì)取消掉tap和長(zhǎng)按的回調(diào)等一系列后續(xù)處理,pointInView(x, y, mTouchSlop),mTouchSlop會(huì)有一個(gè)手指觸摸到外面的余量
- ACTION_UP: if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed)如果之前是按下或者預(yù)按下則進(jìn)入下面的判斷。首先獲取焦點(diǎn),然后如果之前是是預(yù)按下的設(shè)置按下setPressed(true, x, y),包括在抬起前做延遲給一個(gè)按下的效果。如果符合if (!mHasPerformedLongPress && !mIgnoreNextUpEvent)的判斷中,說明是一個(gè)點(diǎn)擊事件執(zhí)行PerformClick。
/**
* Implement this method to handle touch screen motion events.
* <p>
* If this method is used to detect click actions, it is recommended that
* the actions be performed by implementing and calling
* {@link #performClick()}. This will ensure consistent system behavior,
* including:
* <ul>
* <li>obeying click sound preferences
* <li>dispatching OnClickListener calls
* <li>handling {@link AccessibilityNodeInfo#ACTION_CLICK ACTION_CLICK} when
* accessibility features are enabled
* </ul>
*
* @param event The motion event.
* @return True if the event was handled, false otherwise.
*/
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
//第一步 context_clickable內(nèi)容可點(diǎn)擊
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
//第二步
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return clickable;
}
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
//第三步
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
if ((viewFlags & TOOLTIP) == TOOLTIP) {
handleTooltipUp();
}
if (!clickable) {
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
break;
}
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)) {
performClick();
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
mUnsetPressedState.run();
}
removeTapCallback();
}
mIgnoreNextUpEvent = false;
break;
case MotionEvent.ACTION_DOWN:
if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
}
mHasPerformedLongPress = false;
if (!clickable) {
checkForLongClick(0, x, y);
break;
}
if (performButtonActionOnTouchDown(event)) {
break;
}
// Walk up the hierarchy to determine if we're inside a scrolling container.
boolean isInScrollingContainer = isInScrollingContainer();
// For views inside a scrolling container, delay the pressed feedback for
// a short period in case this is a scroll.
if (isInScrollingContainer) {
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// Not inside a scrolling container, so show the feedback right away
setPressed(true, x, y);
checkForLongClick(0, x, y);
}
break;
case MotionEvent.ACTION_CANCEL:
if (clickable) {
setPressed(false);
}
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
break;
case MotionEvent.ACTION_MOVE:
if (clickable) {
drawableHotspotChanged(x, y);
}
// Be lenient about moving outside of buttons
if (!pointInView(x, y, mTouchSlop)) {
// Outside button
// Remove any future long press/tap checks
removeTapCallback();
removeLongPressCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
}
break;
}
return true;
}
return false;
}
ViewGroup.dispatchTouchEvent
整體流程結(jié)構(gòu)如下
- 如果是?戶初次按下(ACTION_DOWN),清空 TouchTargets 和 DISALLOW_INTERCEPT 標(biāo)記
- 攔截處理
- 如果不攔截并且不是 CANCEL 事件,并且是 DOWN 或者 POINTER_DOWN,嘗試把
pointer(?指)通過 TouchTarget 分配給子View;并且如果分配給了新的子 View,調(diào)?
child.dispatchTouchEvent() 把事件傳給子View - 看有沒有 TouchTarget
如果沒有,調(diào)用自己的 super.dispatchTouchEvent()
如果有,調(diào)用child.dispatchTouchEvent() 把事件傳給對(duì)應(yīng)的子 View(如果有的話) - 如果是 POINTER_UP,從 TouchTargets 中清除 POINTER 信息;如果是 UP 或 CANCEL,重置
狀態(tài)
public boolean dispatchTouchEvent(MotionEvent ev) {
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.
// 輔助功能 事件將會(huì)第一個(gè)派發(fā)給開啟了accessibility focused的view
if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
ev.setTargetAccessibilityFocus(false);
}
boolean handled = false;
// 表示窗口是否為模糊窗口(FILTER_TOUCHES_WHEN_OBSCURED),如果是窗口,則表示不希望處理該事件。(如dialog后的窗口)
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
// 過濾字段的最后8bit,也就是指只關(guān)心是ACTION_DOWN、ACTION_UP等事件,而不關(guān)心是哪個(gè)手指引起的。
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.
// 初始化相關(guān)狀態(tài)
//(01) 清空mFirstTouchTarget鏈表,并設(shè)置mFirstTouchTarget為null。mFirstTouchTarget是"接受觸摸事件的View"所組成的單鏈表
//(02) 清空mGroupFlags的FLAG_DISALLOW_INTERCEPT標(biāo)記,如果設(shè)置了FLAG_DISALLOW_INTERCEPT,ViewGroup對(duì)觸摸事件進(jìn)行攔截。
//(03) 清空mPrivateFlags的PFLAG_CANCEL_NEXT_UP_EVEN標(biāo)記,作用是將下一個(gè)時(shí)間變?yōu)镃ancel
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// Check for interception.
final boolean intercepted;
// 如果為DOWN事件,或者mFirstTouchTarget為null(那么事件直接給到自己),就沒必要執(zhí)行攔截。
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
// 查看是否設(shè)置了,禁止攔截的標(biāo)記
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
// ViewGroup的onInterceptTouchEvent不執(zhí)行攔截,除非子類重寫了該方法(如listview)
intercepted = onInterceptTouchEvent(ev);
// 僅僅是避免action被篡改過。
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;
}
// If intercepted, start normal event dispatch. Also if there is already
// a view that is handling the gesture, do normal event dispatch.
if (intercepted || mFirstTouchTarget != null) {
ev.setTargetAccessibilityFocus(false);
}
// Check for cancelation.
// 查看時(shí)候被標(biāo)記了PFLAG_CANCEL_NEXT_UP_EVENT 或者 當(dāng)前是一個(gè)Cancel事件
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
// Update list of touch targets for pointer down, if needed.
// 比如我們多個(gè)手指放到了屏幕上,是否要將第二個(gè)手指的事件下面下去
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
if (!canceled && !intercepted) {
// If the event is targeting accessiiblity focus we give it to the
// view that has accessibility focus and if it does not handle it
// we clear the flag and dispatch the event to all children as usual.
// We are looking up the accessibility focused host to avoid keeping
// state since these events are very rare.
dWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
? findChildWithAccessibilityFocus() : null;
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.
// 清除Targets中相應(yīng)的pointer ids
removePointersFromTouchTargets(idBitsToAssign);
// 遍歷所有的child,將事件派發(fā)下去
final int childrenCount = mChildrenCount;
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.
// 找到一個(gè)可以接受事件的子view,從上面往下面找
// 以 1)Z軸(5.0系統(tǒng)引入) 2)draw的順序 進(jìn)行排序
final ArrayList<View> preorderedList = buildOrderedChildList();
// 可以理解為,是否按照draw的順序(因?yàn)?,buildOrderedChildList在都沒有設(shè)置Z的情況下返回null)
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
// 這里兩端代碼,簡(jiǎn)單的理解根據(jù)不同的排列選項(xiàng)(1、view添加到 2、view的draw順序 3、viewZ 軸順序)
final int childIndex = customOrder
? getChildDrawingOrder(childrenCount, i) : i;
final View child = (preorderedList == null)
? children[childIndex] : preorderedList.get(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.
// 如果存在開啟了AccessibilityFocus 的view
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
// 如果正是當(dāng)前的childView開啟了AccessibilityFocus,直接將i指向最后一個(gè)元素
// 和 break的區(qū)別是,還將執(zhí)行后面的代碼,但是不會(huì)再進(jìn)行循環(huán)了
i = childrenCount - 1;
}
// canViewReceivePointerEvents 判斷child是否為visiable 或者 是否有動(dòng)畫
// isTransformedTouchPointInView 判斷x, y是否在view的區(qū)域內(nèi)(如果是執(zhí)行了補(bǔ)間動(dòng)畫 則x,y會(huì)通過獲取的matrix變換值
// 換算當(dāng)相應(yīng)的區(qū)域,這也是為什么補(bǔ)間動(dòng)畫的觸發(fā)區(qū)域不隨著動(dòng)畫而改變)
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
// getTouchTarget 查找child是否已經(jīng)記錄在mFirstTouchTarget這個(gè)單鏈表中
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.
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
// 簡(jiǎn)單的理解,dispatchTransformedTouchEvent就是將相應(yīng)的事件傳遞下去
// 不過需要注意一點(diǎn)的就是,event被傳遞給child的時(shí)候?qū)?huì)做相應(yīng)偏移,如下
// final float offsetX = mScrollX - child.mLeft;
// final float offsetY = mScrollY - child.mTop;
// event.offsetLocation(offsetX, offsetY);
// 為什么要做偏移呢? 因?yàn)閑vent的getX得到的值是,childView到parentView邊境的距離,是一個(gè)相對(duì)值
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// dispatchTransformedTouchEvent交個(gè)childView去做dispatchTouchEvent
//if (child == null) {
// handled = super.dispatchTouchEvent(event);
//} else {
// handled = child.dispatchTouchEvent(event);
//}
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
// childIndex points into presorted list, find original index
// 找到childIndex所代表的child的最原始的index【?】看代碼,children和mChildren指向同一鏈表
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
// 將相應(yīng)該事件的child包裝成一個(gè)Target,添加到mFirstTouchTarget的鏈表中
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();
}
// 如果沒有child響應(yīng)該事件,則將此事件交給最近加入的target
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;
}
}
}
// mFirstTouchTarget == null 表示,沒有能響應(yīng)該事件的child,那么就調(diào)用父類(也就是View)的dispatchTouchEvent
// 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;
// 表示在Down事件處理中,已經(jīng)將這個(gè)事件交給newTouchTarget處理過了,就不重復(fù)處理了
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
// 再次判定是否需要cancel(被標(biāo)記為cancel 或者 事件被攔截)
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
// 問:難道看到這里你們會(huì)不會(huì)產(chǎn)生一個(gè)疑問,如果parent在ACTION_MOVE過程中攔截了該事件,哪里處理呢?
// 答:如果攔截了該事件,還是需要自身 dispatchTransformedTouchEvent 函數(shù)將事件交個(gè)自己的onTouchEvent
// 此外dispatchTransformedTouchEvent完成上述操作需要一個(gè)條件,也就是child形參數(shù)為null
// 問:那么怎么為null呢?
// 答:往上面看10幾行,不就是了嗎?
// 那么如何達(dá)到,其實(shí)條件就是 mFirstTouchTarget == null, 請(qǐng)看下面的分析
// 如果intercepted == true的情況下, cancelChild == true, predecessor == null
// 從而使得mFirstTouchTarget 一直 -> next,當(dāng)target遍歷到最后的時(shí)候,next == null,從而使得mFirstTouchTarget == null。
// 問: 稍等,這里僅僅做了將mFirstTouchTarget 設(shè)置了為null,那么如何派發(fā)給自己的onTouchEvent呢?
// 這個(gè)只能等下一個(gè)事件過來了
// 結(jié)論【事件攔截,攔截了該事件,并沒有將本次這個(gè)事件傳遞給自身的onTouchEvent,而需要等到下次】
// 問:如何驗(yàn)證
// 答:重新FrameLayout的 onInterceptTouchEvent 和 onTouchEvent 將相應(yīng)的event.getEventTime打印出來,
// 將會(huì)發(fā)現(xiàn)攔截的事件和傳遞到onTouchEvent的時(shí)間不是一個(gè)時(shí)間。
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.
// cancel ACTION_UP ACTION_HOVER_MOVE(表示鼠標(biāo)滑動(dòng))等,清理狀態(tài)
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);
}
}
// 調(diào)試使用,可以忽略
if (!handled && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
}
return handled;
}
}
如何成為自定義高手(一)繪制
如何成為自定義高手(二)動(dòng)畫
如何成為自定義高手(三)布局
如何成為自定義高手(四)觸摸反饋,事件分發(fā)機(jī)制
如何成為自定義高手(五)多點(diǎn)觸摸
如何成為自定義高手(六)滑動(dòng)和拖拽
如何成為自定義高手(七)滑動(dòng)沖突