android触摸event传递源码分析二

来源:互联网 发布:练英语听力什么软件 编辑:程序博客网 时间:2024/06/07 00:59

上一篇文章分析了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老鸟
这里写图片描述

0 0