Android中事件分发,实际上分发的是MotionEvent,事件分发的过程中,涉及到下面三个核心的方法:
onTouchListener 优先级高于 onClickListener
onTouchListener优先级高于onClickListener,onTouchListener返回false,后续的click事件才会被处理,onTouchListener返回true表示消耗了事件,不会再传递。
事件传递的时候是由Activity->window->view,如果view不处理的话,最后事件会回到activity,在事件的流程中:View不会分发事件,View只会处理事件,ViewGroup会先分发事件,如果子View没有处理事件,尝试自己处理事件,如果自己没有处理,最后交给Activity。
android.view.ViewGroup#dispatchTouchEvent
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);
// 一次事件开始,ViewGroup会先清除之前的状态,例如清楚:FLAG_DISALLOW_INTERCEPT的标记
resetTouchState();
}
// Check for interception.
final boolean intercepted;
// 为down事件或者mFirstTouchTarget不为空表示找到了消耗touch事件的view
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;
}
...
// 会便利当前viewgroup的所有child,寻找是否需要消耗事件
// 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;
}
// canReceivePointerEvents判断view是否可见&view没有再播放动画,
// isTransformedTouchPointInView判断点击区域是否在view的范围内
if (!child.canReceivePointerEvents()
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
ViewGroup的TouchEvent中,都会先去判断View是否有子view,有子view的话递归调用子View的dispatchTouchEvent方法,否则直接调用自己的dispatchTouchEvent方法:
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
Down事件分发后,一定会找到了消耗事件的view(或者事件直接被Activity消耗),如果ViewGroup没有消耗事件,事件就不会再继续往当前ViewGroup分发,UP和MOVE事件都会发送到DOWN事件的消耗这上,mFirstTouchTarget不为null,直接找到target进行分发。
// 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的dispatchTouchEvent
android.view.View#dispatchTouchEvent
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;
}
}
View的dispatchTouchEvent中,会先判断view有没有设置TouchListener,如果设置了TouchListener再去判断onTouch方法的返回值,如果onTouch方法没有消耗事件,会再调用onTouchEvent方法。如果View 的onTouchEvent方法返回了false,就表示事件没有被消耗,那么最终会调用到ViewGroup的onTouchEvent。
// 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);
// 上面的代码会调用到dispatchTransformedTouchEvent中的下面的地方,进行事件分发:
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
mFirstTouchTarget为null表示子view没有消耗事件,这里会再调用dispatchTransformedTouchEvent去派发。
ViewGroup事件派发结束后,就会回到Activity中,参考下面的代码:
/**
* Called to process touch screen events. You can override this to
* intercept all touch screen events before they are dispatched to the
* window. Be sure to call this implementation for touch screen events
* that should be handled normally.
*
* @param ev The touch screen event.
*
* @return boolean Return true if this event was consumed.
*/
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
// 执行activity的onTouchEvent
return onTouchEvent(ev);
}
如果ViewGroup的onTouchEvent返回false,那么就会调用Activity的onTouchEvent。
Cancel事件触发场景案例:当ScrollView中添加自定义View时,ScrollView在DOWN事件不会进行拦截,当手指滑动到一定的距离后,onInterceptTouchEvent方法返回true,并触发ScrollView的滚动效果,当ScrollView进行滚动的时候,内部的子View会收到一个cancel事件,并丢失焦点。
代码地址:https://gitee.com/lxd15130140362/lxd-android-start/tree/master/app/src/main/java/com/example/androidstart/view
界面布局样式:
2023-02-19 20:50:19.786 7129-7129/com.example.androidstart I/Activity: dispatchTouchEvent: ACTION_DOWN
2023-02-19 20:50:19.787 7129-7129/com.example.androidstart D/CustomizeLayout: dispatchTouchEvent: ACTION_DOWN
2023-02-19 20:50:19.787 7129-7129/com.example.androidstart D/CustomizeLayout: onInterceptTouchEvent: ACTION_DOWN
2023-02-19 20:50:19.787 7129-7129/com.example.androidstart I/CustomizeTextView: dispatchTouchEvent: ACTION_DOWN
2023-02-19 20:50:19.787 7129-7129/com.example.androidstart I/CustomizeTextView: onTouchEvent: ACTION_DOWN
2023-02-19 20:50:19.787 7129-7129/com.example.androidstart I/CustomizeLayout: onTouchEvent: ACTION_DOWN
2023-02-19 20:50:19.787 7129-7129/com.example.androidstart I/Activity: onTouchEvent: ACTION_DOWN
2023-02-19 20:50:19.829 7129-7129/com.example.androidstart I/Activity: dispatchTouchEvent: ACTION_MOVE
2023-02-19 20:50:19.829 7129-7129/com.example.androidstart I/Activity: onTouchEvent: ACTION_MOVE
2023-02-19 20:50:19.927 7129-7129/com.example.androidstart I/Activity: dispatchTouchEvent: ACTION_MOVE
2023-02-19 20:50:19.928 7129-7129/com.example.androidstart I/Activity: onTouchEvent: ACTION_MOVE
2023-02-19 20:50:20.017 7129-7129/com.example.androidstart I/Activity: dispatchTouchEvent: ACTION_MOVE
2023-02-19 20:50:20.017 7129-7129/com.example.androidstart I/Activity: onTouchEvent: ACTION_MOVE
2023-02-19 20:50:20.018 7129-7129/com.example.androidstart I/Activity: dispatchTouchEvent: ACTION_UP
2023-02-19 20:50:20.018 7129-7129/com.example.androidstart I/Activity: onTouchEvent: ACTION_UP
Down事件会从activity->viewgroup->view,move和up事件都被activity自己消费了,不会进行事件分发,因为之前的down事件没有人分发,就表示子view不会处理点击事件。
事件传输流程:
2023-02-19 20:57:04.896 7618-7618/com.example.androidstart I/Activity: dispatchTouchEvent: ACTION_DOWN
2023-02-19 20:57:04.898 7618-7618/com.example.androidstart D/CustomizeLayout: dispatchTouchEvent: ACTION_DOWN
2023-02-19 20:57:04.898 7618-7618/com.example.androidstart D/CustomizeLayout: onInterceptTouchEvent: ACTION_DOWN
2023-02-19 20:57:04.898 7618-7618/com.example.androidstart I/CustomizeLayout: onTouchEvent: ACTION_DOWN
2023-02-19 20:57:04.899 7618-7618/com.example.androidstart I/Activity: onTouchEvent: ACTION_DOWN
2023-02-19 20:57:04.995 7618-7618/com.example.androidstart I/Activity: dispatchTouchEvent: ACTION_MOVE
2023-02-19 20:57:04.996 7618-7618/com.example.androidstart I/Activity: onTouchEvent: ACTION_MOVE
2023-02-19 20:57:04.998 7618-7618/com.example.androidstart I/Activity: dispatchTouchEvent: ACTION_MOVE
2023-02-19 20:57:04.998 7618-7618/com.example.androidstart I/Activity: onTouchEvent: ACTION_MOVE
2023-02-19 20:57:04.999 7618-7618/com.example.androidstart I/Activity: dispatchTouchEvent: ACTION_UP
2023-02-19 20:57:04.999 7618-7618/com.example.androidstart I/Activity: onTouchEvent: ACTION_UP
Down事件会从Activity->ViewGroup,因为ViewGroup进行了拦截,所以这里不会分发到子View,MOVE和UP事件也只会在activity中进行分发
2023-02-19 21:17:28.939 9448-9448/com.example.androidstart I/Activity: dispatchTouchEvent: ACTION_DOWN
2023-02-19 21:17:28.940 9448-9448/com.example.androidstart D/CustomizeLayout: dispatchTouchEvent: ACTION_DOWN
2023-02-19 21:17:28.941 9448-9448/com.example.androidstart D/CustomizeLayout: onInterceptTouchEvent: ACTION_DOWN
2023-02-19 21:17:28.941 9448-9448/com.example.androidstart I/CustomizeTextView: dispatchTouchEvent: ACTION_DOWN
2023-02-19 21:17:28.942 9448-9448/com.example.androidstart I/CustomizeTextView: onTouchEvent: ACTION_DOWN
2023-02-19 21:17:28.942 9448-9448/com.example.androidstart I/CustomizeLayout: onTouchEvent: ACTION_DOWN
2023-02-19 21:17:29.021 9448-9448/com.example.androidstart I/Activity: dispatchTouchEvent: ACTION_MOVE
2023-02-19 21:17:29.022 9448-9448/com.example.androidstart D/CustomizeLayout: dispatchTouchEvent: ACTION_MOVE
2023-02-19 21:17:29.022 9448-9448/com.example.androidstart I/CustomizeLayout: onTouchEvent: ACTION_MOVE
2023-02-19 21:17:29.023 9448-9448/com.example.androidstart I/Activity: dispatchTouchEvent: ACTION_UP
2023-02-19 21:17:29.023 9448-9448/com.example.androidstart D/CustomizeLayout: dispatchTouchEvent: ACTION_UP
2023-02-19 21:17:29.023 9448-9448/com.example.androidstart I/CustomizeLayout: onTouchEvent: ACTION_UP
down事件会activity->viewgroup->view,因为viewgroup消耗了事件,因此down事件不会回到activity,同时由于已经有了事件消费者,因此MOVE和UP事件不会再往view进行传递,回直接调用到ViewGroup的onTouch中中。
子view在自己的down或者move的时候调用requestdisallowIntereptTouchEvent,这样父view在这次事件传递中就不会拦截当前链路的事件。
链接:
requestDisallowInterceptTouchEvent失效的原因及解决