上一篇分析了View也就是子控件的事件傳遞機制,現(xiàn)在我們來看一下父容器也就是ViewGroup的事件傳遞
ViewGroup事件分發(fā)
ViewGroup和普通的view相比,多了一個onInterceptTouchEvent,我們還是通過自定義一個ViewGroup來查看ViewGroup的事件傳遞
- MyRelativeLayout.java
/**
* @author wxblack-mac
* @DESCRIBE:
* @DATE 2019/3/28 09:58
* GOOD LUCK
*/
public class MyRelativeLayout extends RelativeLayout {
public MyRelativeLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.d(TAG, "dispatchTouchEvent: action" + ev.getAction());
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d(TAG, "onTouchEvent: event" + event.getAction());
return super.onTouchEvent(event);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.d(TAG, "onInterceptTouchEvent: event" + ev.getAction());
return super.onInterceptTouchEvent(ev);
}
}
- activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<com.d9ing.toucheventlsn.MyRelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/rl"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.d9ing.toucheventlsn.MyButton
android:id="@+id/btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="BUTTON" />
</com.d9ing.toucheventlsn.MyRelativeLayout>
然后我們執(zhí)行程序點擊MyButton查看事件的執(zhí)行情況

從日志的打印我們可以看出,點擊事件先發(fā)生在ViewGroup上
我們可以看到ViewGroup的事件響應順序是:
dispatchTouchEvent---->onInterceptTouchEvent--->onTouchListener---->onTouchEvent
我們看ViewGroup的源碼來分析一下我們觀察到的流程
在ViewGroup的dispatchTouchEvent方法中,我們看到調(diào)用了onInterceptTouchEvent,看onInterceptTouchEvent的返回是true還是false
如果是true表示攔截事件,不會給子View分發(fā)事件,會自己消費這個事件
如果是false表示不攔截,會遍歷所有的子控件,將事件分發(fā)給子控件
if (!canceled && !intercepted) {
// If the event is targeting accessibility 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.
...省略一些代碼
// 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;
}
我們可以看到,在dispatchTouchEvent方法中ViewGroup會遍歷子View,然后會調(diào)用dispatchTransformedTouchEvent
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
.....省略一些代碼
resetCancelNextUpFlag(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();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
我們看dispatchTransformedTouchEvent中的方法
// 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);
}
結合ViewGroup的源碼還有View的源碼我們可以知道,如果View的Clickable、longClieckable等設置為false,則handled會返回false dispatchTransformedTouchEvent方法的作用就是判斷ViewGroup是否有子控件或者是否有子控件需要消耗事件。
如果ViewGroup如果存在子控件的情況下,如果子控件的child是一個ViewGroup這個時候就會遞歸執(zhí)行繼續(xù)遍歷ViewGroup。
就會將事件分發(fā)給子View,會執(zhí)行子View的dispatchTouchEvent方法
如果ViewGroup不存在子View, handled = super.dispatchTouchEvent(transformedEvent);會返回false。dispatchTransformedTouchEvent也會返回false。如果返回false后表示沒有要消費事件的View。
然后接下來就會分發(fā)事件到真正的觸摸對象。
// 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);
}
這里我們可以看到當mFirstTouchTarget為null的時候表示沒有子控件。在dispatchTransformedTouchEvent方法中會調(diào)用父類的dispatchTouchEvent。將事件傳遞會父類或者父容器的dispatchTouchEvent。這樣事件就又回到了父類當中。
如果存在子View,ViewGroup會找到真正觸摸發(fā)生的對象。
// 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;
}
}
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;
}
從這段代碼中我們就能看到 會不斷的循環(huán)找下一個觸摸的目標。需要最終找到消費掉這個事件的對象,沒有找到后,也就是代碼中的mFirstTouchTarget這個為空的時候,事件就會被自己消費掉。
總結
看了這么多的源碼,感覺已經(jīng)懵逼了。我們來畫一張圖來總結這個事件傳遞的過程

這個圖并不很完整,只是說明了正常的情況,還有一些情況我們在這解釋一下:
上圖中,在最后一個子View的onTouch事件中,如果子View返回了true則子View會將事件消費掉。如果onTouch返回false代表子View不消費事件,則事件會傳遞回父容器的dispatchTouchEvent經(jīng)過super.dispatch傳遞到父容器也就是上圖第二個容器的onTouch事件。如果父容器的onTouch返回的是true代表事件被父容器消費。如果返回的是false代表父容器不消費這個事件,事件會繼續(xù)想上傳,傳給父容器的dispatchTouchEvent,在dispatchTouchEvent內(nèi)部會傳遞給父容器的onTouch,如果onTouch返回true代表本事件被消費,返回false表示不消費事件。則事件繼續(xù)向上傳。
所以在調(diào)用表示的就是,在子view的onTouch返回false后會傳遞事件到父容器的onTouch再向上傳遞到父容器的onTouch。但是實際的事件傳遞要多了一個傳遞到dispatchTouchEvent。再由dispatchTouchEvent執(zhí)行super.dispatchTouchEvent調(diào)用onTouch。