征服面试官系列:View的事件冲突,原理你了解吗?有怎样的解决方案?

只有一个事件,但处理的对象有多个,而真正处理的对象并不是期待的对象,这样就发生了冲突;

也就是说,一个事件可以由多个view处理时会出现事件冲突(如滚动嵌套),而一个view对一个事件有多个处理方式时也会出现冲突(如touch 和click 事件)。

二、事件分发的流程


在学习事件分发之前,有些状态或标记是通过位运算符来运算的,所以下面先简单了解一下位运算符:

&  比较位的值都是1的时候,该位的结果才是1

|  比较的位只要有一位是1 ,该位的结果就是1
将当前位进行0 1 的交换,即0 换成1 ,1 换成0

2.1)、DOWN 事件的处理与分发流程:

ViewGroup.java中dispatchTouchEvent(MotionEvent ev) 的源码中

2.1.1)重置mGroupFlags

执行的结果 mGroupFlags &= mGroupFlags & (~FLAG_DISALLOW_INTERCEPT);

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();

}

//会重置mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;

2.1.2)事件是否拦截

用上一步重置的mGroupFlags的值替换下面的表达式:

final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;

替换后的结果是:

final boolean disallowIntercept = ( mGroupFlags & (~FLAG_DISALLOW_INTERCEPT)& FLAG_DISALLOW_INTERCEPT) != 0;

由此可以判断disallowIntercept 为false; 就会调用 onInterceptTouchEvent(ev) 来判断是否被拦截,这个方法可在自定义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;

}

2.1.3)事件处理与分发

1) 当被拦截的时候,即 onInterceptTouchEvent(ev)方法返回为true

A) 直接跳过下面的 if (!canceled && !intercepted)条件 ;

由于首次事件处理,那么mFirstTouchTarget==null成立,然后就进入if (mFirstTouchTarget == null) 里面执行,这时候就会进入到dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);

注意:第二个参数canceled 是false ,第三个参数是null

if (!canceled && !intercepted) {

//此处忽略

}

// 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 {

//此处忽略

}

}

B) 进入dispatchTransformedTouchEvent()方法:

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,

View child, int desiredPointerIdBits) {

final boolean handled;

final int oldAction = event.getAction();

if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {

//此处条件不成立,暂时忽略

}

// Perform any necessary transformations and dispatch.

if (child == null) {

handled = super.dispatchTouchEvent(transformedEvent);

} else {

//此处条件不成立,暂时忽略

}

// Done.

transformedEvent.recycle();

return handled;

}

说明:

由于传入的第三个参数是null ,就会直接进入该方法的最底部,调用super.dispatchTouchEvent(transformedEvent); 而当前viewGroup的super是view;这样就很显然调用了view的dispatchTouchEvent()方法.

C) dispatchTouchEvent()方法

说明:

1、设置了OnTouchListener监听者,会回调到监听者的onTouch() 方法中处理,

**

      onTouch()方法返回true ,表示当前view已经消费了该事件,
      onTouch()方法返回false, 则会进入view的onTouchEvent()中

2、没有设置OnTouchListener监听者,则会进入view的onTouchEvent()中

public boolean dispatchTouchEvent(MotionEvent event) {

boolean result = false;

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;

}

}

return result;

}

//onTouchEvent()

public boolean onTouchEvent(MotionEvent event) {

//此时是点击事件,所有下面的clickable是true

final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE

|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)

|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;

if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {

switch (action) {

case MotionEvent.ACTION_DOWN:

// Walk up the hierarchy to determine if we’re inside a scrolling container.

boolean isInScrollingContainer = isInScrollingContainer();

// For views inside a scrolling container, delay the pressed feedback for

// a short period in case this is a scroll.

if (isInScrollingContainer) {

//此处处理的是带滚动的容器中的down事件

} else {

// Not inside a scrolling container, so show the feedback right away

setPressed(true, x, y);

checkForLongClick(

ViewConfiguration.getLongPressTimeout(),

x,

y,

TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);

}

break;

}

//此处返回的是true ,表示已经消费了该down事件

return true;

}

return false;

}

2) 当未被拦截的时候:即 onInterceptTouchEvent(ev)方法返回为false

A) 没有被拦截时,上面拦截状态下跳过的if条件就会执行

说明:

1、mChildrenCount 是指当前viewgroup的直接childview的数量,不包括childview的childview

2、在循环遍历childView的时候,if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign))这个条件中再次给childview进行事件分发;

此处的第二个参数是false ,第三个参数child不为null ,这一步和down事件传入的不同

3、当第2点中if语句成立,也就是有child处理了事件的时候,将该处理事件的view加入到addTouchTarget(child, idBitsToAssign);事件视图链表中(这一点的巧妙之处在于,当move ,up事件发生时,就不用再次查找,直接从该链表中找即可)

if (!canceled && !intercepted) {

if (actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)

|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {

final int childrenCount = mChildrenCount;

if (newTouchTarget == null && childrenCount != 0) {

final ArrayList 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 (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;

}

}

}

复制代码

B) 再次进入dispatchTransformedTouchEvent()方法,就会执行下面代码中的else分支,这个时候会调用的child.dispatchTouchEvent(),而这个时候的child就是我们参数中第三个传递进来的view ,其实就是递归遍历当前view下面的所有的节点childview ,直到找到事件处理的地方。

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,

View child, int desiredPointerIdBits) {

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);

}

}

复制代码

至此,down事件的分发与处理已经完成,那么接下来看看move事件的处理与分发。

2.2)、MOVE 事件的分发与处理

完成了上面的DOWN的分发与处理后,再次分发MOVE事件的时候,还是从ViewGroup的dispatchTouchEvent()方法开始,只是这个时候if (mFirstTouchTarget == null)条件就不成立了,会进入else分支中:

在执行while循环是, if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) 这个条件其实就是处理已经分发过的,避免再次分发

1、如果已经分发过的view ,那么就直接返回了

2、如果没有分发过 ,再次进入dispatchTransformedTouchEvent()方法中,此处需要关注他的第二个参数cancelChild ,关键看一下intercepted的值

final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted;

如果intercepted 在上面是被拦截的,cancelChild 即为true,否则就为false.

{

// 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;

/**

newTouchTarget = addTouchTarget(child, idBitsToAssign);

alreadyDispatchedToNewTouchTarget = true;

private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {

final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);

target.next = mFirstTouchTarget;

mFirstTouchTarget = target;

return target;

}

*/

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;

}

复制代码

3、 cancelChild 为true

下面的代码和donw事件的分发和处理是基本上一样的,如果没有childview节点就交给view的dispatchTouchEvent()来处理,如果有就执行递归调用child.dispatchTouchEvent(event)

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;

}

//下面的代码省略

}

4、cancelChild 为false

这种情况就和down的事件处理与分发就一样了。直到调用到view的onTouchEvent()方法中

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,

View child, int desiredPointerIdBits) {

final boolean handled;

// 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;

}

5、view的onTouchEvent() 处理move事件

public boolean onTouchEvent(MotionEvent event) {

switch (action) {

case MotionEvent.ACTION_MOVE:

if (clickable) {

drawableHotspotChanged(x, y);

}

final int motionClassification = event.getClassification();

final boolean ambiguousGesture =

motionClassification == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE;

int touchSlop = mTouchSlop;

if (ambiguousGesture && hasPendingLongPressCallback()) {

if (!pointInView(x, y, touchSlop)) {

// The default action here is to cancel long press. But instead, we

// just extend the timeout here, in case the classification

// stays ambiguous.

removeLongPressCallback();

long delay = (long) (ViewConfiguration.getLongPressTimeout()

  • mAmbiguousGestureMultiplier);

// Subtract the time already spent

delay -= event.getEventTime() - event.getDownTime();

checkForLongClick(

先自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频

如果你觉得这些内容对你有帮助,可以扫码领取!!!!

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可免费领取!

()

  • mAmbiguousGestureMultiplier);

// Subtract the time already spent

delay -= event.getEventTime() - event.getDownTime();

checkForLongClick(

先自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-kpt2Qo46-1711294253516)]

[外链图片转存中…(img-iORZBeGy-1711294253517)]

[外链图片转存中…(img-cgq7j2Bm-1711294253517)]

[外链图片转存中…(img-Pl2dsWOK-1711294253518)]

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频

如果你觉得这些内容对你有帮助,可以扫码领取!!!!

[外链图片转存中…(img-SWp513E7-1711294253518)]

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可免费领取!

  • 11
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值