android触摸event传递源码分析二

上一篇文章分析了input event怎么传递到activity中,下面接着分析在activity中的具体传递,也是event事件传递最重要的地方。

接上回,先看看activity中收到event后:

public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction(); //这里是空函数,什么都不干
    }
    if (getWindow().superDispatchTouchEvent(ev)) {  //这里传给phoneWindow

        return true;
    }
    return onTouchEvent(ev);
}

所以会传递到phoneWindow的superDispatchTouchEvent()方法,这里可以看出,如果phoneWindow把传递的事件down, move,up拦截了,即返回了true,那么activity的onTouchEvent()将不会获得到这个事件,相反如果phoneWindow不处理这个事件,那么这个事件都会传给activity,同时也知道activity的onTouchEvent()也是最后处理event事件的,可以看下面phoneWindow代码的分析。

    @Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event); //phoneWindow的内部类DecorView
    }

事件传递给DecorView:

private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {

     @Override
     public boolean dispatchTouchEvent(MotionEvent ev) {
         final Callback cb = getCallback();
         return cb != null && !isDestroyed() && mFeatureId < 0 ? cb.dispatchTouchEvent(ev)
                    : super.dispatchTouchEvent(ev);
     }

     public boolean superDispatchTouchEvent(MotionEvent event) {
          return super.dispatchTouchEvent(event); //这里传给了DecorView父亲,即ViewGroup
     }
}

由于DecorView本身是一个FrameLayout,FrameLayout 继承于ViewGroup,那么事件最终传递到ViewGroup里面:

public boolean dispatchTouchEvent(MotionEvent ev){    
    final int action = ev.getAction();
    final int actionMasked = action & MotionEvent.ACTION_MASK;

    if (actionMasked == MotionEvent.ACTION_DOWN) {
        // Throw away all previous state when starting a new touch gesture.
        //一个down事件到来,就会认为是一个新的touch事件到来,
        //所以如果mFirstTouchTarget != null,就会给child view传递cancel事件   
        cancelAndClearTouchTargets(ev); 

        resetTouchState(); //设置mFirstTouchTarget == null
    }

    final boolean intercepted;
    //只有在down事件下才进行判断是否拦截 ,
    //mFirstTouchTarget != null 表示有child view要处理事件
    if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { 
        final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
        if (!disallowIntercept) { //默认viewgroup都是能拦截事件的
            intercepted = onInterceptTouchEvent(ev);   
            //判断当前ViewGroup有没有拦截触摸事件, 注意拦截不代表消费,
            //比如若一个viewGroup在这里onInterceptTouchEvent的down事件返回了ture, 
            //就会后面调用它自己的onTouchEvent(),看看自己是否消费此事件。

            //这里还有一种情况,不是down事件,比如move事件,而viewgroup的child view
            //消费了down事件,viewgroup中途对事件进行了拦截move事件,这里置intercepted==true,
            //还是会导致它的child view收不到以后的事件 
        } 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;  //如果不是down事件,
            //且mFirstTouchTarget == null,即没有child View 对前面的down返回true, 
            //则直接设置intercepted = true, 
            //注意最顶层的View是PhoneWindow的DecorView, 
            //所以如果它的child view没有把事件拦截了,
            //则以后所有的move,up事件都不会传给它下面的child View了,
            //这样activity里的view group或view,都有机会得到down事件,
            //但是如果不消费down事件,则以后无法得到后续的事件,
            //因为activity的最顶层DecorView就把事件给拦截了。
    }

    TouchTarget newTouchTarget = null;
    boolean alreadyDispatchedToNewTouchTarget = false;

    //如果此VierGruop 没有拦截了down事件,事件也不是cancel事件
    if (!canceled && !intercepted) { 
        if (actionMasked == MotionEvent.ACTION_DOWN                          
            || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
            || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {  //如果是down事件

            final int childrenCount = mChildrenCount;
            if (newTouchTarget == null && childrenCount != 0) { //如果child view个数不是0
                //x , y是down事件相对于此viewgroup的点击位置
                final float x = ev.getX(actionIndex);
                final float y = ev.getY(actionIndex);

                final View[] children = mChildren;
                for (int i = childrenCount - 1; i >= 0; i--) { //循环遍历child view

                    //判断点击的位置是否在此child View上
                    //canViewReceivePointerEvents是判断view是否可以接受点击事件,
                    //只有visible和动画中的view可以接受点击事件,
                    //isTransformedTouchPointInView是对点击的x,y换算,
                    //判断点击事件是否在此child view上
                   if (!canViewReceivePointerEvents(child) 
                       || !isTransformedTouchPointInView(x, y, child, null)) { 
                        continue; //如果没有点击在此child view上,则进入下一次循环
                    }

                     //这里是最关键的方法,把down事件传递给当前被点击的child View ,
                     //注意这里必须是此有child View的的子view在dispatchTouchEvent,
                     //OnTouchListener.onTouch或onTouchEvent(event)返回了true,
                     //那么dispatchTransformedTouchEvent方法才会返回true,后面会单独分析它
                    if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                        //这里设置mFirstTouchTarget = child
                        newTouchTarget = addTouchTarget(child, idBitsToAssign);  
                        alreadyDispatchedToNewTouchTarget = true;
                        break;
                    }
                }
            }
        }
    }

    //当前viewGroup的child view都不处理这次down事件,那么mFirstTouchTarget == null
    //不处理是指其Child View 的OnTouchListener.onTouch
    //或onTouchEvent(event)返回了flase 或 调用的super方法
    if (mFirstTouchTarget == null) {
        //这里又传递给了dispatchTransformedTouchEvent方法,但是注意这里第三个参数,传递了null,
        //第三个参数表示child view,传递null,其实就是传递给viewgroup自己,看看自己有没有处理。  
        handled = dispatchTransformedTouchEvent(ev, canceled, null,
                                        TouchTarget.ALL_POINTER_IDS);
    } else {
       //如果有child View处理点击事件,则down , move , up都进入这里
       TouchTarget predecessor = null;
       TouchTarget target = mFirstTouchTarget; // mFirstTouchTarget 就是被点中的child view
       while (target != null) {
           final TouchTarget next = target.next; //这里单指测试时next总为null
           if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { 
                handled = true; //如果是down 事件,已经传递过,就会进入这里
           } else {
               //如果move事件突然被viewgroup拦截了,即intercepted==true,则导致cancelChild==true
               final boolean cancelChild = resetCancelNextUpFlag(target.child)||intercepted;

               // 非down 事件会进入这里, 又递归传递了,
               //这里每个被传递过down事件的ViewGroup的mFirstTouchTarget都不为null,
               //这样递归传递直到非down事件被传到最终被点击的View上
               //注意这里面如果整个activity的child view 返回了false, 
               //则 down 和 up 事件又会传递给activity 的 onTouchEvent()

               //注意如果是viewgroup中途拦截了move或up事件,这里会给child view传递一个cancel事件,
               //并且后面会设置mFirstTouchTarget = null,这样后续事件将不会传递给child view
               if (dispatchTransformedTouchEvent(ev, cancelChild,target.child, 
                                                           target.pointerIdBits)) {
                    handled = true;
               }

               if (cancelChild) { //如果取消传递给child view
                  if (predecessor == null) {
                      //循环直到设置mFirstTouchTarget == null,
                      //那么下次事件再到来时就不会传递给child view了
                      mFirstTouchTarget = next; 
                  } else {
                      predecessor.next = next;
                  }
                  target.recycle();
                  target = next;
                  continue;
               }
            }
            predecessor = target;
            target = next;
        }
    }
    return handled;
}

下面看看上面函数里出现过三次的dispatchTransformedTouchEvent()方法,这个方法是实现传递的关键:

  private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
      final boolean handled;

      final int oldAction = event.getAction();
      //上面dispatchTouchEvent函数第三次调用本函数时,
      //若viewgroup中途拦截了事件时,cancel 会为true
      if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
          event.setAction(MotionEvent.ACTION_CANCEL); //把事件设为取消事件
          if (child == null) {
             //调用到了自己父类,因为viewgroup继承于view,最终调用到view的dispatchTouchEvent
             handled = super.dispatchTouchEvent(event);
          } else {//child不为空
             //传递给child view上,这里实现了递归一级级传递
             handled = child.dispatchTouchEvent(event);
          }
          event.setAction(oldAction);
          return handled;
      }

      final MotionEvent transformedEvent;
      if (newPointerIdBits == oldPointerIdBits) { //手指触摸数没有变化
          if (child == null || child.hasIdentityMatrix()) {
              if (child == null) {
                  //上面dispatchTouchEvent函数第二次调用本函数时,即没用child处理down事件时,
                  //传下来的child为null,这就实现了事件在viewgroup中自己本身的传递
                  //调用自己父类,因为viewgroup继承于view,最终调用view的dispatchTouchEvent
                  handled = super.dispatchTouchEvent(event);
               } else {
                  final float offsetX = mScrollX - child.mLeft;
                  final float offsetY = mScrollY - child.mTop;
                  event.offsetLocation(offsetX, offsetY);

                  //传递给child view上,这里实现了递归一级级传递,
                  //上面dispatchTouchEvent函数第一次调用本函数时,走这里
                  handled = child.dispatchTouchEvent(event);

                  event.offsetLocation(-offsetX, -offsetY);
              }
              return handled;
          }
          transformedEvent = MotionEvent.obtain(event);
      } else {
          transformedEvent = event.split(newPointerIdBits);
      }
      // Done.
      transformedEvent.recycle();
      return handled;
 }

由上面可知事件最终都会传递到view上,并首先传递到view的dispatchTouchEvent上:

public boolean dispatchTouchEvent(MotionEvent event) {
     boolean result = false;

     ListenerInfo li = mListenerInfo;
     //先会调用OnTouchListener的监听方法,看看onTouch方法是否返回true
     if (li != null && li.mOnTouchListener != null
              && (mViewFlags & ENABLED_MASK) == ENABLED
              && li.mOnTouchListener.onTouch(this, event)) { 
            result = true;
      }
     //OnTouchListener返回了true,这里就不会再调用onTouchEvent()方法了
     if (!result && onTouchEvent(event)) {
          result = true;
     }
}

public boolean onTouchEvent(MotionEvent event) {
    switch (action) {
        case MotionEvent.ACTION_UP:
        if (mPerformClick == null) {
            mPerformClick = new PerformClick();
        }
        if (!post(mPerformClick)) {
            performClick(); //执行onclick方法
        }
        break
    }
}

public boolean performClick() {
       playSoundEffect(SoundEffectConstants.CLICK);//点击声音
       li.mOnClickListener.onClick(this);   //执行onClickListener 点击回调
}

到此input event事件源码分析就完了,可以看出源码实现了一个精妙,但也很复杂的事件传递逻辑。

总结

event 事件传递流程:

phoneWindow —> Activity.dispatchTouchEvent() —> DecorView —> contentLayout(系统默认加载的activity父控件) —> activity layout —> ViewGroup —> View —> Activity.onTouchEvent()


event事件传递的函数流程:

dispatchTouchEven() —> onInterceptTouchEvent() (ViewGroup才有此方法) —> OnTouchListener.onTouch() –> onTouchEvent() —> OnClickListener.onClick()

注意:若是OnTouchListener.onTouch() 对事件返回了true,则事件不会走后面onTouchEvent和onClick。

  • down事件永远会从DecorView 一级级传递到被点击的view上,但若是在这个传递过程中,没有DecorView 下的child view对这个down事件消费,则后续的move, up事件都不会传给这些child view了。但是activity自己能拿到这些事件。

  • down事件传递过程中,如果某个ViewGroup对事件在onInterceptTouchEvent()中进行了拦截,则事件不会传递给它的child view,但是如果此ViewGroup 不在它自己onTouchEvent()对事件进行消费,DecorView 还是会认为没有child view消费触摸事件。

  • 调用super方法或return false 都是不消费事件,只有return true才算消费事件。

  • 如果child view消费了down事件后,在后续的move事件中被它的父类拦截了事件,则这个child view会收到一个cancel事件。后续事件就只会传递给它的父类。

更多精彩Android技术可以关注我们的微信公众号,扫一扫下方的二维码或搜索关注公共号:


Android老鸟
这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值