android 事件分发机制

Android 事件分发机制解析

1. view的事件分发机制

view的事件分发是从 dispatchTouchEvent() 开始的,直接上代码;

public boolean dispatchTouchEvent(MotionEvent event) {  
  boolean result = false;
     // 1. view 是否可以点击 && setOnTouchListener 有值 并且 setOnTouchListener 返回值是true 事件分发  结束
 
        if ( (mViewFlags & ENABLED_MASK) == ENABLED && 
              mOnTouchListener != null &&  
              mOnTouchListener.onTouch(this, event)) {  
            boolean result = true;

        } 
         // 2.如果上述条件不都成立 执行 OnTouchEvent();
        if (!result && onTouchEvent(event)) {
            result = true;
        }
            
      
        return result;
 }

/**
  * 分析1:onTouchEvent()
  */
public boolean onTouchEvent(MotionEvent event) {  



    // 若该控件可点击,则进入switch判断中
    if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {  

        // 根据当前事件类型进行判断处理
        switch (event.getAction()) { 

            // a. 事件类型=抬起View(主要分析)
            case MotionEvent.ACTION_UP:  
                    performClick(); 
                    // ->>分析2
                    break;  

            // b. 事件类型=按下View
            case MotionEvent.ACTION_DOWN:  
                postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());  
                break;  

            // c. 事件类型=结束事件
            case MotionEvent.ACTION_CANCEL:  
                refreshDrawableState();  
                removeTapCallback();  
                break;

            // d. 事件类型=滑动View
            case MotionEvent.ACTION_MOVE:  
                final int x = (int) event.getX();  
                final int y = (int) event.getY();  

                int slop = mTouchSlop;  
                if ((x < 0 - slop) || (x >= getWidth() + slop) ||  
                        (y < 0 - slop) || (y >= getHeight() + slop)) {  
                    removeTapCallback();  
                    if ((mPrivateFlags & PRESSED) != 0) {  
                        removeLongPressCallback();  
                        mPrivateFlags &= ~PRESSED;  
                        refreshDrawableState();  
                    }  
                }  
                break;  
        }  

        // 若该控件可点击,就一定返回true
        return true;  
    }  
  // 若该控件不可点击,就一定返回false
  return false;  
}

**
  * 分析2:performClick()
  */  
  public boolean performClick() {  

      if (mOnClickListener != null) {
          // 只要通过setOnClickListener()为控件View注册1个点击事件
          // 那么就会给mOnClickListener变量赋值(即不为空)
          // 则会往下回调onClick() & performClick()返回true
          playSoundEffect(SoundEffectConstants.CLICK);  
          mOnClickListener.onClick(this);  
          return true;  
      }  
      return false;  
  }  
复制代码

总结:如果你在执行ACTION_DOWN的时候返回了false,后面一系列其它的action就不会再得到执行了。简单的说,就是当dispatchTouchEvent在进行事件分发的时候,只有前一个action返回true,才会触发后一个action。

image.png

2.ViewGroup 事件分发机制

viewGroup事件分发可以分为4个阶段:1.点击事件是down,将mFirstTarget、其他的标记、状态值清空,2.检查当前事件是否被拦截,3.如果被拦截,当前的事件不会分发给子view(firstTarget为空),会交由viewGroup父类的dispatchTouchEvent处理;4.如果不拦截,会找到一个满足条件的子view,分发此次的down事件;5.如果找不到满足条件的子view,firstTouch=null,就会调用自身的dispatchTouchEvent;6.如何当前点击事件是move、up时;6.如果找到了符合条件的子view,把down事件分发给子view,并对firstTouchTarget赋值,down事件分发结束;7.接来下就是move、up事件的分发,如果down事件分发给子view了,会再次判断是否拦截;8.如果不拦截,就会把move、up分发给mFirstTouchTarget对应的子view;9.如果拦截,会分发一个cancel事件给firstTouchTarget对应的子view。

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {

    // 1.将firstTouchTarget置空,其他状态清空 
    if (actionMasked == MotionEvent.ACTION_DOWN) {
        cancelAndClearTouchTargets(ev);
        resetTouchState();
    }

    final boolean intercepted;
    
    
   /***
     * 2.检查是否拦截事件,如果点击事件是down、或者事件已经分发给子view,通过viewGroup的 
     * onInterceptTouchEvent 判断
     *
     */ 
  
    if (actionMasked == MotionEvent.ACTION_DOWN
            || mFirstTouchTarget != null) {
        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 {
        intercepted = true;
    }


    TouchTarget newTouchTarget = null;
    boolean alreadyDispatchedToNewTouchTarget = false;
    //  3. 如果事件没有被拦截,会需找一个满足条件的子view分发事件 
    if (!canceled && !intercepted) {
        if (actionMasked == MotionEvent.ACTION_DOWN
                || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                
                
        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();
                /***
                 *4. 如果有子view可以分发当前的事件,对newTouchTarget,firstTouchTarget赋值,记
                 * 消费本次事件的view
                 */
                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                alreadyDispatchedToNewTouchTarget = true;
                break;
            }
        }
    }
    
    if (mFirstTouchTarget == null) {
        // 5. 事件交由viewGroup父类的dispatchTouchEvent 处理 
        // No touch targets so treat this as an ordinary view.
        handled = dispatchTransformedTouchEvent(ev, canceled, null,
                TouchTarget.ALL_POINTER_IDS);
    } else {
        // Dispatch to touch targets, excluding the new touch target if we already
        // dispatched to it.  Cancel touch targets if necessary.
        TouchTarget predecessor = null;
        TouchTarget target = mFirstTouchTarget;
        while (target != null) {
            final TouchTarget next = target.next;
            // 6.找到符合条件的子view,该事件分发结束
            if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                handled = true;
            } else {
                final boolean cancelChild = resetCancelNextUpFlag(target.child)
                        || intercepted;
             //          
                if (dispatchTransformedTouchEvent(ev, cancelChild,
                        target.child, target.pointerIdBits)) {
                    handled = true;
                }
                
                if (cancelChild) {  // 如果拦截了事件,清空 firstTouchTarget 
                    if (predecessor == null) {
                        mFirstTouchTarget = next;
                    } else {
                        predecessor.next = next;
                    }
                    target.recycle();
                    target = next;
                    continue;
                }
            }
            predecessor = target;
            target = next;
        }
    }

}
复制代码
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
        View child, int desiredPointerIdBits) {
    final boolean handled;

   
    final int oldAction = event.getAction();
    //  如果是子view消费了viewGroup分发的事件,后续事件被viewGroup拦截,viewGroup会发送一
       cancel事件给firstTouchTarget对应的子view,该事件结束。下一个事件就不会再分发给子view了。

    if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {   
        event.setAction(MotionEvent.ACTION_CANCEL);
        if (child == null) {
            handled = super.dispatchTouchEvent(event);
        } else {
            handled = child.dispatchTouchEvent(event);
        }
        event.setAction(oldAction);
        return handled;
    }

 
    final int oldPointerIdBits = event.getPointerIdBits();
    final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;

  
    if (newPointerIdBits == 0) {
        return false;
    }

    if (child == null) {
        handled = super.dispatchTouchEvent(transformedEvent);
    } else {
        final float offsetX = mScrollX - child.mLeft;
        final float offsetY = mScrollY - child.mTop;
        transformedEvent.offsetLocation(offsetX, offsetY);
        if (! child.hasIdentityMatrix()) {
            transformedEvent.transform(child.getInverseMatrix());
        }

        handled = child.dispatchTouchEvent(transformedEvent);
    }
    transformedEvent.recycle();
    return handled;
}
复制代码

3.滑动冲突

3.1 滑动冲突场景

方向一致:父容器和子view的滑动方向一致,如:scrollView 嵌套一个recyclewView
方向不一致:父容器和子view的滑动方向不一致,如scrollView 嵌套一个 viewPage。

3.2 外部拦截法

子view需要处理事件时,在父容器里面通过onInterceptTouchEvent返回值为false,让事件交由子view处理;当父容器需要处理事件时,让onInterceptTouchEvent返回值未true,让父容器拦截子view的事件,自己处理事件。伪代码如下:

public boolean onInterceptTouchEvent(MotionEvent event) {
    boolean intercepted=false;
    int x= (int) event.getX();
    int y= (int) event.getY();
    switch (event.getAction()){
        case MotionEvent.ACTION_DOWN:
        intercepted=false;//必须不能拦截,否则后续的ACTION_MOME和ACTION_UP事件都会拦截。
    break;
    case MotionEvent.ACTION_MOVE:
    if (父容器需要当前点击事件){
        intercepted=true;
     }else {
      intercepted=false;
     }  
    break;
    case MotionEvent.ACTION_UP:
        intercepted=false;
    break;
    
    default:
    break;
   }
  mLastXIntercept=x;
  mLastXIntercept=y;
  return intercepted;  
}
复制代码

3.3 内部拦截法

if (!disallowIntercept) {
    intercepted = onInterceptTouchEvent(ev);
    ev.setAction(action); // restore action in case it was changed
} else {
    intercepted = false;
}
复制代码

内部拦截法通过子view(requestDisallowInterceptTouchEvent(disallowIntercept=false),disallowIntercept=false)改变viewGroup的disallowIntercept值,来干预viewGroup是否拦截子view。从上面代码,我们可以知道:disallowIntercept只能控制让viewGroup不拦截子view,拦截子view是通过viewGroup的 onInterceptTouchEvent方法值控制的。所以内部拦截法,就是结合viewGroup的 onInterceptTouchEvent方法和view通过viewgroup.requestDisallowInterceptTouchEvent改变 disallowIntercept值共同来完成。

// 重写 viewGroup  onInterceptTouchEvent方法,down返回值不能为false
@Override
 public boolean onInterceptTouchEvent(MotionEvent ev) {
        int action = ev.getAction();
        if (action == MotionEvent.ACTION_DOWN) {
            return false;
        } else {
            return true;
        }
  }   

复制代码
//  重写子view的 dispatchTouchEvent事件 
@Override
 public boolean dispatchTouchEvent(MotionEvent ev) {
        int x = (int) ev.getX();
        int y = (int) ev.getY();
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN: {
                getParent().requestDisallowInterceptTouchEvent(true);
                break;
            }
            case MotionEvent.ACTION_MOVE: {
                int deltaX = x - mLastXIntercept;
                int deltaY = y - mLastYIntercept;
                //如果是左右滑动
                if (父容器) {
                    getParent().requestDisallowInterceptTouchEvent(false);
                }
                break;
            }
            case MotionEvent.ACTION_UP: {
                getParent().requestDisallowInterceptTouchEvent(false);
                break;
            }
        }
        mLastXIntercept = x;
        mLastYIntercept = y;
        return super.dispatchTouchEvent(ev);
   }   

复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享