【Android面试题】高级UI面试题——事件冲突怎么解决?

2023-09-03  本文已影响0人  小城哇哇

事件冲突怎么解决?

这道题想考察什么?

这道题想考察同学对事件冲突的解决是否掌握了,这个问题也是同样也是开发中最常遇到的问题。

考生应该如何回答

事件冲突的解决办法分两种:内部拦截法和外部拦截法。

内部拦截法

首先我们来介绍下内部拦截法。它的处理方式是:

第一块代码
// 子View中添加如下代码:
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
    int x = (int) event.getX();
    int y = (int) event.getY();

    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN: {
            getParent().requestDisallowInterceptTouchEvent(true);
            break;
        }
        case MotionEvent.ACTION_MOVE: {
            int deltaX = x - mLastX;
            int deltaY = y - mLastY;
            // 1
            if (Math.abs(deltaX) > Math.abs(deltaY)) {
                getParent().requestDisallowInterceptTouchEvent(false);
            }
            break;
        }
        case MotionEvent.ACTION_UP: {
            break;
        }
        default:
            break;
    }

    mLastX = x;
    mLastY = y;
    return super.dispatchTouchEvent(event);
}
第二块代码
// 父容器中添加如下代码
public boolean onInterceptTouchEvent(MotionEvent event) {
    // 1
    if (event.getAction() == MotionEvent.ACTION_DOWN){
        super.onInterceptTouchEvent(event);
        return false;
    }
    return true;
}

首先我们介绍子View中添加的代码,getParent().requestDisallowInterceptTouchEvent(true)的作用,它的代码如下:

第三块代码
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
  if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
    // We're already in this state, assume our ancestors are too
    return;
  }

  if (disallowIntercept) {
    mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
  } else {
    mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
  }

  // Pass it up to our parent
  if (mParent != null) {
    mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
  }
}

可以看出,通过方法参数disallowIntercept的值,控制了mGroupFlags的值,而这个值是控制父容器是否可以拦截子View的关键代码,如下:

第四块代码
public boolean dispatchTouchEvent(MotionEvent ev) {
  if (actionMasked == MotionEvent.ACTION_DOWN
      || mFirstTouchTarget != null) {
    // 1
    final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
    // 2
    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和2可以知道,当调用 requestDisallowInterceptTouchEvent 传入参数 true 时,disallowIntercept 为 true,就会导致 intercepted = false,从而父容器事件必须要分发给子View。
那父容器中添加的代码是干什么呢?这就要说到事件冲突的解决思路了。我们知道一个事件只能由一个控件处理,而事件冲突实际上就是在不同情况下,按我们的需求分配事件给对应的控件处理。例如:子View是左右滑动的,父容器是上下滑动的,那我们就希望当手指左右滑的时候是子View在动,上下滑的时候是父容器在动。
既然我们的 MotionEvent.ACTION_DOWN 事件是分发给 子View 的,说明事件是由子View根据情况分发的,这个对应的就是 第一块代码的标记1处,此处根据水平和垂直滑动的距离判断出用户是在水平滑动还是垂直滑动。如果事件需要给父容器,则设置 requestDisallowInterceptTouchEvent(false)。然后结合第二块代码中的 onInterceptTouchEvent 返回 true,将分发给子View的事件抢过来,如何做到的呢?代码如下:

第五块代码
// 1
final boolean cancelChild = resetCancelNextUpFlag(target.child)
  || intercepted;
// 2
if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) {
  handled = true;
}
第六块代码
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
                                              View child, int desiredPointerIdBits) {
  final int oldAction = event.getAction();
  // 1
  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;
  }
}

第五块代码中的1处,因为 intercepted = true,所以cancelChild = true,从而会进入第六块代码1处,子View这个时候会执行 取消事件,将事件让出来,在下次MOVE事件处理时就会交给父容器处理。从而达到根据需求分配事件的要求。即解决了事件冲突。
那为什么我们还需要在第二块代码中添加标记1处的 if 判断语句呢?原因需要看如下代码:

第七块代码
public boolean dispatchTouchEvent(MotionEvent ev) {
  if (actionMasked == MotionEvent.ACTION_DOWN) {
    resetTouchState();
  }
}
private void resetTouchState() {
  mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}

可以看到,resetTouchState 会重制 mGroupFlags 的值,而该代码在事件为 MotionEvent.ACTION_DOWN 的时候一定会执行。这就导致 disallowIntercept 在事件为 MotionEvent.ACTION_DOWN 的时候一定为 false,即 onInterceptTouchEvent 一定会执行。所以父容器只能在其他事件拦截子View。
外部拦截法的思路一样,在此就不赘述了。

外部拦截法

外部拦截法直接在父布局中判断是否需要自己处理该事件,如果达到自己需要拦截的条件,直接在onInterceptTouchEvent返回为true,中断该手势后续事件的下发,逻辑上更加简单。实例代码如下:

private int mLastXIntercept;
private int mLastYIntercept;
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
    int x = (int) event.getX();
    int y = (int) event.getY();
    final int action = event.getAction();
    switch (action) {
        case MotionEvent.ACTION_DOWN:
            mLastXIntercept = (int) event.getX();
            mLastYIntercept = (int) event.getY();
            break;
        case MotionEvent.ACTION_MOVE:
            if (needIntercept) {//判断是否需要拦截的条件
                return true;
            }
            break;
        case MotionEvent.ACTION_UP:
            break;
    }
    return super.onInterceptTouchEvent(event);
}

最后

有需要以上面试题的朋友可以关注一下哇哇,以上都可以分享!!!

上一篇 下一篇

猜你喜欢

热点阅读