Android ViewGroup事件分发机制
参考资料
鸿洋版ViewGroup事件分发机制
郭霖版ViewGroup事件分发机制
Android开发艺术探索
上一篇已经分析了Android View的事件分发机制,本篇将根据源码讲解ViewGroup的事件分发机制,View的一大难题是滑动冲突,滑动冲突的理论基础就是事件分发机制,所以了解事件分发机制,也有益于大家了解冲突产生的原因,以及对冲突进行处理。
关于事件传递机制,这里先给出一些结论,根据这些结论可以更好的理解整个传递机制:
(1)同一事件序列是指从手指触摸屏幕的那一刻起,最手指离开屏幕的那一刻结束,在整个过程中
所产生的一系列事件,这个事件序列以down事件开始,中间含有不定数量的move,最终以up事件结束;
(2)事件传递都是由外向内传递的,即事件总是先传递给父view,然后再由父View传递给子view;
通过requestDisallowInterceptTouchEvent可以在子元素中干预父View的时间分发过程,但是ACTION_DOWN除外;
(3)如果某个View一旦开始拦截某个事件,那么同一事件序列都只能由他来处理,
并且它的onInterceptTouchEvent不会再次被调用。
(4)View没有onInterceptTouchEvent方法,一旦有事件传递给它,那么它的onTouchEvent就会被调用,
View的ontouchEvent默认都会消耗事件,viewGroup默认不拦截事件,Android源码中可以看到onInterceptTouchEvent
默认返回值为false;
(5)子元素是否可以接受点击事件?第一,子元素是否在做动画;第二,点击事件的坐标是否落在子元素区域中。
(6)如果所有子元素都没有处理事件,这里包含两种情况,第一ViewGroup没有子元素,第二子元素处理了点击事件,但是在dispatchTouchEvent中返回false,一般是因为子元素在onTouchEvent返回false,这两种情况ViewGroup会自己处理点击事件
(7)事件大体传递流程:VeiwGroup的dispatchTouchEvent -> VeiwGroup的onInterceptTouchEvent ->View的dispatchTouchEvent ->View的onTouchEvent
源码解析
ViewGroup.dispatchTouchEvent():
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Handle an initial down.
// 处理原始的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.
// 需要在新事件开始时处理完上一个事件,并且调用resetTouchState重置状态
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// Check for interception.
// 检查当前View是否拦截事件
// ViewGroup在如下两种情况下会判断是否拦截当前事件:事件类型为down或者mFirstTouchTarget != null。
// 当ViewGroup不拦截事件并将事件交由子元素处理时,mFirstTouchTarget会被赋值也就是mFirstTouchTarget != null。
// 这样当move事件和up事件到来时,并且事件已经被分发下去,那么onInterceptTouchEvent这个方法将不会再被调用。
//所以当前ViewGroup拦截事件之后就不会再次调用onInterceptTouchEvent方法;
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
//调用onInterceptTouchEvent方法判断是否拦截当前事件,ViewGroup默认返回false;
//如果拦截事件将intercepted置为true;
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置为true;
if (!canceled && !intercepted) {
//ACTION_DOWN事件
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
// 当ViewGroup不拦截事件的时候,事件会向下分发交由它的子View进行处理
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
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
// 遍历ViewGroup的所有子元素,然后判断子元素是否能够收到点击事件
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;
}
// 子元素是否能够接收PointerEvent,或事件有没有落在子元素的边界范围
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
resetCancelNextUpFlag(child);
// 如果某个子元素满足条件,那么事件就会传递给它处理,
// dispatchTransformedTouchEvent这个方法实际上就是调用子元素的dispatchTouchEvent方法,
// 如果子元素仍然是一个ViewGroup,则递归调用重复此过程。
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
// 子View在其边界范围内接收事件
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();
// 如果子元素的dispatchTouchEvent返回true,表示子元素已经处理完事件,
// 那么mFirstTouchTarget就会被赋值同时跳出for循环。
// mFirstTouchTarget的赋值在addTouchTarget内部完成
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();
}
}
}
// Dispatch to touch targets.
// 如果遍历完所有的子元素事件没有被合适处理,有两种情况:
// 1. ViewGroup没有子元素
// 2. 子元素处理了点击事件,但是dispatchTouchEvent返回false
// 这时ViewGroup会自己处理点击事件。
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
// 这里的第三个参数child为null,此时会调用handled = super.dispatchTouchEvent(event);
//最终会调用ViewGroup自身的onTouchEvent来处理事件;
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;
//将处理该事件的子view复制给target,由子元素来处理该事件
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;
//该方法最终会调用子元素的dispatchTouchEvent,传给给子元素来处理事件
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
}
}
}
// Update list of touch targets for pointer up or cancel, if needed.
if (canceled
|| actionMasked == MotionEvent.ACTION_UP
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
//UP事件到来,重置状态,例如将处理该事件的子view mFirstTouchTarget置为null;
resetTouchState();
}
}
return handled;
}
说明:
(1)如果onInterceptTouchEvent返回true,表示ViewGroup将拦截事件,会将intercepted设置true,这样就不会遍历子view寻找事件接受者;这样mFirstTouchTarget为null,会调用dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
if (child == null) {
//因为传入的参数为null,所以调用ViewGroup自身的dispatchTouchEvent方法来处理事件;
handled = super.dispatchTouchEvent(transformedEvent);
} else {
//当有子元素处理事件时,会调用子元素的dispatchTouchEvent
handled = child.dispatchTouchEvent(transformedEvent);
}
return handled;
}
(2)当子元素处理事件时会调用addTouchTarget();
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
// 由子元素处理事件时,将子元素赋值给mFirstTouchTarget
//这样通过mFirstTouchTarget是否为null,就可以知道事件时由子view还是ViewGroup来处理事件;
mFirstTouchTarget = target;
return target;
}
(3)如果ViewGroup找到了能够处理该事件的View,则直接交给子View处理,自己的onTouchEvent不会被触发;
(4)可以通过复写onInterceptTouchEvent(ev)方法,拦截子View的事件(即return true),把事件交给自己处理,则会执行自己对应的onTouchEvent方法
(5)子View可以通过调用getParent().requestDisallowInterceptTouchEvent(true); 阻止ViewGroup对其MOVE或者UP事件进行拦截;
(6)父View可以通过onInterceptTouchEvent来拦截事件,但是如果父view不拦截down事件,子view如果调用requestDisallowInterceptTouchEvent方法,那么即使父View在move和up的时候return true,也不会将事件拦截掉,也只会调用子view的onTouchEvent;当面对滑动冲突时,我们可以考虑通过requestDisallowInterceptTouchEvent设置FLAG_DISALLOW_INTERCEPT标志位来解决滑动冲突;如果父View拦截Down事件,那么子View将不会收到事件;
(7)滑动冲突的理论基础是事件分发机制,所以熟悉事件分发机制有助于我们解决滑动冲突相关问题;
以上就是事件分发机制的全部内容。最后,本文部分内容直接从其他地方文章直接Copy而来,感谢本文内容所参考文章的作者;