上一篇中我们分析了 View 中的事件分发,Android 中直接继承 View 的控件是最小单位,不能包含其他子 View,其中的分发逻辑也比较简单。本节研究下 ViewGroup 中的事件分发。
ViewGroup 事件传递源码分析
Demo
ViewGroup 中的 dispatchTouchEvent 逻辑较多,我们先看一个 Demo,从这个 Demo 深入分析 ViewGroup#dispatchTouchEvent。
定义个 LinearLayout 复写相关方法:
public class MyLinearLayout extends LinearLayout {
final String TAG = "MyLinearLayout";
public MyLinearLayout(Context context) {
super(context);
}
public MyLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.e(TAG,"dispatchTouchEvent="+ev.getAction());
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.e(TAG,"onInterceptTouchEvent="+ev.getAction());
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.e(TAG,"onTouchEvent="+event.getAction());
return super.onTouchEvent(event);
}
}
定义一个 Button 复写相关方法:
public class MyButton extends android.support.v7.widget.AppCompatButton {
static final String TAG = "MyButton";
public MyButton(Context context) {
super(context);
}
public MyButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
Log.e(TAG,"dispatchTouchEvent="+event.getAction());
return super.dispatchTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.e(TAG,"onTouchEvent="+event.getAction());
return super.onTouchEvent(event);
}
}
布局文件
<?xml version="1.0" encoding="utf-8"?>
<com.mrball.eventdemo.MyLinearLayout 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:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/linearlayout"
android:gravity="center"
tools:context=".MainActivity">
<com.mrball.eventdemo.MyButton
android:layout_width="match_parent"
android:text="button"
android:id="@+id/button"
android:clickable="false"
android:layout_height="wrap_content" />
</com.mrball.eventdemo.MyLinearLayout>
MainActivity:
public class MainActivity extends AppCompatActivity implements View.OnClickListener, View.OnTouchListener {
private static final String TAG = "EventDemo";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.button).setOnTouchListener(this);
findViewById(R.id.button).setOnClickListener(this);
findViewById(R.id.linearlayout).setOnTouchListener(this);
findViewById(R.id.linearlayout).setOnClickListener(this);
}
@Override
public void onClick(View v) {
Log.e(TAG,"onClick=="+v.toString());
}
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.e(TAG,"onTouch==="+event.getAction()+"===v==="+v.toString());
return false;
}
}
点击按钮,点击的时候滑动一下然后得到如下日志:
2019-06-09 15:24:41.462 8954-8954/com.mrball.eventdemo E/MyLinearLayout: dispatchTouchEvent=0
2019-06-09 15:24:41.462 8954-8954/com.mrball.eventdemo E/MyLinearLayout: onInterceptTouchEvent=0
2019-06-09 15:24:41.462 8954-8954/com.mrball.eventdemo E/MyButton: dispatchTouchEvent=0
2019-06-09 15:24:41.462 8954-8954/com.mrball.eventdemo E/EventDemo: onTouch===0===v===com.mrball.eventdemo.MyButton{6f4a64f VFED..C.. ........ 0,1132-1440,1300 #7f070022 app:id/button}
2019-06-09 15:24:41.463 8954-8954/com.mrball.eventdemo E/MyButton: onTouchEvent=0
2019-06-09 15:24:41.548 8954-8954/com.mrball.eventdemo E/MyLinearLayout: dispatchTouchEvent=2
2019-06-09 15:24:41.548 8954-8954/com.mrball.eventdemo E/MyLinearLayout: onInterceptTouchEvent=2
2019-06-09 15:24:41.548 8954-8954/com.mrball.eventdemo E/MyButton: dispatchTouchEvent=2
2019-06-09 15:24:41.548 8954-8954/com.mrball.eventdemo E/EventDemo: onTouch===2===v===com.mrball.eventdemo.MyButton{6f4a64f VFED..C.. ...P.... 0,1132-1440,1300 #7f070022 app:id/button}
2019-06-09 15:24:41.548 8954-8954/com.mrball.eventdemo E/MyButton: onTouchEvent=2
2019-06-09 15:24:41.565 8954-8954/com.mrball.eventdemo E/MyLinearLayout: dispatchTouchEvent=2
2019-06-09 15:24:41.565 8954-8954/com.mrball.eventdemo E/MyLinearLayout: onInterceptTouchEvent=2
2019-06-09 15:24:41.565 8954-8954/com.mrball.eventdemo E/MyButton: dispatchTouchEvent=2
2019-06-09 15:24:41.565 8954-8954/com.mrball.eventdemo E/EventDemo: onTouch===2===v===com.mrball.eventdemo.MyButton{6f4a64f VFED..C.. ...P.... 0,1132-1440,1300 #7f070022 app:id/button}
2019-06-09 15:24:41.565 8954-8954/com.mrball.eventdemo E/MyButton: onTouchEvent=2
2019-06-09 15:24:41.574 8954-8954/com.mrball.eventdemo E/MyLinearLayout: dispatchTouchEvent=1
2019-06-09 15:24:41.574 8954-8954/com.mrball.eventdemo E/MyLinearLayout: onInterceptTouchEvent=1
2019-06-09 15:24:41.575 8954-8954/com.mrball.eventdemo E/MyButton: dispatchTouchEvent=1
2019-06-09 15:24:41.575 8954-8954/com.mrball.eventdemo E/EventDemo: onTouch===1===v===com.mrball.eventdemo.MyButton{6f4a64f VFED..C.. ...P.... 0,1132-1440,1300 #7f070022 app:id/button}
2019-06-09 15:24:41.575 8954-8954/com.mrball.eventdemo E/MyButton: onTouchEvent=1
2019-06-09 15:24:41.578 8954-8954/com.mrball.eventdemo E/EventDemo: onClick==com.mrball.eventdemo.MyButton{6f4a64f VFED..C.. ...P.... 0,1132-1440,1300 #7f070022 app:id/button}
点击 Button 以外的区域并小幅滑动一下:
2019-06-09 15:27:31.025 8954-8954/com.mrball.eventdemo E/MyLinearLayout: dispatchTouchEvent=0
2019-06-09 15:27:31.025 8954-8954/com.mrball.eventdemo E/MyLinearLayout: onInterceptTouchEvent=0
2019-06-09 15:27:31.025 8954-8954/com.mrball.eventdemo E/EventDemo: onTouch===0===v===com.mrball.eventdemo.MyLinearLayout{37db861 VFE...C.. ........ 0,0-1440,2432 #7f07004d app:id/linearlayout}
2019-06-09 15:27:31.025 8954-8954/com.mrball.eventdemo E/MyLinearLayout: onTouchEvent=0
2019-06-09 15:27:31.033 8954-8954/com.mrball.eventdemo E/MyLinearLayout: dispatchTouchEvent=2
2019-06-09 15:27:31.033 8954-8954/com.mrball.eventdemo E/EventDemo: onTouch===2===v===com.mrball.eventdemo.MyLinearLayout{37db861 VFE...C.. ...P.... 0,0-1440,2432 #7f07004d app:id/linearlayout}
2019-06-09 15:27:31.033 8954-8954/com.mrball.eventdemo E/MyLinearLayout: onTouchEvent=2
2019-06-09 15:27:31.061 8954-8954/com.mrball.eventdemo E/MyLinearLayout: dispatchTouchEvent=2
2019-06-09 15:27:31.061 8954-8954/com.mrball.eventdemo E/EventDemo: onTouch===2===v===com.mrball.eventdemo.MyLinearLayout{37db861 VFE...C.. ...P.... 0,0-1440,2432 #7f07004d app:id/linearlayout}
2019-06-09 15:27:31.061 8954-8954/com.mrball.eventdemo E/MyLinearLayout: onTouchEvent=2
2019-06-09 15:27:31.078 8954-8954/com.mrball.eventdemo E/MyLinearLayout: dispatchTouchEvent=2
2019-06-09 15:27:31.078 8954-8954/com.mrball.eventdemo E/EventDemo: onTouch===2===v===com.mrball.eventdemo.MyLinearLayout{37db861 VFE...C.. ...P.... 0,0-1440,2432 #7f07004d app:id/linearlayout}
2019-06-09 15:27:31.078 8954-8954/com.mrball.eventdemo E/MyLinearLayout: onTouchEvent=2
2019-06-09 15:27:31.094 8954-8954/com.mrball.eventdemo E/MyLinearLayout: dispatchTouchEvent=2
2019-06-09 15:27:31.094 8954-8954/com.mrball.eventdemo E/EventDemo: onTouch===2===v===com.mrball.eventdemo.MyLinearLayout{37db861 VFE...C.. ...P.... 0,0-1440,2432 #7f07004d app:id/linearlayout}
2019-06-09 15:27:31.094 8954-8954/com.mrball.eventdemo E/MyLinearLayout: onTouchEvent=2
2019-06-09 15:27:31.840 8954-8954/com.mrball.eventdemo E/MyLinearLayout: dispatchTouchEvent=1
2019-06-09 15:27:31.840 8954-8954/com.mrball.eventdemo E/EventDemo: onTouch===1===v===com.mrball.eventdemo.MyLinearLayout{37db861 VFE...C.. ...P.... 0,0-1440,2432 #7f07004d app:id/linearlayout}
2019-06-09 15:27:31.840 8954-8954/com.mrball.eventdemo E/MyLinearLayout: onTouchEvent=1
2019-06-09 15:27:31.840 8954-8954/com.mrball.eventdemo E/EventDemo: onClick==com.mrball.eventdemo.MyLinearLayout{37db861 VFE...C.. ...P.... 0,0-1440,2432 #7f07004d app:id/linearlayout}
你会发现除了 ACTION_DOWN 以外,MOVE 和 UP 事件没有触发 OnInterceptTouchEvent。 下面我们看下源码,从源码中分析整个分发流程。
1、先从 dispatchTouchEvent 说起
ViewGroup#dispatchTouchEvent
@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.
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;
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;
//定义一个变量 记录是否已经完成派发,由于确定派发目标使用了子控件的 dispatchTouchEvent,因此确定派发目标的时候实际上已经完成派发,这种情况下该变量将被设置为 true
boolean alreadyDispatchedToNewTouchTarget = false;
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.
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
//通过索引号获得触控点 id,当找到派发目标后,会将这个 idBitsToAssign 和目标 View 一起生成一个 TouchTarget
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;
}
//检查点击事件是否在控件的坐标范围内,如果不在跳过 查找下一个控件
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
//查找当前控件是否已经在 mFirstTouchTarget 链表中,如果在表明该控件已经在接收一组事件,ViewGroup 会默认该控件对本组事件也感兴趣,将本组事件的目标设置为当前控件,并终止派发目标的查找。
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);
// 使用 dispatchTransformedTouchEvent 尝试将事件派发给当前子控件,
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;
}
// 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();
}
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;
}
}
}
// 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) {
//如果 touchTarget 是新确定的 touchTarget,在确定目标的过程中已经将事件分发出去,不需要再派发。
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;
}
这段代码更长,我们一段一段找重点来看:
// 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.
cancelAndClearTouchTargets(ev);
resetTouchState();
}
这段代码中调用了 cancelAndClearTouchTargets()
#cancelAndClearTouchTargets
/**
* Cancels and clears all touch targets.
*/
private void cancelAndClearTouchTargets(MotionEvent event) {
if (mFirstTouchTarget != null) {
boolean syntheticEvent = false;
if (event == null) {
final long now = SystemClock.uptimeMillis();
event = MotionEvent.obtain(now, now,
MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
syntheticEvent = true;
}
for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
resetCancelNextUpFlag(target.child);
dispatchTransformedTouchEvent(event, true, target.child, target.pointerIdBits);
}
clearTouchTargets();
if (syntheticEvent) {
event.recycle();
}
}
}
这段代码中会判断 mFirstTarget,它是一个链表,如果该属性不为 null,它会做两件事
- 遍历 mFirstTarget 链表,对链表的每一个节点,执行 dispatchTransformedTouchEvent 方法。
- clearTouchTargets。
先看第一个:
ViewGroup#dispatchTransformedTouchEvent
```java
/**
- 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. 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; }
这个方法我们下面还要分析,现在我们先看 cancel=true 的情况下会执行那些逻辑:
```java
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;
}
昨天我们分析了,View 中的事件分发 Android 事件系列-1 View 中的事件分发 其中 dispatchTouchEvent 中有一段代码:
// Clean up after nested scrolls if this is the end of a gesture;
// also cancel it if we tried an ACTION_DOWN but we didn't want the rest
// of the gesture.
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}
ACTION_UP 或 ACTION_CANCEL 的情况下,停止嵌套滑动。 接着看第二件事: ViewGroup#
/**
* Clears all touch targets.
*/
private void clearTouchTargets() {
TouchTarget target = mFirstTouchTarget;
if (target != null) {
do {
TouchTarget next = target.next;
target.recycle();
target = next;
} while (target != null);
mFirstTouchTarget = null;
}
}
逻辑也很简单,遍历所有节点,执行 recycle() 方法。
小结
至此,我们分析了 ViewGroup#dispatchTouchEvent 中的第一个主要逻辑:如果当前事件是 ACTION_DOWN,清空 touchTargets、重置 touchState。
现在回到 ViewGroup#dispatchTouchEvent 接着往下走
// 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;
}
这段代码使用 intercepted 变量检查 ViewGroup 是否拦截事件。直接看图:
- 如果没有 touch targets 而且 事件也不是 ACTION_DOWN ,这个 view group 可以继续拦截事件
- touch targets 不为 null 或 事件是 ACTION_DOWN 继续判断 disallowIntercept 标识位。
- disallowIntercept 为真,即不允许拦截,直接将 intercepted 设置为 false。
- disallowIntercept 为假,即允许拦截,接收 onInterceptTouchEvent 的返回值。
看一下,onInterceptTouchEvent:
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
&& ev.getAction() == MotionEvent.ACTION_DOWN
&& ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
&& isOnScrollbarThumb(ev.getX(), ev.getY())) {
return true;
}
return false;
}
这段代码我们可以直接理解为默认返回false,即不拦截。
接着回到 dispatchTouchEvent 往下看:
// 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);
}
这两行注释很详细,如果被拦截了,开始一个正常事件的分发。如果已经有一个 view 在处理这一系列事件,也做正常事件分发。 继续往下看:
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
从这行往下,都是事件分发,也就是找到处理该事件的目标 View 将事件交给它处理。
接着 if (!canceled && !intercepted) 判断表明,事件不是ACTION_CANCEL并且ViewGroup的拦截标志位intercepted为false(不拦截)则会进入其中。
接着往下:
newTouchTarget = getTouchTarget(child);
private TouchTarget getTouchTarget(@NonNull View child) {
for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
if (target.child == child) {
return target;
}
}
return null;
}
通过getTouchTarget去查找当前子View是否在mFirstTouchTarget.next这条target链中的某一个targe中,如果在则返回这个target,否则返回null。
如果这个 target 存在,则 break 跳出这个循环,ACTION_DOWN 的时候 mFirstTouchTarget 还未被赋值,自然会返回 null 。 接着往下:
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 将事件传递给子 View,调用 child 的 dispatchTouchEvent 方法。dispatchTouchEvent 和 dispatchTransformedTouchEvent 配合基本上就是一个递归,直到 child 是一个 View 的时候才能跳出循环。
因为在dispatchTransformedTouchEvent()会调用递归调用dispatchTouchEvent()和onTouchEvent(),所以dispatchTransformedTouchEvent()的返回值实际上是由onTouchEvent()决定的。简单地说onTouchEvent()是否消费了Touch事件的返回值决定了dispatchTransformedTouchEvent()的返回值,从而决定mFirstTouchTarget是否为null,进一步决定了ViewGroup是否处理Touch事件
newTouchTarget = addTouchTarget(child, idBitsToAssign);
这一行为 mFirstTouchTarget 赋值。
ACTION_DOWN 之外的其他事件
// 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;
}
}
根据前面的分析,如果子 View 接收事件,mFirstTouchTarget 被赋值,如果子 View 不接收事件 mFirstTouchTarget 为 null。 其中第一个逻辑, mFirstTouchTarget 为 null 走了 handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); 这里主要看第三个参数,也就是 child ,当 child 为 null 的时候 handled = super.dispatchTouchEvent(transformedEvent); 这是就是上篇文章中分析的 View 中的 dispatchTouchEvent 方法,不再一一解释了。
mFirstTouchTarget不为null时,也就是说找到了可以消费Touch事件的子View且后续Touch事件可以传递到该子View。可以看见在源码的else中对于非ACTION_DOWN事件继续传递给目标子组件进行处理,依然是递归调用 dispatchTransformedTouchEvent()方法来实现的处理。
到此ViewGroup的dispatchTouchEvent方法分析完毕。
总结:
- 如果 ViewGroup 方法中拦截了,即 OnInterceptTouchEvent 返回 true,intercepted 标志 为 true。这个时候 mFirstTouchTarget 没有被赋值,走 dispatchTransformedTouchEvent 自己处理。
- 没有拦截的话 dispatchTouchEvent 配合 dispatchTransformedTouchEvent 递归找到目标 View。
回到我们开始的 Demo,点击 Button 的时候:
- 首先分发的是 ACTION_DOWN 事件,走进 onInterceptTouchEvent。
- 后续 MOVE 和 UP 事件,mFirstTouchTarget 已经被赋值,所以依然走进 onInterceptTouchEvent。
点击 Button 以外的区域的时候:
- 首先分发的是 ACTION_DOWN 事件,走进 onInterceptTouchEvent。
- 后续事件不是 ACTION_DOWN,mFirstTouchTarget 也没被赋值,所以不走 onInterceptTouchEvent。