ViewGroup/View 事件分发和疑惑

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_15893929/article/details/83240815

这两天今天重新回看了ViewGroup 事件分发,发现之前的认识有很多知识的盲点,这里记录下自己的学习结果,最后虽然理解了,但是发现很难讲诉清楚。

疑惑

在学习的时候大致流程,总有几个疑惑:

  1. onInterceptTouchEvent 什么时候会被调用
  2. onInterceptTouchEvent 是不是每次都会被调用
  3. onInterceptTouchEvent 在拦截ACTION_DOWN,后续会不会调用。此时,viewGroup的onTouchEvent会不会调用
  4. onInterceptTouchEvent 在拦截ACTION_MOVE,后续会不会调用。此时,viewGroup的onTouchEvent会不会调用
  5. View/ViewGroup不消费Action_Down,后续事件会不会走DispatchTouchEvent、onTouchEvent

事件类型 MotionEvent

ACTION_DOWN 按下
ACTION_MOVE 移动
ACTION_UP 抬起
ACTION_CANCEL 取消(跟 onInterceptTouchEvent 拦截事件有关)

重点: 一系列事件是以 ACTION_DOWN,中间经过0次或多次 ACTION_MOVE,最后以 ACTION_UP 结束。

后续的分发事件的都是以上面一整个系列事件为基础的讨论,以一次ACTION_DOWN开始,终止于ACTION_UP。

ViewGroup关于事件分发主要有三个方法:

bool dispatchTouchEvent( )
viewGroup/view 用来进行事件分发,一定会被调用
返回结果表示是否处理消耗事件

bool onInterceptTouchEvent( )
viewGroup 用于是否拦截某个事件 ,若没有选择拦截,则每个事件都会经过onIntercepteTouchtEvent,但如果一旦拦截成功,后续就不会走onInterceptTouchEvent,直接进入该viewGroup的onTouchEvent处理
返回结果表示 viewGroup 是否拦截

bool onTouchEvent( )
处理事件,返回值决定当前控件是否消费了这个事件

分发方法

事件流程图

事件分发
参考Android 事件分发机制详解:史上最全面、最易懂

重要原则:

  1. 一系列事件是以 ACTION_DOWN,中间经过0次或多次 ACTION_MOVE,最后以 ACTION_UP 结束
  2. ViewGroup 默认不处理事件,会分发给View处理,如果View不处理,则由ViewGroup处理事件,如果ViewGroup不处理则最后由Activity的onTouchEvent
  3. ViewGroup 在 onInterceptTouchEvent 拦截开始事件ACTION_DOWN,后续的事件不会走onInterceptTouchEvent,同时事件ACTION_DOWN和后续事件会传递到该 ViewGroup 的 onTouchEvent 方法中
  4. VIewGroup 在 onInterceptTouchEvent 拦截半路事件如ACTION_MOVE,后续也不会走onInterceptTouchEvent,同时会转成一个ACTION_CANCEL事件传给之前处理事件的View的onTouchEvent,而且ACTION_MOVE事件是不会传到ViewGroup的onTouchEvent。只有后续的事件如ACTION_UP才会传递到 ViewGroup 的 onTouchEvent
  5. ACTION_DOWN 会重置拦截标志和负责处理事件的View(mFirstTouchTarget ),表示是一次事件开始,这也是requestDisallowInterceptTouchEvent失效的原因

验证:

验证图
为了验证和理清之间的关系,特地写了一个demo证实:

MyActivity 充当Acivity
MyViewGroup 继承之 LinearLayout,充当ViewGroup
MyTextView 继承于TextView,充当TextView

1. MyTextView 不处理消耗任何事件,即在onTouchEvent()返回false
MyActivity  : dispatchTouchEvent: ACTION_DOWN  
MyViewGroup: dispatchTouchEvent: ACTION_DOWN
MyViewGroup: onInterceptTouchEvent: ACTION_DOWN
MyTextView: dispatchTouchEvent: ACTION_DOWN
MyTextView: onTouchEvent: ACTION_DOWN  (返回了false, 表示不消耗) 
MyViewGroup: onTouchEvent: ACTION_DOWN
MyActivity : onTouchEvent: ACTION_DOWN
MyActivity : dispatchTouchEvent: ACTION_MOVE
MyActivity : onTouchEvent: ACTION_MOVE
MyActivity : dispatchTouchEvent: ACTION_UP
MyActivity : ACTION_UP

由上面可见

  • 由dispatchTouchEvent 由 MyActivity -> MyViewGroup -> MyTextView,再由TouchEvent由 MyTextView-> MyViewGroup -> MyActivity 传递回来
  • 如果ViewGroup和View都不处理Action_down事件,后续的事件不会分发下去,都由Activity处理
2. MyTextView接收ACTION_DOWN 和 ACTION_MOVE
MyActivity: dispatchTouchEvent: ACTION_DOWN
MyViewGroup: dispatchTouchEvent: ACTION_DOWN
MyViewGroup: onInterceptTouchEvent: ACTION_DOWN
MyTextview: dispatchTouchEvent: ACTION_DOWN
MyTextview: onTouchEvent: ACTION_DOWN  (返回了true, 表示消耗)

MyActivity: dispatchTouchEvent: ACTION_MOVE
MyViewGroup: dispatchTouchEvent: ACTION_MOVE
MyViewGroup: onInterceptTouchEvent: ACTION_MOVE
MyTextview: dispatchTouchEvent: ACTION_MOVE (返回了true, 表示消耗)
MyTextview: onTouchEvent: ACTION_MOVE 
MyActivity: onTouchEvent: ACTION_MOVE

MyActivity: dispatchTouchEvent: ACTION_UP
MyViewGroup: dispatchTouchEvent: ACTION_UP
MyViewGroup: onInterceptTouchEvent: ACTION_UP
MyTextview: dispatchTouchEvent: ACTION_UP (返回了true, 表示消耗)
MyTextview: onTouchEvent: ACTION_UP
MyActivity: onTouchEvent: ACTION_UP

上面是一个完整的流程 ACTION_DOWN -> ACTION_MOVE -> ACTION_UP

3. MyGroupView 拦截ACTION_DOWN,但是MyGroupView不消费ACTION_DOWN事件
MyActivity: dispatchTouchEvent: ACTION_DOWN
MyViewGroup: dispatchTouchEvent: ACTION_DOWN
MyViewGroup: onInterceptTouchEvent: ACTION_DOWN
MyViewGroup: onTouchEvent: ACTION_DOWN (在这里返回了false,表示不消费)
MyActivity: onTouchEvent: ACTION_DOWN
MyActivity: dispatchTouchEvent: ACTION_MOVE
MyActivity: onTouchEvent: ACTION_MOVE
MyActivity: dispatchTouchEvent: ACTION_UP
MyActivity: onTouchEvent: ACTION_UP

可见

  • ViewGroup 在 onInterceptTouchEvent 拦截开始事件ACTION_DOWN,后续的事件不会走onInterceptTouchEvent,同时事件ACTION_DOWN和后续事件会传递到该 ViewGroup 的 onTouchEvent 方法中
  • ViewGroup不处理ACTION_DOWN,后续的事件MOVE、UP也不会传给该ViewGroup,最后都被Activity处理
4. MyGroupView 拦截ACTION_DOWN并在 onTouchEvent消费ACTION_DOWN
MainActivity: dispatchTouchEvent: ACTION_DOWN
MyViewGroup: dispatchTouchEvent: ACTION_DOWN
MyViewGroup: onInterceptTouchEvent: ACTION_DOWN 
(拦截了,Action_Down 会去到MyViewGroup的onTouchEvent)
MyViewGroup: onTouchEvent: ACTION_DOWN
MainActivity: dispatchTouchEvent: ACTION_MOVE
MyViewGroup: dispatchTouchEvent: ACTION_MOVE
MyViewGroup: onTouchEvent: ACTION_MOVE
MainActivity: onTouchEvent: ACTION_MOVE
MainActivity: dispatchTouchEvent: ACTION_UP
MyViewGroup: dispatchTouchEvent: ACTION_UP
MyViewGroup: onTouchEvent: ACTION_UP
MainActivity: onTouchEvent: ACTION_UP

上面验证

  • ViewGroup 在 onInterceptTouchEvent 拦截开始事件ACTION_DOWN,后续的事件不会走onInterceptTouchEvent,同时事件ACTION_DOWN和后续事件会传递到该 ViewGroup 的 onTouchEvent 方法中
5. MyGroupView 不拦截 ACTION_DOWN, 但是拦截ACTION_MOVE,并且ACTION_DOWN在MyTextView消费
MainActivity: dispatchTouchEvent: ACTION_DOWN
MyViewGroup: dispatchTouchEvent: ACTION_DOWN
MyViewGroup: onInterceptTouchEvent: ACTION_DOWN 
(不拦截,ACTION_DOWN)
MyTextview: dispatchTouchEvent: ACTION_DOWN
MyTextview: onTouchEvent: ACTION_DOWN

MainActivity: dispatchTouchEvent: ACTION_MOVE
MyViewGroup: dispatchTouchEvent: ACTION_MOVE
MyViewGroup: onInterceptTouchEvent: ACTION_MOVE 
(拦截了, 但是不会去到MyViewGroup的onTouchEvent)
MyTextview: dispatchTouchEvent: ACTION_CANCEL
MyTextview: onTouchEvent: ACTION_CANCEL
MainActivity: onTouchEvent: ACTION_MOVE

MainActivity: dispatchTouchEvent: ACTION_UP
MyViewGroup: dispatchTouchEvent: ACTION_UP
 (不会走onInterceptTouchEvent)
MyViewGroup: onTouchEvent: ACTION_UP
MainActivity: onTouchEvent: ACTION_UP
  • 一旦拦截onInterceptTouchEvent过,后续就不会走onInterceptTouchEvent
  • VIewGroup 在 onInterceptTouchEvent 拦截半路事件如ACTION_MOVE,后续也不会走onInterceptTouchEvent,同时会转成一个ACTION_CANCEL事件传给之前处理事件的View的onTouchEvent,而且ACTION_MOVE事件是不会传到ViewGroup的onTouchEvent。只有后续的事件如ACTION_UP才会传递到 ViewGroup 的 onTouchEvent

如何处理滑动事件冲突

在《Android艺术开发》里面主要有两种方法:

  1. 外部拦截法
    父容器需要则拦截,不需要则不拦截,在onInterceptTouchEvent() 控制,并且ACTION_DOWN不能拦截,在view的onTouchEvent返回true,否侧后续事件就不走过来了。
public boolean onInterceptTouchEvent(MotionEvent event) {
    boolean intercepted = false;
    int x = (int)event.getX();
    int y = (int)event.getY();
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN: {
            intercepted = false;
           break;
       }
       case MotionEvent.ACTION_MOVE: {
           if (满足父容器的拦截要求) {
                intercepted = true;
           } else {
                intercepted = false;
           }
           break;
       }
       case MotionEvent.ACTION_UP: {
           intercepted = false;
           break;
       }
       default:
           break;
       }
            mLastXIntercept = x;
            mLastYIntercept = y;
            return intercepted;
}
  1. 内部拦截法
    父容器不拦截任何事件,将所有事件传递给View,在View的dispatchTouchEvent中控制, 如果需要则消耗掉,如果不需要则通过 requestDisallowInterceptTouchEvent 方法交给父容器处理
public boolean dispatchTouchEvent(MotionEvent event) {
     int x = (int) event.getX();
     int y = (int) event.getY();

     switch (event.getAction()) {
     case MotionEvent.ACTION_DOWN: {
         parent.requestDisallowInterceptTouchEvent(true);
         break;
     }
     case MotionEvent.ACTION_MOVE: {
         int deltaX = x - mLastX;
         int deltaY = y - mLastY;
         if (父容器需要此类点击事件) {
             parent.requestDisallowInterceptTouchEvent(false);
         }
         break;
     }
     case MotionEvent.ACTION_UP: {
         break;
     }
     default:
         break;
     }

     mLastX = x;
     mLastY = y;
     return super.dispatchTouchEvent(event);
 }

父容器的onInterceptTouchEvent

public boolean onInterceptTouchEvent(MotionEvent event) {
     int action = event.getAction();
     if (action == MotionEvent.ACTION_DOWN) {
         return false;
     } else {
         return true;
     }
 }

requestDisallowInterceptTouchEvent 控制父容器是否走onInterceptTouchEvent。
这也是为什么需要在父容器的onInterceptTouchEvent不拦截ACTION_DOWN,否则都没机会走到子View的dispatchTouchEvent

无论是哪种拦截法,都是必须在子View中处理消耗ACTION_DOWN事件,否则接受不到后续事件

参考:
https://www.jianshu.com/p/2be492c1df96

https://www.cnblogs.com/lbangel/p/3791431.html

https://www.jianshu.com/p/ea0108d8510e

https://www.jianshu.com/p/8236278676fe

猜你喜欢

转载自blog.csdn.net/qq_15893929/article/details/83240815