七、事件分发

1.事件分发的总结

  • 事件分发顺序:Activity(Window) -> ViewGroup -> View
  • 事件分发过程由dispatchTouchEvent()、onInterceptTouchEvent()和onTouchEvent()三个方法协助完成。
    三个方法之间的关系,伪代码如下:
    //点击事件产生后,会直接调用dispatchTouchEvent()方法
    public  boolean dispatchTouchEvent(MotionEvent event){
        //代表是否消耗事件
        boolean consume = false;
        if (onInterceptTouchEvent(event)){
            //如果onInterceptTouchEvent()返回为true,则代表当前View拦截的点击事件
            //事件会被当前View进行处理
            //即调用onTouchEvent()方法去处理点击事件
            consume = onTouchEvent(event);
        }else{
            //不拦截,交由子元素处理,重复上述过程
            //直到点击事件被处理为止。
            consume = child.dispatchTouchEvent(event);
        }
        return consume;
    }

2.事件分发的源码分析

事件分发顺序:Activity(Window) -> ViewGroup -> View

1. Activity中的事件分发

当一个点击事件发生时,事件最先传递到Activity中的dispatchTouchEvent()进行事件分发

具体是由Activity的window来完成。

Activity中的dispatchTouchEvent源码:

  public boolean dispatchTouchEvent(MotionEvent ev) {
        //一般事件最开始都是down,所以这里返回true
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
          //注意1
            onUserInteraction();
        }
        //注意2
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }
  • 注意1:onUserInteraction(),方法为空方法,当该Activity处于栈顶的时候,触屏点击home,back,menu键等都会触发此方法。所以onUserInteraction()方法主要用于屏保
  • 注意2:Window类是抽象类,且PhoneWindow是其唯一实现类。
    superDispatchTouchEvent()是也是抽象方法。
    PhoneWindow类中的superDispatchTouchEvent()方法:
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
    return mDecor.superDispatchTouchEvent(event);
    //mDecor是DecorView的实例
    //DecorView是视图的顶层view, 继承自FrameLayout, 是所有界面的父类
}

mDecor.superDispatchTouchEvent(event)方法:

public boolean superDispatchTouchEvent(MotionEvent event) {
    return super.dispatchTouchEvent(event);
    //DecorView继承自FrameLayout
    //那么它的父类就是ViewGroup
    //而super.dispatchTouchEvent(event)方法, 其实就应该是      
    //ViewGroup的dispatchTouchEvent()
}

综上:执行getWindow().superDispatchTouchEvent(ev)实际上是执行了ViewGroup.superDispatchTouchEvent(ev)。
activity中的superDispatchTouchEvent实际上就是在执行ViewGroup.superDispatchTouchEvent()方法。这样,事件就从Activity传递到了ViewGroup。
总结:
当一个点击事件发生时,调用顺序如下,

  1. 事件最先传递到Activity的dispatchTouchEvent进行分发。
  2. 调用Window的实现类PhoneWindow的superDisPatchTouchEvent(),
  3. 调用DecorView的superDispatchTouchEvent()。即父类ViewGroup的dispatchTouchEvent()方法。
2. ViewGroup中的事件分发

ViewGroup的dispatchTouchEvent()方法:

...
          // Check for interception.主要是拦截器的处理
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
        //是否拦截
//可以在子View通过调用
//requestDisallowInterceptTouchEvent方法对这个值进行修改, 不让该View拦截事件
                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 {
                // 当没有触摸targets, 且不是down事件时, 开始持续拦截触摸。
                intercepted = true;
            }
...  

该代码主要是判断是否拦截。
a.如果当前事件的motionEvent.action_down,进入判断,
b.如果mFirstTouchTarget != null,即已经发生过motionEvent.action_down,并且该事件已经有ViewGroup的子view来处理了,那么也进行判断。调用viewgroup.onInterceptTouchEvent(),判断是否拦截。如果不是以上两种情况,即已经是move或者up事件,并且之前的事件没有对象处理,则设置为true,开始拦截接下来所有的事件。这也就解释了如果view的onTuchEvent()方法返回false,那么接下来的的一系列事件都不会交由它处理。如果ViewGroup的onInterceptTouchEvent(),第一次执行为true,则mFirstTouchTarget = null,则也会使得接下来不会调用onInterceptTouchEvent(),直接将拦截设置为true。
当viewgroup不拦截事件的时候,事件会向下分发交由它的子View或者ViewGroup进行处理。

/**从底层的父视图开始遍历,寻找newTouchTarget,即上面的mFirstTouchTarget ,如果已经存在寻找的newTouchTarget,说明正在接收触摸事件,则跳出循环。
*/
...
                    for (int i = childrenCount - 1; i >= 0; i--) {
                            final int childIndex = getAndVerifyPreorderedIndex(
                                    childrenCount, i, customOrder);
                            final View child = getAndVerifyPreorderedView(
                                    preorderedList, children, childIndex);
//如果当前视图无法获取焦点,则跳过本次循环
                            if (childWithAccessibilityFocus != null) {
                                if (childWithAccessibilityFocus != child) {
                                    continue;
                                }
                                childWithAccessibilityFocus = null;
                                i = childrenCount - 1;
                            }
//如果view不可见或者触摸的焦点不在view的范围内,跳过本次循环
                            if (!child.canReceivePointerEvents()
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                ev.setTargetAccessibilityFocus(false);
                                continue;
                            }
                            newTouchTarget = getTouchTarget(child);
//视图已经接收触摸事件,退出整个循环
                            if (newTouchTarget != null) {
                                newTouchTarget.pointerIdBits |= idBitsToAssign;
                                break;
                            }
//重置取消或抬起标志位
//如果触摸位置在child的区域内,则把事件分发给view或者ViewGroup
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// 获取TouchDown的时间点
                                mLastTouchDownTime = ev.getDownTime();
                                if (preorderedList != null) {
                                    //获取touchDown的index
                                    for (int j = 0; j < childrenCount; j++) {
                                        if (children[childIndex] == mChildren[j]) {
                                            mLastTouchDownIndex = j;
                                            break;
                                        }
                                    }
                                } else {
                                    mLastTouchDownIndex = childIndex;
                                }
                                mLastTouchDownX = ev.getX();
                                mLastTouchDownY = ev.getY();
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                alreadyDispatchedToNewTouchTarget = true;
                                break;
                            }
  • Android事件分发是先传递到ViewGroup,再由ViewGroup传递到view
  • 在ViewGroup中通过onInterceptTouchEvent()

1.onInterceptTouchEvent方法返回true代表拦截事件,即不允许事件继续向子View传递;
2.返回false代表不拦截事件,即允许事件继续向子View传递;(默认返回false)
3.子View中如果将传递的事件消费掉,ViewGroup中将无法接收任何事件。


3. View中的事件分发

View.dispatchTouchEvent源码分析:

//过滤触摸事件以应用安全策略。
     if (onFilterTouchEventForSecurity(event)) {
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }

            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }
  1. 条件 li != null ,直接实例化,不为空
  2. 条件 li.mOnTouchListener != null

//mOnTouchListener是在View类下setOnTouchListener方法里赋值的
public void setOnTouchListener(OnTouchListener l) {
// getListenerInfo() 返回的是静态类ListenerInfo
//即只要我们给控件注册了Touch事件, mOnTouchListener就一定
//被赋值( 不为空)
getListenerInfo().mOnTouchListener = l;
}

  1. 条件 (mViewFlags & ENABLED_MASK) == ENABLED
  • 该条件是判断当前点击的控件是否enable
  • 由于很多view默认是enable的,因此该条件恒定为true。
  1. 条件 li.mOnTouchListener.onTouch(this, event)
  • 回调控件注册touch事件时的onTouch方法
          view.setOnTouchListener(new View.OnTouchListener() {
              @Override
              public boolean onTouch(View v, MotionEvent event) {
                  return false;
              }
          });
    
  • 如果在onTouch方法返回true,就会让条件成立,整个方法返回为true
  • 如果在onTouch方法返回false,就会继续后面的判断,执行onTouchEvent(event)方法。
    onTouchEvent(event):
public boolean onTouchEvent(MotionEvent event) {
    ...
       if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
           switch (action) {
               case MotionEvent.ACTION_UP:
                   mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                   if ((viewFlags & TOOLTIP) == TOOLTIP) {
                       handleTooltipUp();
                   }
              
                   boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0
                       if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                           // This is a tap, so remove the longpress check
                           removeLongPressCallback();
                           if (!focusTaken) {
                       // 注意: 经历了种种判断进入performClickInternal()方法
                               if (mPerformClick == null) {
                                   mPerformClick = new PerformClick();
                               }
                               if (!post(mPerformClick)) {
                                   performClickInternal();
                               }
                           }
                       }

                       if (mUnsetPressedState == null) {
                           mUnsetPressedState = new UnsetPressedState();
                       }

                       if (prepressed) {
                           postDelayed(mUnsetPressedState,
                                   ViewConfiguration.getPressedStateDuration());
                       } else if (!post(mUnsetPressedState)) {
                           // If the post failed, unpress right now
                           mUnsetPressedState.run();
                       }

                       removeTapCallback();
                   }
                   mIgnoreNextUpEvent = false;
                   break;

注意:当手指抬起的时候触发点击事件,经过一系列的判断,调用 performClickInternal(),该方法里面又调用了performClick().

 public boolean performClick() {
      // We still need to call this method to handle the cases where performClick() was called
      // externally, instead of through performClickInternal()
      notifyAutofillManagerOnClick();
      final boolean result;
      final ListenerInfo li = mListenerInfo;
      if (li != null && li.mOnClickListener != null) {
          playSoundEffect(SoundEffectConstants.CLICK);
          li.mOnClickListener.onClick(this);
          result = true;
      } else {
          result = false;
      }
     sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
      notifyEnterOrExitForAutoFillIfNeeded(true);
      return result;
  }
  • 只要li.mOnClickListener 不为空,就会调用onclick方法。

     public void setOnClickListener(@Nullable OnClickListener l) {
             if (!isClickable()) {
              setClickable(true);
          }
          getListenerInfo().mOnClickListener = l;
      }
    

    mOnClickListener 是在View类下setOnClickListener方法里赋值的,而我们设置按钮点击的事件的时候,会调用该方法,自动赋值,最后回调onClick()方法。

结论:

  1. onTouch() 的执行高于onClick()。

  2. 每当控件被点击的时候:

    • 如果在回调onTouch()里面返回false,就会让dispatchTouchEvent()返回false,那么就会执行onTouchEvent()方法;如果回调了setOnclickListener()来给控件注册点击事件的话,最后会在通过performClickInternal(),调用performCik()方法里面调用onClick()方法。

    onTouch()返回false(该事件没有被onTouch()消费掉) = 执行onTouchEvent() = 执行Onclick()。

    • 如果在回调onTouch()里返回为true,就会让dispatchTouchEvent方法返回true,那么将不会执行onTouchEvent(),即onClick()也不会执行。

    onTouch()返回true(该事件被onTouch()消费掉) =dispatchTouchEvent()返回true(不会再继续向下传递) = 不会执行onTouchEvent() = 不会执行onClick().

你可能感兴趣的:(七、事件分发)