android 的事件分发机制

  说到事件分发,我们都应该有一些了解,再开发的多多少少都会碰到一些事件冲突(比如滑动冲突),利用事件分发机制就可以解决,对于事件分发机制我虽然看过很多文章,但自己不做总结,总有点一知半解所以有了这片文章。好了说正文。

  当一个点击操作发生时,事件分发是从Activity 的dispatchTouchEvent方法开始的,看源码可知实现在Window的  superDispatchTouchEvent方法中,是一个抽象方法,我们需要找到Window的实现类。

public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();
    }
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    return onTouchEvent(ev);
}

Window的实现类是什么呢?就是PhoneWindow,它是如何处理点击事件呢

public boolean superDispatchTouchEvent(MotionEvent event){
  return mDecor.superDispatchTouchEvent(event);
}

可以清楚的看出将处理传给了DecorView,这个View也就是我们常说的根View,它是一个ViewGroup,我们Activity中setContentView其实就是将我们的布局做根View的子View。

根据上面的分析,事件分发通过Activity分发后,然后会流向顶级View(一般是ViewGroup),在通过ViewGroup的dispatchTouchEvent方法分发给我们的布局view。在介绍过程之前,先说三个重要的方法

//事件分发器,
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    return super.dispatchTouchEvent(ev);
}

//事件拦截器,只存在于ViewGroup
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    return super.onInterceptTouchEvent(ev);
}

//事件处理器
@Override
public boolean onTouchEvent(MotionEvent event) {
    return super.onTouchEvent(event);
}

到了根View后事件分发流程分为两种,一种是ViewGroup,一种是View。

ViewGroup中的事件分发,先来看看dispatchTouchEvent方法中的代码  

boolean handled = false;
//过滤掉窗口被遮挡时的事件
if (onFilterTouchEventForSecurity(ev)) {
    final int action = ev.getAction();
    final int actionMasked = action & MotionEvent.ACTION_MASK;

    // Handle an initial down.
    if (actionMasked == MotionEvent.ACTION_DOWN) {
        // Throw away all previous state when starting a new touch gesture.
        // The framework may have dropped the up or cancel event for the previous gesture
        // due to an app switch, ANR, or some other state change.

        //清除TouchTarget链表数据,重置一些状态        
        cancelAndClearTouchTargets(ev);
        resetTouchState();
    }

    // Check for interception.   intercepted为false代表viewGroup不拦截,true代表viewGruop拦截
    final boolean intercepted;
    //为down事件时进入判断,或者mFirstTouchTarget被赋值了,也就是子类消费了事件     
    if (actionMasked == MotionEvent.ACTION_DOWN
            || mFirstTouchTarget != null) {
        //disallowIntercept为true代表viewGroup禁止拦截,为false代表viewGroup可以拦截事件
        //mGroupFlags  子类通过调用requestDisallowInterceptTouchEvent方法可以改变其值,但在down事件时会被重置        final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
        if (!disallowIntercept) {
            //当没有禁止拦截时,进入到viewGroup的onInterceptTouchEvent拦截方法中
            intercepted = onInterceptTouchEvent(ev);
            ev.setAction(action); // restore action in case it was changed
        } 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 move up事件,当为down事件时,会做清除TouchTarget链表数据和重置状态操作,这一步操作也就是我们不能在子View中禁止父view拦截down事件的原因,接着往下看到了mFirstTouchTarget ,它是TouchTarget链表的头节点,当有子类消费了事件时,它就会被赋值,接着进入到判断,disallowIntercept表示的是禁止拦截,子类之所以可以禁止父View拦截事件,调用requestDisallowInterceptTouchEvent方法改变了mGroupFlags值,所以在move、up事件可以禁止父类拦截事件,disallowIntercept为false时,进入到ViewGroup的onInterceptTouchEvent拦截器,disallowIntercept为true,intercepted为false,表示表示ViewGroup不拦截事件,intercepted为true表示ViewGroup拦截事件。

TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
//判断事件是否取消,或者是viewGroup否拦截,不拦截不取消,则进入viewGroup将事件分发给子View的流程
if (!canceled && !intercepted) {

    // If the event is targeting accessibility focus we give it to the
    // view that has accessibility focus and if it does not handle it
    // we clear the flag and dispatch the event to all children as usual.
    // We are looking up the accessibility focused host to avoid keeping
    // state since these events are very rare.
    View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
            ? findChildWithAccessibilityFocus() : null;
 .....................
}
 .....................
//如果事件被viewGroup拦截,或者没有子View消费,则事件又回到ViewGroup,进入到view的事件分发流程
if (mFirstTouchTarget == null) {
    // No touch targets so treat this as an ordinary view.
    handled = dispatchTransformedTouchEvent(ev, canceled, null,
            TouchTarget.ALL_POINTER_IDS);
}

当intercepted拦截了down事件时,intercepted为true,事件就由dispatchTransformedTouchEvent流向ViewGroup的onTouchEvent方法中,如果onTouchEvent不做处理最终流向Activity,也就是View的事件分发流程,下文会介绍,我们先来分析intercepted为false,不拦截事件时,做了一些什么操作。

final ArrayList<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
        && isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
//开始遍历子view,寻找是哪个view消费了事件
for (int i = childrenCount - 1; i >= 0; i--) {
    final int childIndex = getAndVerifyPreorderedIndex(
            childrenCount, i, customOrder);
    final View child = getAndVerifyPreorderedView(
            preorderedList, children, childIndex);

....................
  //判断view是否可见或者有动画在执行
  if (!canViewReceivePointerEvents(child)
          //判断事件是否落入该view范围
        || !isTransformedTouchPointInView(x, y, child, null)) {
      ev.setTargetAccessibilityFocus(false);
      continue;
  }
...................
  //将事件分发给该子view,进入到子view的事件分发流程,
  if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
      // Child wants to receive touch within its bounds.
        mLastTouchDownTime = ev.getDownTime();
       if (preorderedList != null) {
        // childIndex points into presorted list, find original 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();
      //如果子view消费了事件,则在addTouchTarget 为mFirstTouchTarget赋值,然后跳出循环
      newTouchTarget = addTouchTarget(child, idBitsToAssign);
      alreadyDispatchedToNewTouchTarget = true;
      break;
   }

}

只截取了部分利于分析的代码,开始可以分明显的看出去遍历每个子View,然后就是去判断子View是否有动画或者可见 && 点击事件是否落在子View范围内,如果不满足条件就进行下一次循环,满足条件则会执行dispatchTransformedTouchEvent将事件分发给该子View,当该子View消费了事件,addTouchTarget方法就会被执行,这个方法里面会给mFirstTouchTarget赋值,前面已经介绍了,当该子View不消费事件时,最终事件又会流向ViewGroup的onTouchEvent方法。

View的事件分发流程,同样先来看dispatchTouchEvent方法中的代码  

扫描二维码关注公众号,回复: 9245184 查看本文章
if (onFilterTouchEventForSecurity(event)) {
    if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
        result = true;
    }
    //noinspection SimplifiableIfStatement
    ListenerInfo li = mListenerInfo;
   // 这里判断该view有没有使用OnTouchListener监听,有没有在回调方法onTouch中消费事件,并且该view是不是可以点击
   // 如果上述条件符合,返回true,表示消费了事件,可明显看出onTouch比onTouchEvent优先级高
    if (li != null && li.mOnTouchListener != null
            && (mViewFlags & ENABLED_MASK) == ENABLED
            && li.mOnTouchListener.onTouch(this, event)) {
        result = true;
    }
    //如果前面没有消费,则进入到该view的onTouchEvent方法
    if (!result && onTouchEvent(event)) {
        result = true;
    }
}

if (!result && mInputEventConsistencyVerifier != null) {
    mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}

// Clean up after nested scrolls if this is the end of a gesture;
// also cancel it if we tried an ACTION_DOWN but we didn't want the rest
// of the gesture.
if (actionMasked == MotionEvent.ACTION_UP ||
        actionMasked == MotionEvent.ACTION_CANCEL ||
        (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
    stopNestedScroll();
}

注释已经很清楚了,我就不重复描述了,接着看onTouchEvent方法中的代码

final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
        || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
        || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
  ............................... 
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
    switch (action) {
        case MotionEvent.ACTION_UP:
           ...............................                         
          if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
             // This is a tap, so remove the longpress check
              removeLongPressCallback();
              // Only perform take click actions if we were in the pressed state
              if (!focusTaken) {
              // Use a Runnable and post this rather than calling
              // performClick directly. This lets other visual state
              // of the view update before click actions start.
              if (mPerformClick == null) {
             mPerformClick = new PerformClick();
         }
         if (!post(mPerformClick)) {
            performClickInternal();
         }
      ............................
         break;
    }
  return true;
}

上述代码表明只要View clickable或者long_clickable有一个属性为true, onTouchEvent就会消耗事件,当为up事件时,会去执行performClick()方法,在这个方法中回去调用我们经常用的监听事件onClick。至于onLongClick事件在down事件时就会去发送延时意图,等待调用,当onClick执行前就会去移除长按回调方法。

事件分发介绍完了,如有问题,欢迎指正。

发布了12 篇原创文章 · 获赞 6 · 访问量 930

猜你喜欢

转载自blog.csdn.net/qq_30867605/article/details/87864306