事件分发机制三大方法 dispatchTouchEvent onInterceptTouchEvent onTouchEvent
ViewGroup # dispatchTouchEvent 源码详解
先提出几个问题再阅读源码
- 1. ViewGroup的某个孩子没有消费ACTION_DOWN事件;那么,后续的 ACTION_MOVE和ACTION_UP 会响应吗 ?
- 2. ACTION_CANCEL 事件什么时候会收到?
- 3. requestDisallowInterceptTouchEvent 有什么作用 ?
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// ACTION_DOWN 时先初始化触摸事件 , 把之前保存的状态全部清空
// mFirstTouchTarget = null
// (mGroupFlags & FLAG_DISALLOW_INTERCEPT) disallowIntercept置为false
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
resetTouchState();
}
//intercepted = true 条件:
//1. down 有子view 消费了事件 && disallowIntercept =false && 自身 onInterceptTouchEvent = true
//2. 没有子view 消费事件
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
// 如果是为ACTION_DOWN 事件
//或者 mFirstTouchTarget != null , 表示有子view消费了此事件
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
//disallowIntercept = false
//调动自身 onInterceptTouchEvent
//如果ACTION_DOWN 时候返回 true , 子view 怎么都无法响应触摸事件
intercepted = onInterceptTouchEvent(ev);
//防止onInterceptTouchEvent 中对action 修改
//所以重新设置一遍
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 || mFirstTouchTarget != null) {
ev.setTargetAccessibilityFocus(false);
}
// PFLAG_CANCEL_NEXT_UP_EVENT 标记的作用是记录View是否被临时移出Window 或 viewGroup。
//canceled = true 条件 :
//1. 此view 被viewgroup 或 window 移除
//2. actionMasked == MotionEvent.ACTION_CANCEL
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
//split 作用是判断可以把事件分发到多个子View , 多点触摸开关
//这个同样在ViewGroup中提供了public的方法: setMotionEventSplittingEnabled ( boolean split)来设置.
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
///////////////////////////子view处理触摸事件的关键代码///////////////////////////
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
if (!canceled && !intercepted) {
//没有取消 , 没有拦截 ,即 子view可以处理触摸事件
// 如果是辅助功能事件,会寻找他的targetview来接收这个事件
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
? findChildWithAccessibilityFocus() : null;
//ACTION_POINTER_DOWN :代表用户又使用一个手指触摸到屏幕上,也就是说,在已经有一个触摸点的情况下,有新出现了一个触摸点。
//ACTION_HOVER_MOVE : 鼠标事件 , 指针在窗口或者View区域移动,但没有按下。
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
// 多点触控会有不同的索引,获取索引号
// 该索引位于MotionEvent中的一个数组没索引值就是数组的下标值
// 只有up或者down事件才会携带索引值
final int actionIndex = ev.getActionIndex(); // always 0 for down
// 这个整型变量记录了TouchTarget中view所对应的触控点id
// 触控点id的范围是0-31,整型变量中一个二进制为1,则对应绑定该id的触控点
// 这里根据是否需要分离,对触控点id进行记录
// 而如果不需要分离,则默认接受所有触控点的事件
// ALL_POINTER_IDS = -1; (-1 二进制 = 11111111111111111111111111111111)
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
//清空这个手指idBitsToAssign对应的TouchTarget链表。
removePointersFromTouchTargets(idBitsToAssign);
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
//倒序遍历子view
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}
// 满足 下面条件 子view才 可以接受触摸事件 :
// 条件1 : 触摸坐标(x,y)在child的可视范围之内
// 条件2 : child可接受触摸事件:是指child的是可见的(VISIBLE);或者虽然不可见,但是位于动画状态。
// 则继续往下执行。否则,调用continue , 跳过这个子view。
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
// 从缓存中获取newTouchTarget
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
//newTouchTarget != null 表示之前处理过
//重新设置手指id 跳出循环
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
// 调用dispatchTransformedTouchEvent()将触摸事件分发给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();
//如果 child.dispatchTouchEvent(event) = true;
// 子view包装成TouchTarge , 头插法插入到mFirstTouchTarget 的单链表中
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
if (preorderedList != null) preorderedList.clear();
}
if (newTouchTarget == null && mFirstTouchTarget != null) {
//这种情况 , mFirstTouchTarget!=null ,newTouchTarget == null
//表示之前能消费事件 , 但是现在不行了(手指不在之前能消费那个view范围内)
//但是newTouchTarget = mFirstTouchTarget , 事件还是交给之前能消费那个view处理
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
/////////////////////////////////////////////////////////////////////
if (mFirstTouchTarget == null) {
// 没有能消费的子view . 自己super.dispatchTouchEvent处理
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
//如果子view 消费了事件 , handled = true;
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
//如果cancelChild =true , 给子view发送ACTION_CANCEL事件
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
//如果cancelChild =true , 将子view从mFirstTouchTarget 链表移除
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
//遍历mFirstTouchTarget链表
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);
//移除idBitsToRemove 对应的TouchTarget
removePointersFromTouchTargets(idBitsToRemove);
}
}
return handled;
}
总结 :
ACTION_DOWN 事件先去找到能处理此次事件的view , 如果子view 和自己都不能处理就返回 false , 后续的move , up事件都不会再响应 ,如果找到能处理的view , 后续的事件都交给它处理
如果子view之前能处理事件后面被父view拦截了或者从父view/window移除 , 会收到CANCEL 事件 , 不会再处理任何后续事件
子view之前能消费事件 , 但是现在不行了(手指触摸点不在之前能消费那个子view范围内) , 事件还是交给子view处理
解答问题 :
问题1 : ViewGroup的某个孩子没有消费ACTION_DOWN事件;那么,后续的 ACTION_MOVE和ACTION_UP 会响应吗 ?
答 : 如果孩子没有消费ACTION_DOWN事件 , 则mFirstTouchTarget = null , intercepted =true , 子view无法响应后续触摸事件
问题2 : 子viewACTION_CANCEL 事件什么时候会收到 ?
答 : 前提子view消费了 ACTION_DOWN 事件 , 然后 1. 父view intercepted =true 或 2. 子view 从window 或父view 移除 , 子view 才会收到一个ACTION_CANCEL 事件 , 并且后续的事件都不会处理
问题3: requestDisallowInterceptTouchEvent 有什么作用 ?
如果子view.requestDisallowInterceptTouchEvent (true) , 则intercepted =false , 父view不会拦截触摸事件
责任链设计模式思想
定义
客户端发出一个请求,链上的对象都有机会来处理这一请求,而客户端不需要知道谁是具体的处理对象。
多个对象都有机会处理请求,从而避免了请求的发送者和接受者之间的耦合关系。
将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它为止。其过程实际上是一个递归调用。
在事件分发机制中也有责任链设计模式的思想 :
ACTION_DOWN , 会按照层级从Acvitiy -> ViewGroup -> View 这条链路找到能处理它的view , 找到了后续的ACTION_MOVE /ACTION_ UP等事件都交给它处理
参考链接 :
Android 触摸事件机制(四) ViewGroup中触摸事件详解
Android MotionEvent之ACTION_CANCEL