android SDK-25事件分發機制--原始碼正確解析
阿新 • • 發佈:2019-01-10
android SDK-25事件分發機制–原始碼正確解析
Android 事件分發分為View和ViewGroup的事件分發,ViewGroup比View過一個攔截判斷,viewgroup可以攔截事件,從而決定要不要把事件傳遞給子view,因為view沒有子view所以不存在攔截事件的情況。
事件分發主要從事件的分發,攔截,和處理三個函式的呼叫邏輯關係來分析。
public boolean dispatchTouchEvent(MotionEvent event) { } public boolean onInterceptTouchEvent(MotionEvent ev) { } public boolean onTouchEvent(MotionEvent event) { }
首先,螢幕上面一個點選事件,通過感測器捕獲到點選,然後知道把點選事件傳遞到activity 到PhoneWindow,再到,DecorView 最後就到我們自己在佈局檔案中的view或者viewgroup。
下面分析dispatchTouchEvent原始碼 :(sdk-25)(==>這個標記為重點,不用全懂,只要把==>這個標記處的邏輯理清楚就行)
@Override 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. if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) { ev.setTargetAccessibilityFocus(false); } //輔助功能點選事件的判斷和處理根本不用管,直接不要管,跳轉到下一個重點 // ==> 一個標誌,初始值為不處理,意識就是不處理MotionEvent boolean handled = false; if (onFilterTouchEventForSecurity(ev)) { final int action = ev.getAction(); final int actionMasked = action & MotionEvent.ACTION_MASK; // Handle an initial down. // ==> 處理第一個點選事件(事件分為,點選事件,move事件,up事件,所以點選事件是第一個事件) 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. // ==> 如果是點選事件,說明是一個新的事件序列開始了(按下,移動,手指擡起離開螢幕) // 由於是新的事件序列開始,所以要重置一些引數,和清空一下資料等等操作。 cancelAndClearTouchTargets(ev); resetTouchState(); } // Check for interception. final boolean intercepted; // ==> 判斷要不要打斷,如果不是點選事件,並且mFirstTouchTarget為null,則該viewgroup則打斷攔截事件。 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; } // 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. 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; // ==>如果沒有返回並且沒有被攔截,則進行分發 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. View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus() ? findChildWithAccessibilityFocus() : null; // ==>如果是down事件,進行遍歷子view,並且把事件分發給子view,前提是down事件的坐 標必須在子view中等等條件 ==>這句話,請把下面的原始碼分析完再一定回來看一看,你要思考一下,這裡如果是down事件才 會進去,如果是move事件那麼不會進去分發了,其實往後看原始碼會發現,在down事件分發給 一個child,如果這個child消費了這個down事件,那麼這個child就會被儲存起來,以後的 move(可能0到多次move),up都會直接分發給這個child,就不用每次再去遍歷所有的Child 這些效率就提高和很多,但是你也會想到,如果一個down事件被某個child消費只有,其它 child就無法被分發事件了,除非我們手動呼叫child的分發方法,或者打斷事件序列,從發分發一個down事件,這段話請看完後面的原始碼再來推敲一下,就完全理解事件分發機制了。 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; 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. final ArrayList<View> preorderedList = buildTouchDispatchChildList(); final boolean customOrder = preorderedList == null && isChildrenDrawingOrderEnabled(); 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); // 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; } // ==>判斷這個子view能否接受點選事件和子view是否包含這個點選的座標,具體的判斷點選這兩個方法進去看 if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) { ev.setTargetAccessibilityFocus(false); continue; } 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); // ==> 關鍵之處,這裡將事件傳遞給child進行處理了,馬上進入這個方法看看吧 if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign))
// ==>dispatchTransformedTouchEvent方法
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) { final boolean handled; // Canceling motions is a special case. We don't need to perform any transformations // or filtering. The important part is the action, not the contents. //根本都不是什麼返回事件,所以跳過,直接到重點 final int oldAction = event.getAction(); if (cancel || oldAction == MotionEvent.ACTION_CANCEL) { event.setAction(MotionEvent.ACTION_CANCEL); if (child == null) { handled = super.dispatchTouchEvent(event); } else { handled = child.dispatchTouchEvent(event); } event.setAction(oldAction); return handled; } // Calculate the number of pointers to deliver. final int oldPointerIdBits = event.getPointerIdBits(); final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits; // If for some reason we ended up in an inconsistent state where it looks like we // might produce a motion event with no pointers in it, then drop the event. if (newPointerIdBits == 0) { return false; } // If the number of pointers is the same and we don't need to perform any fancy // irreversible transformations, then we can reuse the motion event for this // dispatch as long as we are careful to revert any changes we make. // Otherwise we need to make a copy. final MotionEvent transformedEvent; if (newPointerIdBits == oldPointerIdBits) { if (child == null || child.hasIdentityMatrix()) { if (child == null) { handled = super.dispatchTouchEvent(event); } else { final float offsetX = mScrollX - child.mLeft; final float offsetY = mScrollY - child.mTop; event.offsetLocation(offsetX, offsetY); handled = child.dispatchTouchEvent(event); event.offsetLocation(-offsetX, -offsetY); } return handled; } transformedEvent = MotionEvent.obtain(event); } else { transformedEvent = event.split(newPointerIdBits); } // Perform any necessary transformations and dispatch. if (child == null) { // ==>如果child為空,直接呼叫該viewgroup自己父類的dispatchTouchEvent,也就是viewdispatchTouchEvent方法,點選進去會發現它會呼叫onTouchEvent,也就是說如果viewGroup如果沒有child那麼他就會呼叫自己的onTouchEvent方法來消費這個事件,這個handle就表明了這個事件viewgroup自己是否消費 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()); } // ==>我們的child不為空的時候,就呼叫child事件分發方法,於是到這裡可以看到事件的傳遞了,先來分析簡單的情況,如果child是view不是viewgroup,那麼dispatchTouchEvent流程就簡單了,會呼叫onTouchEvent來告訴父view,child它是否消費父親分發給他的事件,這個handle就表明了這個事件clild是否消費 handled = child.dispatchTouchEvent(transformedEvent); } // Done. transformedEvent.recycle(); return handled; }
//這個重要的方法分析完之後,馬上返回剛才原始碼的地方,繼續。。。
//如果剛才,我們的child不為空並且child消費down事件,那麼就很棒了,說明有child處理事件了
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();
// ==>重點,從上面的if條件可以看出,有child消費down事件才會執行到這裡,這個方法點
進去發現mFirstTouchTarget = target;
這句程式碼,很重要哦。很明顯就是把消費down事件的child賦值給mFirstTouchTarget,從而儲存起來
newTouchTarget = addTouchTarget(child, idBitsToAssign);
// ==> alreadyDispatchedToNewTouchTarget = true; 已經把事件分發給新的觸控目標
下面在分發的時候會用到
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();
}
//這裡,如果是down事件,並且mFirstTouchTarget != null則加入連結串列newTouchTarget,這裡不要管
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被賦值為訊息了down事件的child,所以不為空了
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// 這裡非常重要,如果child為空,表明viewgroup在分發事件給child的dispatchTouchEvent被返回了false,說明子view都不消費down事件,那麼這裡會呼叫dispatchTransformedTouchEvent這個重要的方法,點進去就會發現,當child為null的時候,會呼叫viewgroup自己的父類View的dispatchTouchEvent,從而呼叫onTouchEvent,就是說兒子不消費,給老子再看看要不要消費。
// 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;
// ==>看到這裡,你是不是發現怎麼又呼叫了這個重要的,吧事件分發給child的方法,剛才不是已
經分發了,這不是第二次又來分發嗎?,當然不會,你看前面的,第一次分發事件的時候已經講
alreadyDispatchedToNewTouchTarget=true;了
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;
}
總結:事件分發,viewgroup自己先判斷要不要攔截事件,和有沒有呼叫過requestDisallowInterceptTouchEvent方法來不攔截事件,如果不攔截,當down事件的時候,遍歷child,看child是否消費,child如果訊息,則被儲存下來,後面的事件就不遍歷直接分發給他,如果child不消費,那麼viewgruop繼續執行,呼叫自己onTouchEvent方法來判斷自己是不是要消費事件。