美文网首页Android开发Android开发经验谈Android技术知识
View的事件分发机制和滑动冲突解决总结

View的事件分发机制和滑动冲突解决总结

作者: Joe_blake | 来源:发表于2018-06-08 14:53 被阅读28次

    Touch事件处理和传递

    1. 事件分类:

      ​ ACTION_DOWN, ACTION_UP, ACTION_MOVE:按下、离开、移动

      ​ ACTION_POINTER_DOWN, ACTION_POINTER_UP:多点按下、离开

      ​ ACTION_CANCEL:当控件收到前驱事件(前驱事件:一个从DOWN一直到UP的所有事件组合称为完整的手势,中间的任意一次事件对于下一个事件而言就是它的前驱事件)之后,后面的事件如果被父控件拦截,那么当前控件就会收到一个CANCEL事件,并且把这个事件会传递给它的子View,没有子View就调用父容器的dispatchTouchEvent()。

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

      ViewGroup的dispatchTransformedTouchEvent方法对ACTION_CANCEL的处理

    2. 事件处理方法:

      • 传递—-dispatchTouchEvent():

        只要事件传递到了当前View,那么dispatchTouchEcent方法就一定会被调用。返回结果表示是否消耗当前事件。

      • 拦截——onInterceptTouchEvent():

        在dispatchTouchEcent方法内部调用此方法,用来判断是否拦截某个事件。如果当前View拦截了某个事件,那 么在这同一个事件序列中,此方法不会再次被调用。返回结果表示是否拦截当前事件。

      • 消费—-onTouchEvent()函数和OnTouchListener():

        在dispatchTouchEcent方法内调用此方法,用来处理事件。返回结果表示是否处理当前事件,如果不处理,那么在同一个事件序列里面,当前View无法再收到后续的事件。

    3. Android事件传递结论:

      1. 事件都是从Activity.dispatchTouchEvent()开始传递
      2. 事件由父View传递给子View,ViewGroup可以通过onInterceptTouchEvent()方法对事件拦截,停止其向子view传递
      3. 某个View一旦决定拦截事件,那么这个事件序列之后的事件都会由它来处理,并且不会再调用onInterceptTouchEvent。
      4. 如果View没有对ACTION_DOWN进行消费,之后的其他事件不会传递过来,也就是说ACTION_DOWN必须返回true,之后的事件才会传递进来
      5. 如果一个View处理了down事件,却没有处理其他事件,那么这些事件不会交给父元素处理,并且这个View还能继续受到后续的事件。而这些未处理的事件,最终会交给Activity来处理。
      6. ViewGroup的onInterceptToucheEvent默认返回false,也就是默认不拦截事件。
      7. View没有InterceptTouchEvent方法,如果有事件传过来,就会直接调用onTouchEvent方法。
      8. View的enable属性不会影响onTouchEvent的默认返回值。就算一个View是不可见的,只要他是可点击的(clickable或者longClickable有一个为true),它的onTouchEvent默认返回值也是true。
      9. 如果事件从上往下传递过程中一直没有被停止,且最底层子View没有消费事件,事件会反向往上传递,这时父View(ViewGroup)可以进行消费,如果还是没有被消费的话,最后会到Activity的onTouchEvent()函数。
      10. OnTouchListener优先于onTouchEvent()对事件进行消费
    4. View拦截处理事件的流程,伪代码表示:

    public boolean dispatchTouchEvent(MotionEvent ev){     
        boolean consum = false;     
        if(onInterceptTouchEvent(ev)){      //判断是否拦截        
            consum = onTouchEvent(ev);      //处理事件 
        }else{                              //不拦截,则交给子View的dispatchTouchEvent()方法处理,
            consum = child.dispatchTouchEvent(ev);     
        }          
        return consum; 
    }
    

    滑动冲突解决

    外部拦截法:

    • 事件都经过父容器的拦截处理,如果父容器需要则拦截,如果不需要则不拦截。

    • 改写父容器的onInterceptTouchEvent(MotionEvent event)方法,确定是否拦截事件:

      • ACTION_DOWN事件返回false,使事件可以传递给子View。

      • ACTION_MOVE事件判断拦截时返回true,不拦截返回false。

      • ACTION_UP事件返回false,否则当子View处理时,无法处理click事件。

    • Example:

    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;
           }
           return intercepted;
    }
    
    

    内部处理法:

    • 父容器不拦截任何事件,将所有事件传递给子元素,如果子元素需要则消耗掉,如果不需要则通过requestDisallowInterceptTouchEvent方法交给父容器处理。

    • 重写父容器的onInterceptTouchEvent方法

    • 重写子View的dispatchTouchEvent(MotionEvent event)方法

    • Example:

    //重写父容器的onInterceptTouchEvent
    public boolean onInterceptTouchEvent(MotionEvent event) {
    
         int action = event.getAction();
         if (action == MotionEvent.ACTION_DOWN) {//不处理DOWN事件,全部交给子View判断
             return false;
         } else {
             return true;
         }
     }
    
    //重写子View的dispatchTouchEvent方法
    public boolean dispatchTouchEvent(MotionEvent event) {
         int x = (int) event.getX();
         int y = (int) event.getY();
    
         switch (event.getAction()) {
         case MotionEvent.ACTION_DOWN: {
            //父Viewgroup不处理onInterceptTouchEvent回调
             parent.requestDisallowInterceptTouchEvent(true);
             break;
         }
         case MotionEvent.ACTION_MOVE: {
             int deltaX = x - mLastX;
             int deltaY = y - mLastY;
             if (父容器需要此类点击事件) {
                //需要父Viewgroup处理onInterceptTouchEvent回调,拦截事件
                 parent.requestDisallowInterceptTouchEvent(false);
             }
             break;
         }
         case MotionEvent.ACTION_UP: {
             break;
         }
         default:
             break;
         }
    
         mLastX = x;
         mLastY = y;
         return super.dispatchTouchEvent(event);
     }
    

    相关文章

      网友评论

        本文标题:View的事件分发机制和滑动冲突解决总结

        本文链接:https://www.haomeiwen.com/subject/vylasftx.html