AndroidTouch分发机制续篇

  发布日期:   2018-08-24
  最新修改:   2020-07-05
  阅读次数:   36 次

图解刨之
sequenceDiagram participant Activity participant PhoneWindow participant DecorView participant ViewGroup participant Child Activity -->> Activity :1 、boolean dispatchTouchEvent(MotionEvent ev)->解析点1 Activity ->> PhoneWindow :2、getWindow().superDispatchTouchEvent(ev)->解析点2 PhoneWindow ->> DecorView :3、 return this.mDecor.superDispatchTouchEvent(event)->解析点3 DecorView ->> ViewGroup : 4、return super.dispatchTouchEvent(event)->解析点4 ViewGroup -->> ViewGroup:第1步:开始处理时间分发onFilterTouchEventForSecurity(ev) ViewGroup -->> ViewGroup:第2步:检测是否需要清空目标和状态 ViewGroup -->> ViewGroup:第3步:检查child是否允许parent可以拦截 requestDisallowInterceptTouchEvent() ViewGroup -->> ViewGroup:第4步:如果child允许parent可以拦截,就会调用onInterceptTouchEvent()询问parent是否要拦截 ViewGroup -->> ViewGroup:第5步:如果没有child可响应,则分发给自己,有则分发给child,通过调用dispatchTransformedTouchEvent是否传递有第三个参数进行区分 ViewGroup -->> dispatchTouchEvent:第6步:调用dispatchTouchEvent(event) dispatchTouchEvent -->> dispatchTouchEvent:先调用onTouch事件 dispatchTouchEvent -->> dispatchTouchEvent:onTouch返回false时继续调用onTouchEvent(event) ViewGroup -->> ViewGroup:第7步:再次检查取消标记,并进行相应的处理

总结:

  • 1、首先Activity接收到touch事件后在dispatchTouchEvent方法中会调用DecorView的superDispatchTouchEvent方法;
  • 2、DecorView最终调用的是viewGroup的3、dispatchTouchEvent方法;
  • 3、在viewgroup的dispatchTouchEvent方法中会做判断:
    • 1)检查disallowIntercept
    • 2)disallowIntercept允许拦截:检查onInterceptTouchEvent是否真的要拦截,如果拦截则event不再分发下去,调用自己的dispatchTouchEvent方法,如果不拦截,则调用ViewGroup自己dispatchTransformedTouchEvent方法,
    • 3)在dispatchTransformedTouchEvent里又做了两件事,如果有childview,就调用childview的dispatchTouchEvent方法将事件分发下去,如果没有childview,仍然是调用自己的dispatchTouchEvent方法。
    • 4)所以调用view的dispatchTouchEvent有两种情况,一种是拦截了,不传给子view了,一种是没有子view了;
解析:
  • 解析点1

      public boolean dispatchTouchEvent(MotionEvent ev) {
    
          if (ev.getAction() == MotionEvent.ACTION_DOWN) {
              onUserInteraction();
          }
          //先将事件分发给window,如果windown消费就直接return true结束本次touch事件
          if (getWindow().superDispatchTouchEvent(ev)) {
              return true;
          }
          //window未消费时走onTouchEvent事件
          return onTouchEvent(ev);
      }
  • 小结:代码比较短,但重点肯定在getWindow().superDispatchTouchEvent(ev),不然如果走onTouchEvent,之后也就没啥好说的了,我们移步到getWindow().superDispatchTouchEvent(ev),getWindow其实就是PhoneWindow的实例

  • 解析点2

     public boolean superDispatchTouchEvent(MotionEvent event) {
    
         //方法很简单,里面调用的是mDecor的superDispatchTouchEvent
         //DecorView是PhoneWindow的一个final的内部类并且继承FrameLayout的,
         //也是Window界面的最顶层的View对象,其实decorView是在activity的setContentView方法里面构建的
    
          return this.mDecor.superDispatchTouchEvent(event);
      }
  • 解析点3

     //DecorView类的定义
     private class DecorView extends FrameLayout implements RootViewSurfaceTaker {}
    
     public boolean superDispatchTouchEvent(MotionEvent event) {
               //其调用了parent的方法,也就是Framelayout的方法,而FrameLayout并没有重写该方法,而FraneLayout的parent是ViewGroup,故直奔ViewGroup
                return super.dispatchTouchEvent(event);
            }
  • 解析点4,由解析点3可知,实际是调用viewGroup的,代码较长,只保留重要代码

     public boolean dispatchTouchEvent(MotionEvent ev) {
    
             //调试用,不理会
            if (this.mInputEventConsistencyVerifier != null) {
                this.mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
            }
    
            if (ev.isTargetAccessibilityFocus() && this.isAccessibilityFocusedViewOrHost()) {
                ev.setTargetAccessibilityFocus(false);
            }
    
            //第1步:开始处理时间分发
            //onFilterTouchEventForSecurity()表示是否要分发该触摸事件。 
            //如果该View不是位于顶部,并且有设置属性使该View不在顶部时不响应触摸事件,则不分发该触摸事件,即返回false。
            //否则,则对触摸事件进行分发,即返回true。
            boolean handled = false;
            if (this.onFilterTouchEventForSecurity(ev)) {
                int action = ev.getAction();
                //取低8位 该值为触摸类型,是down 还是move 或者up之类的
                int actionMasked = action & MotionEvent.ACTION_MASK;
    
                // 第2步:检测是否需要清空目标和状态
                // 如果是ACTION_DOWN(即按下事件),则清空之前的触摸事件处理目标和状态。
                // 这里的情况状态包括:
                // (01) 清空mFirstTouchTarget链表,并设置mFirstTouchTarget为null。
                //      mFirstTouchTarget是"接受触摸事件的View"所组成的单链表
                // (02) 清空mGroupFlags的FLAG_DISALLOW_INTERCEPT标记
                //      如果设置了FLAG_DISALLOW_INTERCEPT,则不允许ViewGroup对触摸事件进行拦截。
                // (03) 清空mPrivateFlags的PFLAG_CANCEL_NEXT_UP_EVEN标记
    
                if (actionMasked == 0) {
                    this.cancelAndClearTouchTargets(ev);
                    this.resetTouchState();
                }
    
                // 第3步:检查当前ViewGroup是否想要拦截触摸事件
                // 
                // 是的话,设置intercepted为true;否则intercepted为false。
                // 如果不是"按下事件(ACTION_DOWN)" 且 mFirstTouchTarget为null;设置intercepted为true
                // 否则的话,就执行if代码块里面的内容。
                boolean intercepted;
                boolean canceled;
                if (actionMasked != 0 && this.mFirstTouchTarget == null) {
                    intercepted = true;
                } else {
    
                    // 检查禁止拦截标记:FLAG_DISALLOW_INTERCEPT
                    // 如果调用了requestDisallowInterceptTouchEvent()标记的话,则FLAG_DISALLOW_INTERCEPT会为true。
                    // 例如,ViewPager在处理触摸事件的时候,就会调用requestDisallowInterceptTouchEvent()
                    //     ,禁止它的父类对触摸事件进行拦截
    
                    canceled = (this.mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                    if (!canceled) {
                         // 如果禁止拦截标记为false的话,则调用onInterceptTouchEvent();并返回拦截状态。   
                        intercepted = this.onInterceptTouchEvent(ev);
                        ev.setAction(action);
                    } else {
                        intercepted = false;
                    }
                }
    
                if (intercepted || this.mFirstTouchTarget != null) {
                    ev.setTargetAccessibilityFocus(false);
                }
    
                // 第4步:检查当前的触摸事件是否被取消
                // 
                // (01) 对于ACTION_DOWN而言,mPrivateFlags的PFLAG_CANCEL_NEXT_UP_EVENT位肯定是0;因此,canceled=false。
                // (02) 当前的View或ViewGroup要被从父View中detach时,PFLAG_CANCEL_NEXT_UP_EVENT就会被设为true;
                //      此时,它就不再接受触摸事情。
                canceled = resetCancelNextUpFlag(this) || actionMasked == MotionEvent.ACTION_CANCEL;
    
                // 第5步:将触摸事件分发给"当前ViewGroup的子View和子ViewGroup"
                // 
                // 如果触摸"没有被取消",同时也"没有被拦截"的话,则将触摸事件分发给它的子View和子ViewGroup。  
                // 如果当前ViewGroup的孩子有接受触摸事件的话,则将该孩子添加到mFirstTouchTarget链表中。
    
                boolean split = (this.mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                ViewGroup.TouchTarget newTouchTarget = null;
                boolean alreadyDispatchedToNewTouchTarget = false;
                int idBitsToRemove;
                if (!canceled && !intercepted) {
                    View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus() ? this.findChildWithAccessibilityFocus() : null;
                    if (actionMasked == 0 || split && actionMasked == 5 || actionMasked == 7) {
    
                        // 这是获取触摸事件的序号 以及 触摸事件的id信息。
                        // (01) 对于ACTION_DOWN,actionIndex肯定是0
                        // (02) 而getPointerId()是获取的该触摸事件的id,并将该id信息保存到idBitsToAssign中。
                        //    这个触摸事件的id是为多指触摸而添加的;对于单指触摸,getActionIndex()返回的肯定是0;
                        //    而对于多指触摸,第一个手指的id是0,第二个手指的id是1,第三个手指的id是2,...依次类推。
                        idBitsToRemove = ev.getActionIndex();
                        int idBitsToAssign = split ? 1 << ev.getPointerId(idBitsToRemove) : -1;
    
                        // 清空这个手指之前的TouchTarget链表。
                        // 一个TouchTarget,相当于一个可以被触摸的对象;它中记录了接受触摸事件的View
                        this.removePointersFromTouchTargets(idBitsToAssign);
                        int childrenCount = this.mChildrenCount;
    
                        // 获取该ViewGroup包含的View和ViewGroup的数目,
                        // 然后递归遍历ViewGroup的孩子,对触摸事件进行分发。
                        // 递归遍历ViewGroup的孩子:是指对于当前ViewGroup的所有孩子,都会逐个遍历,并分发触摸事件;
                        //   对于逐个遍历到的每一个孩子,若该孩子是ViewGroup类型的话,则会递归到调用该孩子的孩子,...
                        if (newTouchTarget == null && childrenCount != 0) {
                            float x = ev.getX(idBitsToRemove);
                            float y = ev.getY(idBitsToRemove);
                            ArrayList<View> preorderedList = this.buildOrderedChildList();
                            boolean customOrder = preorderedList == null && this.isChildrenDrawingOrderEnabled();
                            View[] children = this.mChildren;
    
                            for(int i = childrenCount - 1; i >= 0; --i) {
                                int childIndex = customOrder ? this.getChildDrawingOrder(childrenCount, i) : i;
                                View child = preorderedList == null ? children[childIndex] : (View)preorderedList.get(childIndex);
    
                                // 如果child可以接受触摸事件,
                                // 并且触摸坐标(x,y)在child的可视范围之内的话;
                                // 则继续往下执行。否则,调用continue。
                                // child可接受触摸事件:是指child的是可见的(VISIBLE);或者虽然不可见,但是位于动画状态。
                                if (childWithAccessibilityFocus != null) {
                                    if (childWithAccessibilityFocus != child) {
                                        continue;
                                    }
    
                                    childWithAccessibilityFocus = null;
                                    i = childrenCount - 1;
                                }
    
                                if (canViewReceivePointerEvents(child) && this.isTransformedTouchPointInView(x, y, child, (PointF)null)) {
    
                                    // getTouchTarget()的作用是查找child是否存在于mFirstTouchTarget的单链表中。
                                    // 是的话,返回对应的TouchTarget对象;否则,返回null。
                                    newTouchTarget = this.getTouchTarget(child);
                                    if (newTouchTarget != null) {
                                        newTouchTarget.pointerIdBits |= idBitsToAssign;
                                        break;
                                    }
    
                                     // 重置child的mPrivateFlags变量中的PFLAG_CANCEL_NEXT_UP_EVENT位。
                                    resetCancelNextUpFlag(child);
    
                                     // 调用dispatchTransformedTouchEvent()将触摸事件分发给child。
                                    if (this.dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
    
                                        // 如果child能够接受该触摸事件,即child消费或者拦截了该触摸事件的话;
                                        // 则调用addTouchTarget()将child添加到mFirstTouchTarget链表的表头,并返回表头对应的TouchTarget
                                        // 同时还设置alreadyDispatchedToNewTouchTarget为true。
                                        this.mLastTouchDownTime = ev.getDownTime();
                                        if (preorderedList != null) {
                                            for(int j = 0; j < childrenCount; ++j) {
                                                if (children[childIndex] == this.mChildren[j]) {
                                                    this.mLastTouchDownIndex = j;
                                                    break;
                                                }
                                            }
                                        } else {
                                            this.mLastTouchDownIndex = childIndex;
                                        }
    
                                        this.mLastTouchDownX = ev.getX();
                                        this.mLastTouchDownY = ev.getY();
                                        newTouchTarget = this.addTouchTarget(child, idBitsToAssign);
                                        alreadyDispatchedToNewTouchTarget = true;
                                        break;
                                    }
    
                                    ev.setTargetAccessibilityFocus(false);
                                } else {
                                    ev.setTargetAccessibilityFocus(false);
                                }
                            }
    
                            if (preorderedList != null) {
                                preorderedList.clear();
                            }
                        }
                      // 如果newTouchTarget为null,并且mFirstTouchTarget不为null;
                      // 则设置newTouchTarget为mFirstTouchTarget链表中第一个不为空的节点。
                      if (newTouchTarget == null && this.mFirstTouchTarget != null) {
                          for(newTouchTarget = this.mFirstTouchTarget; newTouchTarget.next != null; newTouchTarget = newTouchTarget.next) {
                              ;
                          }

                          newTouchTarget.pointerIdBits |= idBitsToAssign;
                      }
                  }
              }

              // 第6步:进一步的对触摸事件进行分发
              // 
              // (01) 如果mFirstTouchTarget为null,意味着还没有任何View来接受该触摸事件;
              //   此时,将当前ViewGroup看作一个View;
              //   将会调用"当前的ViewGroup的父类View的dispatchTouchEvent()"对触摸事件进行分发处理。
              //   即,会将触摸事件交给当前ViewGroup的onTouch(), onTouchEvent()进行处理。
              // (02) 如果mFirstTouchTarget不为null,意味着有ViewGroup的子View或子ViewGroup中,
              //   有可以接受触摸事件的。那么,就将触摸事件分发给这些可以接受触摸事件的子View或子ViewGroup。
              if (this.mFirstTouchTarget == null) {

                  // 注意:这里的第3个参数是null
                  handled = this.dispatchTransformedTouchEvent(ev, canceled, (View)null, -1);
              } else {
                  ViewGroup.TouchTarget predecessor = null;
                  ViewGroup.TouchTarget target = this.mFirstTouchTarget;

                  label163:
                  while(true) {
                      while(true) {
                          if (target == null) {
                              break label163;
                          }

                          ViewGroup.TouchTarget next = target.next;
                          if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                              handled = true;
                          } else {
                              boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted;
                              if (this.dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) {
                                  handled = true;
                              }

                              if (cancelChild) {
                                  if (predecessor == null) {
                                      this.mFirstTouchTarget = next;
                                  } else {
                                      predecessor.next = next;
                                  }

                                  target.recycle();
                                  target = next;
                                  continue;
                              }
                          }

                          predecessor = target;
                          target = next;
                      }
                  }
              }

              // 第7步:再次检查取消标记,并进行相应的处理
              if (!canceled && actionMasked != 1 && actionMasked != 7) {
                  if (split && actionMasked == 6) {
                      int actionIndex = ev.getActionIndex();
                      idBitsToRemove = 1 << ev.getPointerId(actionIndex);
                      this.removePointersFromTouchTargets(idBitsToRemove);
                  }
              } else {
                  this.resetTouchState();
              }
          }

          if (!handled && this.mInputEventConsistencyVerifier != null) {
              this.mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
          }

          return handled;
      }

   转载规则

《AndroidTouch分发机制续篇字》GajAngels 采用 知识共享署名-非商业性使用 4.0 国际许可协议 进行许可。