事件处理及解决事件冲突

事件传递原则:先捕获然后冒泡

  1. 在捕获阶段,事件先由外部的View接收,然后传递给其内层的View,依次传递到更够接收此事件的最小View单元,完成事件捕获过程;

  2. 在冒泡阶段,事件则从事件源的最小View单元开始,依次向外冒泡,将事件对层传递。

  3. Android中不同的控件所具有的事件分发、拦截和响应稍有不同,主要表现在Activity本身不具有事件拦截,不是ViewGroup的最小view单元也不具有事件拦截(因为它没有自己的子View)。

Android中事件分发

  1. 事件分发:dispatchTouchEvent(MotionEvent ev);自身可以消费事件返回(true)

    1. 当有事件的时候,先有当前的Activity捕获,
      • 如果返回true自身消费了,如果不想activity做任何事情可以直接返回true ;
      • 如果返回false交给上层的onTouchEvent消费时间,如果是activity的就直接交给系统消费
      • 如果返回super.dispatchTouchEvent(),
        1. 如果是activity交给ViewGroup的diapatchTouchEvent(),
        2. 如果是子View就给自身的onTouchEvent消费
        3. 如果是ViewGroup就交给自身的onInterceptTouchEvent()处理
  2. 事件拦截:只有ViewGroup拥有

    • 如果返回true表示拦截事件交给本层的onTouchEvent()处理事件
    • 如果返回false或者super.onInterceptTouchEvent(ev)都不拦截事件交给子View的dispatchTouchEvent()处理事件
  3. 事件处理:onTouchEvent();

    • 如果返回true表示事件由本层消费以后完结,不会再向上冒泡传递;
    • 如果返回false,或者默认的super.onTouchEvent()事件将在本层做处理后上层View冒泡传递给上层View的onTouchEvent()做处理
  4. 总结为:

    1. 从以上过程中可以看出,dispatchTouchEvent无论返回true还是false,事件都不再进行分发,

    2. 只有当其返回super.dispatchTouchEvent(ev),才表明其具有向下层分发的愿望,

    3. 但是是否能够分发成功,则需要经过事件拦截onInterceptTouchEvent的审核。事件是否具有冒泡特是由onTouchEvent的返回值决定的。

    4. 注意: ACTION_DOWN是跟着时间传递一起走的,而ACTION_MOVE与ACTION_UP

      • ACTION_DOWN事件在哪个控件消费了(return true), 那么ACTION_MOVE和ACTION_UP就会从上往下(通过dispatchTouchEvent)做事件分发往下传,就只会传到这个控件,不会继续往下传;

      • 如果ACTION_DOWN事件是在dispatchTouchEvent消费,那么事件到此为止停止传递,三个传递是一样儿的;

      • 如果ACTION_DOWN事件是在onTouchEvent消费的,那么会把ACTION_MOVE或ACTION_UP事件传给该控件的onTouchEvent处理并结束传递。

这里写图片描述

事件冲突的解决方法(如:pullToRefresh跟viewPager的冲突)

分两种情况: 外部拦截法 和内部拦截法

外部拦截法:

  • 在ViewGroup的onInterceptTouchEvent(MotionEvent ev)中判断ev的ACTION_DOWN(return false),ACTION_MOVE;ACTION_UP(return false理由同DOWN)属性,注意ACTION_DOWN不能返回true,否则剩下的两个事件将只能有自身消费不能传递给子View;在ACTION_MOVE中判断上下左右移动的位置,分别设置为true拦截,false不拦截给子类处理即可;

    /**
     * 拦截事件
     * @param ev
     * @return
     */
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean intercepted = false;
        int x = (int) ev.getX();
        int y = (int) ev.getY();
    
        switch (ev.getAction()) {
            /*如果拦截了Down事件,则子类不会拿到这个事件序列,整个事件只能有本身处理*/
            case MotionEvent.ACTION_DOWN:
                lastXIntercept = x;
                lastYIntercept = y;
                intercepted = false;
                if (!mScroller.isFinished()) {
                    mScroller.abortAnimation();
                    intercepted = true;
                }
                break;
            case MotionEvent.ACTION_MOVE:
                final int deltaX = x - lastXIntercept;
                final int deltaY = y - lastYIntercept;
                /*根据条件判断是否拦截该事件*/
                if (Math.abs(deltaX) > Math.abs(deltaY)) {
                    intercepted = true;
                } else {
                    intercepted = false;
                }
                break;
            case MotionEvent.ACTION_UP:
                intercepted = false;
                break;
    
        }
        lastXIntercept = x;
        lastYIntercept = y;
        return intercepted;
    }

内部拦截法

  • 主要是requestDisallowInterceptTouchEvent(boolean),这个标志可以决定父控件是否拦截事件,如果设置了这个标志则不拦截,如果没设这个标志,它就会调用父控件的onInterceptTouchEvent()来询问父控件是否拦截。但这个标志对Down事件无效。

     if (actionMasked == MotionEvent.ACTION_DOWN) {
                cancelAndClearTouchTargets(ev);
                //清楚标志
                resetTouchState();
            }
    
            // Check for interception.
            final boolean intercepted;
            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;
            }
  • 重写子View的diapatchTouchEvent()

    public boolean dispatchTouchEvent(MotionEvent event) {
    ...
    
    switch (action) {
      case MotionEvent.ACTION_MOVE:
          /**
          跟上面的 !disallowIntercept  对应,当为true的时候表示父控件不拦截 false:拦截事件父类消费
          */
            getParent().requestDisallowInterceptTouchEvent(true);
    
        break;
      case MotionEvent.ACTION_MOVE:
        if(子元素需要处理此事件)
              getParent().requestDisallowInterceptTouchEvent(true);
    
        break;
      case MotionEvent.ACTION_UP: {
        break;
    }
    ...
    return super.dispatchTouchEvent(event);
    ;
    }
  • 同时重写父类的onInterceptTouchEvent(),可以通过继承重写的方法

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
    
     /**
     当为MotionEvent.ACTION_DOWN不拦截事件否则子类将不可能收到传递的事件
     */
      int action=ev.getAction();
      if(action==MotionEvent.ACTION_DOWN){
        return false;
      }else {
        return true;
      }
    }

猜你喜欢

转载自blog.csdn.net/wei_ai_n/article/details/72853507