ViewGropw 事件分发源码解析

事件分发的主要三个方法就是

public boolean disptatchTouchEvent(MotionEvent ev);
public boolean onIterceptTouchEvent(MotionEvent ev);
public boolean onTouchEvent(MotionEvent ev);

大致流程用伪代码描述如下

public boolean dispatchTouchEvent(MotionEvent ev){
    boolean cousume =false;
    if( onInterceptTouchEvent(ev)){
        consume = onTouchEvent(ev);
    }else {
        consume = child.dispatchTouchEvent(ev);
    }
    return consume;
}

1,ViewGroup 的 onInterceptTouchEvent() 默认返回false,即不拦截触摸事件.
2,一次触摸事件总是以 MotionEvent.ACTION_DOWN 开始,中间可能有多个
3,MotionEvent.ACTION_MOVE, 最后以 MotionEvent.ACTION_UP结束.一次触摸事件通常被一个view 接受并处理掉;

final int action = ev.getAction();
//事件分发总是以down事件开始
 if (actionMasked == MotionEvent.ACTION_DOWN) {
 // mFirstTouchTarget表示一次事件分发中,由处理事件的子view组成的单链表
     mFirstTouchTarget.clear();
     mFirstTouchTarget=null;

//重置FLAG_SISALLOW_INTERCEPT 标记
    disallowIntercept=false;
}

//是否拦截,final修饰,表示只能赋值一次
  final boolean intercepted; 

//接下来是判断是否需要拦截事件
if (action == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
    //这是上文清除的那个标记,子view调用requestDisallowInterceptTouchEvent 来阻止父类拦截触摸事件
    //在上边已经将它至为了false
    if(!disallowInterctpt){
        intercepted =onIterceptTouchEvnet(ev);
    }eles{
        intercepted = false;
    }
}else{
//viewgroup来处理本次触摸事件
    intercepted = false;    
}

讲解,在最开始就把 mFirstTouchTarget 和 disallowIntercept 都重置,所
以第一次,所以会执行 onIterceptTouchEvnet(ev)方法,来决定是否拦截,
1,如果onIterceptTouchEvnet返回true,那么 mFirstTouchTarget就为null , 那么down之后的move,up就都交给 本viewgroup来处理,不会再走onIterceptTouchEvnet()方法;
2.如果onIterceptTouchEvnet 返回 false,表示不拦截,就会在下边让mFirstTouchTarget得到处理触摸事件的view,最后的move,up事件都会走 vp的onIterceptTouchEvnet 判断师傅拦截.
3.view 调用requestDisallowInterceptTouchEvent() 阻止父类拦截down以外的所有事件.

接着看代码
//是否是anciton_cancel
final boolean canceled = action==MotionEvent.ACTION_CANCEL; 
//是否完成了事件分发
 boolean alreadyDispatchedToNewTouchTarget = false;

 //最先接受事件分发的子view对于的targert
 //TouchTarget  将 view 和触摸事件的 idbit 联系起来,同时他还是一个链表的节点结构,由一个 public TouchTarget next; 成员
 TouchTarget newTouchTarget = null; 

 //这里是事件分发的代码,不是取消事件,也没被拦截,就需要进行分发
 if (!canceled && !intercepted) {
     if(action=MotionEvent.ACTION_DOWN){  

     //idBit时触摸点的id,是为了区分多点触控和单点触控的标志
         final int idBitsToAssign=event.getPointId(); 

         //获得vp的子view的个数 
         final int childrenCount = mChildrenCount;

         //有子view但还没有子view处理触摸事件
        if (newTouchTarget==null&&childrenCount != 0) {

        //拿到具体触摸事件的坐标,相对于vp本身的坐标
         final float x = ev.getX();
         final float y = ev.getY();

         //得到一个处理事件分发的子view的排序集合 z坐标由小到大-触摸事件完成后这个集合就会销毁
        final ArrayList<View> preorderedList =buildTouchDispatchChildList();

        //mChildren 是vp的子view的数组集合
        final View[] children = mChildren;

        ///遍历从后向前得到view(z坐标从大向小)
         for (int i = childrenCount - 1; i >= 0; i--) {

            //这个方法其实就是 preorderedList不为空,就从preorderedList里得到view,为空就在 childern 里得到view
            final View child=getAndVerifyPreorderedView(preorderedList, children,childIndex);


            //child能否接受点击事件-child可见,或正在执行动画,且触摸坐标在动画范围里
             if (!canViewReceivePointerEvents(child)|| !isTransformedTouchPointInView(x, y, child, null)) {
                    continue;
             }
            //到这里时,表示这个child 可以处理触摸事件

            //找到child关联的touchtarget,第一次肯定为null
             newTouchTarget = getTouchTarget(child);

            if (newTouchTarget != null) {
                //child 接受了点击事件,把这次点击的 事件id 交给child的touchtarget
               //相当于事件被传递到子view
               newTouchTarget.pointerIdBits |= idBitsToAssign;
               break;                      
             }

上述代码就是以view视图的z坐标进行遍历,找到触摸事件的坐标所处的那个view,将触摸事件的pointerId交个这个view对应的ToiuchTarget,这样,这个vew和触摸事件就建立起来了关联.同时跳出循环

            接着看代码

            //这个方法是真正进行事件分发的地方,如果第三个参数 child 不为空,就由调用
            //child的dispatchTouchEvent(event);方法,为空的话,就调用
            super.dispatchTouchEvent(event);交给了vp的父类处理,
            //为什么要交给父类?因为上边提到了 if (!canceled && !intercepted) 
            //表示vp不拦截事件,即vp自己不处理,而子view也不处理的话,就把事件交给vp的
            //上一层 这个方法的返回结果表示是否处理了事件,第二个参数表面是否
            // action==MotionEvent.ACTION_CANCEL,最后的参数是 本次的pointerId,
            //这个方法里还同样考虑的多点触摸的处理..不过没看懂
             if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {

                //新建一个touchTarget,关联上child,pointerId,并把它连接到
                //mFistTouchTargt的单链表上,作为最先处理本次触摸事件剩下的 
                //up,move事件的view
                final TouchTarget target = TouchTarget.obtain(child, idBitsToAssign);
                 target.next = mFirstTouchTarget;
                 mFirstTouchTarget = target;

                //表示完成了事件分发
                alreadyDispatchedToNewTouchTarget = true;
                 break;
             }

        }//这是遍历view的结束框

        //销毁z坐标排序的view序列,防止内存泄漏
        if (preorderedList != null) preorderedList.clear();
     }

        //如果本次遍历vp的子view没有找到处理事件的((newTouchTarget == null )
        //就从之前的处理touch的列表中找到合适的view,把触摸事件和他相关联
        //进入这里的情况应该是上边
        dispatchTransformedTouchEvent时,child进行了事件分发,但是返回了false,即子view能接受到触摸事件却没处理,就只能找到他上层的view处理
         if (newTouchTarget == null && mFirstTouchTarget != null) {

               newTouchTarget = mFirstTouchTarget;
               while (newTouchTarget.next != null) {
                  newTouchTarget = newTouchTarget.next;
               }
                newTouchTarget.pointerIdBits |= idBitsToAssign;
           }
    }

    //接下来,这里是move,up 事件的处理

        if (mFirstTouchTarget == null) {
            //没有子view要处理,就调用super.dispatchTouchEvent(),来处理
            handled = dispatchTransformedTouchEvent(ev, canceled, null,
                    TouchTarget.ALL_POINTER_IDS);
        }else{ 
            target=mFistTouchTarget;
            while(target !=null){
                final TouchTarget next = target.next;
                 if (alreadyDispatchedToNewTouchTarget && next == newTouchTarget) {
                    handled = true;
                }else{
                        //遍历调用mFirstTargetTouchEvent的view和view对应的pointId 进行事件分发
                      if (dispatchTransformedTouchEvent(ev, cancelChild,
                            target.child, target.pointerIdBits)) {
                        handled = true;
                    }
                    target=next;
                }

            }

        }

      //对up事件的处理,就是重置所有状态 mFirstTouchTarget至为空
    if (canceled|| actionMasked == MotionEvent.ACTION_UP) {
            mFirstTouchTarget.clear;
            mFirstTouchTarget=null;
    }else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
            //应该是得到本次 pointer_up 对应的pointerId
          final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
            //遍历 mFristTouchTarget,把和这个id在target中出去
          removePointersFromTouchTargets(idBitsToRemove);
    }

    return handled;//返回分发的结果,到这里,分发事件结束
 }

所以,大致流程还是先判断是否要拦截.不拦截的话.先处理 down事件.通过down事件的处理找到子view中能进行事件处理的view,无论有或者没有, 那么之后的move,up事件就可以通过mFirstTouchTarget来决定如何进行分发,不过这里代码比较麻烦的地方是掺杂了多点触控(idBitsToAssign),及cancel事件的特殊处理,和辅助功能相关的(Accessibility,这部分没有列出来).我也是看了三遍.才明白.通过这次笔记,才最终彻底理清脉络.

接下来看 onInterceptTouchEvent 
    //通过实现这个方法,你可以观测到事件如何在子类中进行分发-需要返回false
    //无论如何,down事件都会进这个方法,而且down要么被child消耗,要么被vp消耗
    //onTouchEvnet()返回true后,move和up事件就不会进入这个方法,而是直接走
    //ouTouchEvent,如果你返回了false,那么之后的up,move,都会进入这个方法,在走到子
    //view的onTouhEvent()中.
    //这些注释是本来这个方法上的注释的摘抄
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        //这些没看懂,但是应该是基本不会发生
    if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
            && ev.getAction() == MotionEvent.ACTION_DOWN
            && ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
            && isOnScrollbarThumb(ev.getX(), ev.getY())) {
        return true;
    }
    return false;
}

至于onTouchEvent(); viewgropu 并没有实现这个方法,vp继承自view,所以直接看view的onTouchEvent()方法就好了. 不在本篇讨论范围,等我的view事件分发代码详解在写.

果然.讲出来是学习最快的方法.通过这个博客才最终理解了viewgroup的分发原理.及里边的一些细节的用处.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值