ViewGroup/View 事件分发和疑惑详解手机开发

这两天今天重新回看了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 : onTouchEvent: 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. MyViewGroup 拦截ACTION_DOWN,但是MyViewGroup不消费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. MyViewGroup 拦截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. MyViewGroup 不拦截 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_CANCEL 
 
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://blog.ytso.com/6272.html

(0)
上一篇 2021年7月17日
下一篇 2021年7月17日

相关推荐

发表回复

登录后才能评论