文章目录
事件分发(不阐述事件的产生,从java层开始分析)
总所周知,视图在安卓中是树的结构,当我们触摸屏幕,下层的电信号层层向上传递,传递到java层需要判断此事件交给哪个树节点,判断的过程就是分发的过程。
进入源码分析
1.事件分发的流程
//1.Activity#dispatchTouchEvent()
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
//空方法和屏保有关
onUserInteraction();
}
//下一步Window唯一实现类就是PhoneWindow
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
//2.PhoneWindow.superDispatchTouchEvent()
public boolean superDispatchTouchEvent(MotionEvent event) {
//下一步 mDecor就是DecorView
return mDecor.superDispatchTouchEvent(event);
}
//3.DecorView.superDispatchTouchEvent()
public boolean superDispatchTouchEvent(MotionEvent event) {
//DecorView继承自FrameLayout,FrameLayout继承自ViewGroup,FrameLayout没有重写dispatchTouchEvent(),所以会执行ViewGroup中的dispatchTouchEvent()
return super.dispatchTouchEvent(event);
}
//4.ViewGroup的dispatchTouchEvent()
//5.ViewGroup可能会调用它子view的dispatchTouchEvent()
//6.View会调用自身的onTouchEvent()决定怎么执行
//4,5,6在后面详细讲解
2.事件在view中的流程
分析View
中的分发,ViewGroup
最终会调用View
的dispatchTouchEvent()
,因此先看View
中是怎么分发的,只关注重要代码
这里笔者提出一个问题
View
为什么不直接处理而是需要再次分发,不是ViewGroup
才需要分发吗?
在分析之前笔者浅谈一下对此问题的理解:View
的分发是判断事件交给哪个方法执行,是执行监听器还是执行默认方法。
查看源码对上述理解进行验证
view#dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent event) {
...
ListenerInfo li = mListenerInfo;
//首先判断如果监听li对象!=null 且我们通过setOnTouchListener设置了监听,即是否有实现OnTouchListener,如果有实现就判断当前的view状态是不是ENABLED,如果实现的OnTouchListener的onTouch中返回true,并处理事件,则
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
//如果满足这些条件那么返回true,这个事件就在此处理
// 意味着这个View需要事件分发
result = true;
}
//如果上一段判断的条件没有满足(没有在代码里面setOnTouchListener的话),就判断View自身的onTouchEvent方法有没有处理,没有处理最后返回false,处理了返回true;
if (!result && onTouchEvent(event)) {
result = true;
}
...
return result;
}
dispatchTouchEvent()
如果说监听器没有返回true
则会执行自己的onTouchEvent()
view#onTouchEvent
public boolean onTouchEvent(MotionEvent event) {
...
final int action = event.getAction();//事件的类型,如down,move,up
switch (action) {//事件的动作类型
case MotionEvent.ACTION_UP: // 离开
...
performClick();//直接调用PerformClick()方法,执行click
...
break;
case MotionEvent.ACTION_DOWN: //动作:点下
...
break;
case MotionEvent.ACTION_CANCEL: //动作:取消。所以将所有点击动作还原、清零
setPressed(false);// 设为为点击
removeTapCallback();//移除阀门
removeLongPressCallback();//移除长按阀门
mInContextButtonPress = false;//上下文按钮点击false
mHasPerformedLongPress = false;//长按点击未执行
mIgnoreNextUpEvent = false;//不再忽略下一个事件
break;
case MotionEvent.ACTION_MOVE: //动作:滑动(不放开,持续)
...
break;
}
return true;//只要发生了点击,长按点击、,上下文点击,就消耗掉事件
//默认不消耗事件 return false;
}
执行onTouchEvent()
时,up
事件会执行 performClick();
view#performClick
public boolean performClick() {
// We still need to call this method to handle the cases where performClick() was called
// externally, instead of through performClickInternal()
notifyAutofillManagerOnClick();
final boolean result;
final ListenerInfo li = mListenerInfo;
//查看是否设置点击监听器,只要设置了就一定返回true,没有设置就返回false
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
notifyEnterOrExitForAutoFillIfNeeded(true);
return result;
}
所以说在有时候给一个View
设置了点击监听器和Touch
监听器可能会有冲突,在分发过程中先会去执行TouchListener
的事件,不生效才会去执行点击监听器的事件,因此在使用的过程中要注意这个问题
3.事件在ViewGroup的流程
先通读代码,后续对down
,move
进行详细分析
1.重点函数解析
1.ViewGroup#dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
//是否有焦点
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;
// down事件清除操作
if (actionMasked == MotionEvent.ACTION_DOWN) {
//清除上次操作,每次down都意味着从头开始
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// 拦截标志位
final boolean intercepted;
//如果是down事件,或者说已经有人消费过了事件就命中if
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//如果是down事件的话,disallowIntercept为flase,子通过设置这个标志位可以控制父是否有拦截能力
//down的时候,还没有达到子,所以说子还不能通过调用requestDisallowInterceptTouchEvent()来限制父的能力
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
//有能力拦截
if (!disallowIntercept) {
//上层视图通过调用onInterceptTouchEvent(),判断是否拦截
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
//没能力拦截直接为false
intercepted = false;
}
} else {
//不是down事件而且有人消费过了前面的事件
intercepted = true;
}
//如果拦截或者有人消费过了事件就命中if
if (intercepted || mFirstTouchTarget != null) {
ev.setTargetAccessibilityFocus(false);
}
//是否是取消事件
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
// Update list of touch targets for pointer down, if needed.
final boolean isMouseEvent = ev.getSource() == InputDevice.SOURCE_MOUSE;
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0
&& !isMouseEvent;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
//不取消不拦截就执行真正的分发事件
if (!canceled && !intercepted) {
//如果事件的目标是可访问性焦点,我们将其交给
//具有可访问性焦点的视图,如果它不处理它
//我们像往常一样清除flag,把活动发给所有的children。
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
? findChildWithAccessibilityFocus() : null;
//down事件才会分发
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
//这个事件的索引,也就是第几个事件,如果是down事件就是0
final int actionIndex = ev.getActionIndex(); // always 0 for down
//获取分配的ID的bit数量 多指事件相关后续进行分析
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
//清理之前触摸这个指针标识,以防他们的目标变得不同步。
removePointersFromTouchTargets(idBitsToAssign);
final int childrenCount = mChildrenCount;
//没有设置Target而且子view的数量不是0进入if
if (newTouchTarget == null && childrenCount != 0) {
//事件的坐标
final float x =
isMouseEvent ? ev.getXCursorPosition() : ev.getX(actionIndex);
final float y =
isMouseEvent ? ev.getYCursorPosition() : ev.getY(actionIndex);
//view的集合
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);
//拿到当前view
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
//判断view能否接收事件
//前者判断是否显示或者有动画
//后者判断事件是否在view范围内
//代码下2
if (!child.canReceivePointerEvents()
|| !isTransformedTouchPointInView(x, y, child, null)) {
continue;
}
//判断这个view是否在target链上,也就是之前是否响应过事件
//代码3
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
//如果在这个链上直接把新事件交给这个view就可以了
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
//如果子View不在之前的触摸目标列表中,先重置childView的标志,去除掉CACEL的标志
resetCancelNextUpFlag(child);
//真正的事件执行方法,后续分析
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
//如果命中if在,则保留view的信息,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();
//更新当前最新的事件响应view,代码4
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();
}
//第一次响应,mFirstTouchTarget不为null,如果这时候又来一个点击事件而且没有view对其进行响应 //newTouchTarget就会为null,将命中if,找到最后一个响应过事件的target,并把这次没有响应的事件交给它
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;
}
}
}
//没人消费过事件或者直接拦截,自己处理这个事件
if (mFirstTouchTarget == null) {
//真正的处理
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;
//遍历target链表
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
//已经处理过target直接返回true
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
//是否是取消事件
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
//如果cancelChild为true就取消事件,如果说为cancelChild为false就执行target链上的事件代码1.3
//cancelChild为false说明此时可能为一个多点事件,target.child还是之前真正响应过事件的view
//这个view再执行一次新的事件
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) {
//如果是多点触摸下的手指抬起事件,就要根据idBit从TouchTarget中移除掉对应的Pointer(触摸点)
final int actionIndex = ev.getActionIndex();
final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
removePointersFromTouchTargets(idBitsToRemove);
}
}
if (!handled && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
}
return handled;
}
2.ViewGroup#dispatchTransformedTouchEvent
真正的事件处理函数
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
// 单独处理ACTION_CANCEL事件
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;
// 一般情况下,old和new是一样的,而且都是ALL_POINTER_IDS,如果说是多点击事件新旧不同,后续分析
// 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;
}
final MotionEvent transformedEvent;
//单点事件
if (newPointerIdBits == oldPointerIdBits) {
// 走这里
if (child == null || child.hasIdentityMatrix()) {
// 一般情况下后一个判断为false
if (child == null) {
// 子view为null,调用父类View的dispatchEvent()方法
handled = super.dispatchTouchEvent(event);
} else {
// 子view不为null,调整事件在view中的相对位置后再调用子view的dispatchTouchEvent()方法
// 但一般这儿走不到,分发给子view是走后面的逻辑
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 {
//1.3多点击事件会对事件进行一个运算处理
transformedEvent = event.split(newPointerIdBits);
}
// 1.1 viewGroup继承自view 去执行view的dispatchTouchEvent(),自己拦截则传入的child为空;
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
} else {
//1.2 在这里分发给子view
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;
}
3.子view判断是否能响应这个点击
ViewGroup#canReceivePointerEvents
//是否可见,是否有动画
protected boolean canReceivePointerEvents() {
return (mViewFlags & VISIBILITY_MASK) == VISIBLE || getAnimation() != null;
}
ViewGroup#isTransformedTouchPointInView
//位置是否合理
protected boolean isTransformedTouchPointInView(float x, float y, View child,
PointF outLocalPoint) {
final float[] point = getTempLocationF();
point[0] = x;
point[1] = y;
transformPointToViewLocal(point, child);
final boolean isInView = child.pointInView(point[0], point[1]);
if (isInView && outLocalPoint != null) {
outLocalPoint.set(point[0], point[1]);
}
return isInView;
}
4.子View是否相应过事件
ViewGroup#getTouchTarget
private TouchTarget getTouchTarget(@NonNull View child) {
for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
if (target.child == child) {
return target;
}
}
return null;
}
5.保存返回为true的view
链表头插法
ViewGroup#addTouchTarget
//执行这个方法后,mFirstTouchTarget将不为null
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
1.down事件
1.拦截
当用户点击时,ViewGroup
会先判断是否拦截,没有则往下分发,此时子view
并不能限制ViewGroup
的拦截能力,因为子任何事件都没有拿到,触发不了子的任何事件,子没有时机去限制父类,代码都为3.1.1中的片段
//disallowIntercept是子对父的拦截能力的限制,down时一定为false
if (!disallowIntercept) {
//判断是否拦截,此时假设拦截 为true
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
//假设后续子对父限制的话,直接为false
intercepted = false;
}
拦截之后直接走
//没人消费过事件或者直接拦截,自己处理这个事件
if (mFirstTouchTarget == null) {
//真正的处理 3.1
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
//省略
}
这时候传入的child
为null
,canceled
为false
if (child == null) {
// 子view为null,调用父类View的dispatchEvent()方法
handled = super.dispatchTouchEvent(event);
}else{
//省略
}
super.dispatchTouchEvent(event)
则是view
的方法,上面 2.事件在view中的流程已进行分析
2.不拦截(目前只讨论单点击,单点事件标志位肯定一开始都为null)
不拦截的话就进行真正的分发,下面代码非完整代码,都为某函数的片段
先看子view
有没有能力接收这个事件
//判断view能否接收事件
//前者判断是否显示或者有动画
//后者判断事件是否在view范围内
//代码上3.1.3
if (!child.canReceivePointerEvents()
|| !isTransformedTouchPointInView(x, y, child, null)) {
continue;
}
有能力处理这个事件再执行
//此时分发事件 代码3.1.2
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
//省略
}
这时候传入的child
不为null
了
//代码3.1.2
if (child == null) {
//省略
} else {
//1.2 在这里分发给子view
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
transformedEvent.offsetLocation(offsetX, offsetY);
if (! child.hasIdentityMatrix()) {
transformedEvent.transform(child.getInverseMatrix());
}
//执行子的dispatchTouchEvent(),递归调用开始
handled = child.dispatchTouchEvent(transformedEvent);
}
直到找到一个返回为true
的view
给newTouchTarget
,mFirstTouchTarget
赋值
//此时分发事件 代码3.1.1
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
//省略
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
//更新当前最新的事件响应view,代码4
newTouchTarget = addTouchTarget(child, idBitsToAssign);
//准备好分发新事件
alreadyDispatchedToNewTouchTarget = true;
break;
}
再执行else
,因为就一次点击,所以target
循环一次就结束,又因为alreadyDispatchedToNewTouchTarget
在上边已经赋值为true
使得
handled = true
代码3.1.1
if (mFirstTouchTarget == null) {
//省略
} else {
//遍历target链表
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
//已经处理过target直接返回true,子view执行完就命中这个if
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
//省略
}
target = next;
}
}
最后返回handled
,此次点击事件结束
总结:当分发down
事件时,首先看自身拦截否,如果拦截则执行自身的逻辑,如果不拦截,则for
循环遍历子view
,找到一个处理down
事件的view
并进行保存,在后面move
的时候发挥作用。
2.move事件
在move
事件中,首先move
事件不会再进行分发,一个view
如果没有处理down
那么一定是处理不了move
的
//代码3.1.1
//是否分发的条件
if(actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE){
//省略
}
move
事件来临
//代码3.1.1
//因为down已经被处理了所以mFirstTouchTarget != null
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//down的时候已经分发过,则子可能会限制父的拦截能力
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;
}
1.子限制父的拦截能力或者本身就不拦截导致intercepted = false
//代码3.1.1
//因为down有人处理过导致mFirstTouchTarget!=null进入else
if (mFirstTouchTarget == null) {
//省略
} else {
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
//move事件也只循环一次
while (target != null) {
final TouchTarget next = target.next;
//新事件产生的时候会把alreadyDispatchedToNewTouchTarget=false进入else
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
//resetCancelNextUpFlag(target.child)=false,intercepted=false
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
//cancelChild=false,target.child为上次我们处理了down事件的view,所以说把move事件交给了上次处理down事件的view 代码1.2
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
//省略
}
predecessor = target;
target = next;
}
}
2.子不限制父的拦截能力且本身要拦截导致intercepted =true
第一次move
事件到达
//代码3.1.1
//因为down有人处理过导致mFirstTouchTarget!=null进入else
if (mFirstTouchTarget == null) {
//省略
} else {
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
//move事件也只循环一次
while (target != null) {
final TouchTarget next = target.next;
//新事件产生的时候会把alreadyDispatchedToNewTouchTarget=false进入else
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
//intercepted=true
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
//cancelChild=true,target.child为上次我们处理了down事件的view,所以说把move事件交给了上次处理down事件的view
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
//cancel则去除当前节点,将mFirstTouchTarget置为null
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
此时在view
在执行dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)
时,cancelchild
为true
//代码3.1.2
// 单独处理ACTION_CANCEL事件
final int oldAction = event.getAction();
//为true进入if
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
//设置event为cancel事件
event.setAction(MotionEvent.ACTION_CANCEL);
//执行cancel事件
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
执行完cancel
后,第二move
来临
//代码3.1.1
//cancle完mFirstTouchTarget == null成立,自己执行move事件
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
}
总结:当move
时,假设说子view
在处理down
的时候调用了requestDisallowInterceptTouchEvent()
或者说本身不拦截的话(intercepted = false)
,则会把此次move
交给处理了down
事件的view
处理,假设说move
被拦截(intercepted = true)
,第一次move
是先给down view
执行cancel
事件(mFirstTouchTarget = null)
,第二次move
来临时,就会自己去处理move
事件。
up
事件比较简单请读者尝试自己分析
3.多指事件
多指事件也有一个先后顺序,事件分发通过链表的形式来处理多指事件(mFirstTouchTarget
(链表的头),newTouchTarget
(当前最新的target
)
此时只分析一种情景,其他情景请读者自己分析
布局如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout android:layout_height="match_parent"
android:layout_width="match_parent"
xmlns:android="http://schemas.android.com/apk/res/android">
<com.hbsd.myviewconstructor.MyTextView
android:background="@color/black"
android:layout_width="wrap_content"
android:layout_weight="1"
android:layout_height="match_parent"/>
<com.hbsd.myviewconstructor.MyButton
android:background="@color/red"
android:layout_width="wrap_content"
android:layout_weight="1"
android:layout_height="match_parent"/>
</LinearLayout>
MyTextView
继承自TextView
,MyButton
继承自Button
,只是重写onTouchEven
t,监听事件log
情况
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
Log.e("button down", "first down");
return true;
case MotionEvent.ACTION_POINTER_DOWN:
Log.e("button down", "again down");
break;
case MotionEvent.ACTION_POINTER_UP:
Log.e("button down", "again up");
break;
case MotionEvent.ACTION_UP:
Log.e("button down", "first up");
break;
}
return super.onTouchEvent(event);
}
情景:
用户点击左侧TextView
不抬起,再用另一个手指点击右侧Button
不抬起,再来一只手指点击左侧TextView
不抬起,再来一只手指点击右侧Button
不抬起,然后倒着依次抬起。
先讲述原理再分析情景
多指触发的原理
手指头是靠位运算来区分的,比如说第一个手指头为00000001,第二个则为00000010,第三个则为00000100
在down
的时候,默认为00000001,当后续手指头down
,则1往左移当前个手指数,左移即×2
上述所说,只有down
事件会分发不准确,还有多指down
,也就是ACTION_POINTER_DOWN
也会执行分发操作
进入分发,则执行下面代码
//3.1.1
//第几个手指头
final int actionIndex = ev.getActionIndex(); // always 0 for down
//拿到相应的二进制int,split(代表是否支持多指)默认为true,假设是第三个手指,则1左移三位
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
后续执行removePointersFromTouchTargets(idBitsToAssign);
ViewGroup#removePointersFromTouchTargets
private void removePointersFromTouchTargets(int pointerIdBits) {
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
//遍历链表
while (target != null) {
final TouchTarget next = target.next;
//与操作是11为1,其他为0,如果说不为0,则意味着这两个手指重复
if ((target.pointerIdBits & pointerIdBits) != 0) {
//与非操作,执行完则target.pointerIdBits = 0
target.pointerIdBits &= ~pointerIdBits;
if (target.pointerIdBits == 0) {
//移除相同元素
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
上述方法执行完毕则链表中已经移除相同点击手指的target
回到ViewGroup
的分发,此时进入循环遍历子View
,后续会执行newTouchTarget = getTouchTarget(child);
ViewGroup#getTouchTarget
//寻找相同的child,假设找到则newTouchTarge不为空
private TouchTarget getTouchTarget(@NonNull View child) {
for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
if (target.child == child) {
return target;
}
}
return null;
}
后续再执行
//3.1.1
//子view重复则不为null
if (newTouchTarget != null) {
//分析或处理,假设第一个手指是0000 0001,第二个手指是0000 0010,两者或结果为0000 0011,也就代表此view当前有两个手指
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
假设上述为null
,则进入dispatchTransformedTouchEvent
//3.1.2
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
...
//获取全部的点击,假设说此时是第2个手指则值为0000 0011
final int oldPointerIdBits = event.getPointerIdBits();
//若此时是第二个手指头则 desiredPointerIdBits为0000 00010,两者与则为0000 00010
final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
if (newPointerIdBits == 0) {
return false;
}
final MotionEvent transformedEvent;
//若是第二个手指点击则不相等执行else
if (newPointerIdBits == oldPointerIdBits) {
//分析多指事件不分析此处
} else {
//多指事件的重点
transformedEvent = event.split(newPointerIdBits);
}
// 交给View的分发,执行
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
} else {
...
handled = child.dispatchTouchEvent(transformedEvent);
}
return handled;
}
若上述不为null
,则再后续的while
中进行事件触发
MotionEvent#split
//参数idBits表示当前触摸点
public final MotionEvent split(int idBits) {
//缓存,类似于Message,安卓中很常见
MotionEvent ev = obtain();
synchronized (gSharedTempLock) {
//获取手指个数
final int oldPointerCount = nativeGetPointerCount(mNativePtr);
// 初始化gSharedTempPointerProperties、gSharedTempPointerCoords、gSharedTempPointerIndexMap数组。
ensureSharedTempPointerCapacity(oldPointerCount);
final PointerProperties[] pp = gSharedTempPointerProperties;
final PointerCoords[] pc = gSharedTempPointerCoords;
final int[] map = gSharedTempPointerIndexMap;
//获取当前的事件类型
final int oldAction = nativeGetAction(mNativePtr);
//必须与ACTION_MASK进行与才能获取真实事件类型
final int oldActionMasked = oldAction & ACTION_MASK;
//当前事件的手指索引
final int oldActionPointerIndex = (oldAction & ACTION_POINTER_INDEX_MASK)
>> ACTION_POINTER_INDEX_SHIFT;
//新的手指索引,这里是旧的转换新的,因此会有新的来标记
int newActionPointerIndex = -1;
//新的事件触发的手指数
int newPointerCount = 0;
//新事件的全部手指
int newIdBits = 0;
//遍历全部手指
for (int i = 0; i < oldPointerCount; i++) {
//将底层的数据交给pp数组
nativeGetPointerProperties(mNativePtr, i, pp[newPointerCount]);
// 获取触摸点ID
final int idBit = 1 << pp[newPointerCount].id;
//如果说当前遍历的手指和参数相同或者被包含
if ((idBit & idBits) != 0) {
// 该触摸点是TouchTarget感兴趣的
if (i == oldActionPointerIndex) {
// 且该触摸点是引发当前事件的那个触摸点,特别记录下它的索引
newActionPointerIndex = newPointerCount;
}
// 缓存记录
map[newPointerCount] = i;
newPointerCount += 1;
newIdBits |= idBit;
}
}
// 安全检查
if (newPointerCount == 0) {
throw new IllegalArgumentException("idBits did not match any ids in the event");
}
// 用于记录事件拆分后新的动作类型
final int newAction;
// 仅对ACTION_POINTER_DOWN和ACTION_POINTER_UP进行类型调整
if (oldActionMasked == ACTION_POINTER_DOWN || oldActionMasked == ACTION_POINTER_UP) {
if (newActionPointerIndex < 0) {
// An unrelated pointer changed.
// 引发当前事件的那个触摸点不是TouchTarget感兴趣的,则将类型调整为
// ACTION_MOVE,对于该TouchTarget来说,当作普通的滑动事件处理。
newAction = ACTION_MOVE;
} else if (newPointerCount == 1) {
// The first/last pointer went down/up.
// 引发当前事件的那个触摸点是该TouchTarget感兴趣的,且TouchTarget
// 感兴趣的个数为1。说明该TouchTarget仅对当前这一个触摸点感兴趣(单点触摸),那么
// 对于该TouchTarget来说,将是一个全新序列的开始或结束。
// 将动作类型调整为ACTION_DOWN或ACTION_UP。
newAction = oldActionMasked == ACTION_POINTER_DOWN
? ACTION_DOWN : ACTION_UP;
} else {
// A secondary pointer went down/up.
// 到了这个case,意味着该触摸点是该TouchTarget上的多点触摸事件,沿用
// 动作类型,并组合上触摸点索引。
newAction = oldActionMasked
| (newActionPointerIndex << ACTION_POINTER_INDEX_SHIFT);
}
} else {
// Simple up/down/cancel/move or other motion action.
newAction = oldAction;
} // 事件动作类型调整完毕
// 初始化MotionEvent副本
final int historySize = nativeGetHistorySize(mNativePtr);
for (int h = 0; h <= historySize; h++) {
final int historyPos = h == historySize ? HISTORY_CURRENT : h;
for (int i = 0; i < newPointerCount; i++) {
nativeGetPointerCoords(mNativePtr, map[i], historyPos, pc[i]);
}
final long eventTimeNanos = nativeGetEventTimeNanos(mNativePtr, historyPos);
if (h == 0) {
// 使用原对象数据初始化native层对象,并返回对象指针,这里传入了调整后的动作类型。
ev.mNativePtr = nativeInitialize(ev.mNativePtr,
nativeGetDeviceId(mNativePtr), nativeGetSource(mNativePtr),
newAction, nativeGetFlags(mNativePtr),
nativeGetEdgeFlags(mNativePtr), nativeGetMetaState(mNativePtr),
nativeGetButtonState(mNativePtr),
nativeGetXOffset(mNativePtr), nativeGetYOffset(mNativePtr),
nativeGetXPrecision(mNativePtr), nativeGetYPrecision(mNativePtr),
nativeGetDownTimeNanos(mNativePtr), eventTimeNanos,
newPointerCount, pp, pc);
} else {
nativeAddBatch(ev.mNativePtr, eventTimeNanos, pc, 0);
}
}
return ev;
}
}
//作者:dehang0
//链接:https://juejin.cn/post/6844904065617362952
//来源:稀土掘金
上边的方法主要是拆解事件,如何理解呢,举个例子:
比如说第一个手指点击某个View
,第二个手指再点击另一个View
,此时有两个手指,上述的for
会执行两次,两次的手指一个为0000 0001,一个为0000 0010,而传入的参数为当前要执行事件的View
的所有手指,对于第二个View
来讲则是0000 0010,(idBit & idBits) != 0
成立则使得newPointerCount + 1
,此时执行完下面代码,事件由Pointer Down
变为Down
,返回到dispatchTransformedTouchEvent
,执行view
的分发方法,此时执行的事件为Down
// 引发当前事件的那个触摸点是该TouchTarget感兴趣的,且TouchTarget
// 感兴趣的个数为1。说明该TouchTarget仅对当前这一个触摸点感兴趣(单点触摸),那么
// 对于该TouchTarget来说,将是一个全新序列的开始或结束。
// 将动作类型调整为ACTION_DOWN或ACTION_UP。
newAction = oldActionMasked == ACTION_POINTER_DOWN
? ACTION_DOWN : ACTION_UP;
再对上述方法的for
循环进行解释,举个例子:
假设说当前由三根手指,第一根和第三个手指被view1
相应,那么此时idBits
为0000 0101,紧接遍历所有手指,0000 0001,0000 0010,0000 0100,对于view1
来讲第一个和第三根手指做完与运算,是满足(idBit & idBits) != 0
的,此时就会执行两次newPointerCount += 1
,最终为2执行下面代码,此case
不改变事件,旧action
是Pointer Down
执行后还是Pointer Down
// 到了这个case,意味着该触摸点是该TouchTarget上的多点触摸事件,沿用
// 动作类型,并组合上触摸点索引。
newAction = oldActionMasked
| (newActionPointerIndex << ACTION_POINTER_INDEX_SHIFT);
情景分析
用户点击左侧TextView
不抬起,再用另一个手指点击右侧Button
不抬起,再来一只手指点击左侧TextView
不抬起,再来一只手指点击右侧Button
不抬起,不再分析手指抬起
上述情景的执行顺序,分析点再split
函数,其他方法不进行分析
第一个手指点击
TextView Down
直接触发Down
,没有疑问
第二个手指点击
Button Down
此时触发Button
的Down
事件,因为执行split
,将Ponit Down
转换成了Down
,走下面分支
// 引发当前事件的那个触摸点是该TouchTarget感兴趣的,且TouchTarget
// 感兴趣的个数为1。说明该TouchTarget仅对当前这一个触摸点感兴趣(单点触摸),那么
// 对于该TouchTarget来说,将是一个全新序列的开始或结束。
// 将动作类型调整为ACTION_DOWN或ACTION_UP。
newAction = oldActionMasked == ACTION_POINTER_DOWN
? ACTION_DOWN : ACTION_UP;
TextView Move
此时链表有两个元素,第一个在上面执行了Down
事件,此时要执行第二个元素
// An unrelated pointer changed.
// 引发当前事件的那个触摸点不是TouchTarget感兴趣的,则将类型调整为
// ACTION_MOVE,对于该TouchTarget来说,当作普通的滑动事件处理。
newAction = ACTION_MOVE;
第三个手指点击
TextView Ponit Down,
此时TextView
将有两个手指,则走下面分支
// A secondary pointer went down/up.
// 到了这个case,意味着该触摸点是该TouchTarget上的多点触摸事件,沿用
// 动作类型,并组合上触摸点索引。
newAction = oldActionMasked
| (newActionPointerIndex << ACTION_POINTER_INDEX_SHIFT);
Button Move
和上述分析一样走newAction = ACTION_MOVE;
第四个手指点击
Button Ponit Down
TextView Move
此情况不再分析和第三个手指情况一样
总结:多指事件中重点就是split
函数,此函数根据不同情况对事件进行重定向。
✨ 原 创 不 易 , 还 希 望 各 位 大 佬 支 持 一 下 \textcolor{blue}{原创不易,还希望各位大佬支持一下} 原创不易,还希望各位大佬支持一下
👍 点 赞 , 你 的 认 可 是 我 创 作 的 动 力 ! \textcolor{green}{点赞,你的认可是我创作的动力!} 点赞,你的认可是我创作的动力!
⭐️ 收 藏 , 你 的 青 睐 是 我 努 力 的 方 向 ! \textcolor{green}{收藏,你的青睐是我努力的方向!} 收藏,你的青睐是我努力的方向!
✏️ 评 论 , 你 的 意 见 是 我 进 步 的 财 富 ! \textcolor{green}{评论,你的意见是我进步的财富!} 评论,你的意见是我进步的财富!