在View的事件分發(Activity篇)中我們分析了Touch事件由Activity最後分發到ViewGroup中的dipatchTouchEvent()方法中的過程,這篇博客主要大家分析ViewGroup中dispatchTouchEvent方法中處理Touch事件的邏輯,由於源代碼比較複雜,所以直接採取註釋代碼的方式解讀源代碼。如果對源代碼無興趣可以直接跳到結尾獲得,這段源代碼我們所獲得的關於ViewGroup的Touch事件分發的知識點。
@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);
}
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Handle an initial down.
//MotionEvent爲ACTION_DOWN取消和清除TouchTargets、重置TouchState、FLAG_DISALLOW_INTERCEPT標誌位,該標誌位默認爲false
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.
//該方法會重置mFirstTouchTargets爲null
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// Check for interception.
final boolean intercepted;
//MotionEvent爲ACTION_DOWN或FirstTouchTarget不爲null(一個Touch事件開始的ACTION_DOWN或者該事件已經交給子View去處理(FirstTouchTarget不爲null)
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//FLAG_DISALLOW_INTERCEPT(該標誌位的設立爲不允許當前View/ViewGroup攔截事件,但是改標誌爲當MotionEvent爲ACTION_DOWN時會被重置(25行代碼處,當MotionEvent爲ACTION_DOWN時重置))
----------
//1、此處我們得出一個重要結論:只要不是ACTION_DOWN事件,那麼FLAG_DISALLOW_INTERCEPT該標誌位可以干涉ViewGroup不攔截這個事件,在這種情況下,onInterceptTouchEvent()這個方法將失去作用。普通情況是否攔截當前事件是由onInterceptTouchEvent(true攔截/false不攔截)這個方法決定。
----------
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
//如果設置了標誌位攔截該事件,最終還要詢問onInterceptTouchEvent方法是否攔截該事件
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
//此處的邏輯爲沒有設置標誌位該ViewGroup攔截事件,所以該ViewGroup攔截該事件
intercepted = false;
}
} else {
//此處的邏輯說明該事件已經交給該ViewGroup處理,事件爲ACTION_MOVE/ACTION_UP,所以攔截標誌爲(intercepted)true
// 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;
//此處爲重置newTouchTarget爲null
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
View childWithAccessibilityFocus = 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.
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.
//在記錄表裏面尋找一個能夠接收事件的View,從前往後掃描子View
final ArrayList<View> preorderedList = buildOrderedChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
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.
//尋找能夠獲得焦點的Children(View)
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}
//這個Child(View)能夠接受到點擊事件,並且左邊在這個View的範圍內
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
//尋找指定的child(View)的TouchTarget
newTouchTarget = getTouchTarget(child);
//如果newTouchTarget不爲空,說明已經有View消耗了這個Touch事件,有了相應的TouchTarget,那就直接跳出這個循環
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);
//正常的如果找不到newTouchTarget,即是這個Touch事件還沒有被子View消耗,有相應記錄的TouchTarget,即調用dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)傳遞給子View,然後根據返回值去判斷子View是否消耗的這個Touch事件
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
//子View消耗了這個事件那麼就將子View所在View的數組中的位置記錄下來,即是mLastTouchDownIndex這個數值,然後跳出這個循環(如果有先前順序列表就去列表裏面找,沒有就用先前View(Children)所在數組的數值)
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();
//將接收這個事件的Child(View)賦值給mFirstTouchTarget,並將原來的mFirestTouchTarget給TouchTarget列表的下一個,並且跳出這個循環,因爲事件被Child(View)消耗了,所以newTouchTarget依然也有值
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();
}
//該種情況是,如果事件不是ACTION_DOWN,mFisrtTarget就不會被重置爲null,但是如果要newTouchTarget有值,必須是事件在Child(View)裏面被消耗了,簡單概括事件被分下去了,,卻沒被消耗,於是就把事件添加進最近的TouchTarget裏面去了
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;
}
}
}
----------
2、//mFirstTarget即是當事件分發都沒有分發給子View,如果沒有子View接收這個事件,那麼後續的所有事件都會交給ViewGroup自己
----------
//事件直接沒有被分給Child(View),所以調用ViewGroup的dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS)自己去處理這個事件
// 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
這是將事件分發給子View的dispatchTransformedTouchEvent()方法的源代碼:
/**
* 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;
// 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.
//如果子View爲空就會調用View的dispatchTouchEvent方法去處理
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());
}
//子View是ViewGroup就調用ViewGroup如果子View是View就調用View的
handled = child.dispatchTouchEvent(transformedEvent);
}
// Done.
transformedEvent.recycle();
return handled;
}
結論:
1、只要不是ACTION_DOWN事件,那麼FLAG_DISALLOW_INTERCEPT該標誌位可以干涉ViewGroup不攔截這個事件,在這種情況下,onInterceptTouchEvent()這個方法將失去作用。普通情況是否攔截當前事件是由onInterceptTouchEvent(true攔截/false不攔截)這個方法決定。
2、事件分發是有父到子進行分發的
3、如果子View不消耗事件就會重新交給父View處理
4、如果決定了攔截一個事件,那麼整個事件都會交給這個ViewGroup處理
5、分發給子View的事件,如果是View就會調用View的dispatchTouchEvent(),是ViewGroup就會調用ViewGroup的dispatchTouchEvent()