View事件分发机制

1 常见滑动冲突举例


2 View事件分发机制源码分析

当一个点击操作发生时,事件最先传递给当前Activity,由Activity的dispatchTouchEvent来进行事件派发。

(1)Activity

public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }
onUserInteraction是空实现,没做任何处理。事件交给了Activity所属PhoneWindow的superDispatchTouchEvent进行分发。

再来看看superDispatchTouchEvent

(2)PhoneWindow

@Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }
其中mDecor为DecorView,所以事件交给了DecorView.下面代码简要说明了DecorView实例化的过程

@Override
    public final View getDecorView() {
        if (mDecor == null) {
            installDecor();
        }
        return mDecor;
    }
private void installDecor() {
        if (mDecor == null) {
            mDecor = generateDecor();
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
               ......
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);
            mTitleView = (TextView)findViewById(com.android.internal.R.id.title);
            if (mTitleView != null) {
                mTitleView.setLayoutDirection(mDecor.getLayoutDirection());
                ......
                mTitleView.setText(mTitle);
protected DecorView generateDecor() {
        return new DecorView(getContext(), -1);
    }{
protected ViewGroup generateLayout(DecorView decor){
	......
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        ......
        return contentParent;
    }
我们知道,在Activity中通过getWindow().getDecorView()就可获得Activity当中的DecorView ,这里提前看看DecorView在View体系的地位。



(3)DecorView(PhoneWindow内部类)

private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {......}
在这里事件已经到了Root view——DecorView,DecorView继承自FrameLayout 。
public boolean superDispatchTouchEvent(MotionEvent event) {
            return super.dispatchTouchEvent(event);
        }
这里super.dispatchTouchEvent调用到了ViewGroup中的dispatchTouchEvent


(4)ViewGroup

这里重点分析dispatchTouchEvent方法

@Override
    public boolean dispatchTouchEvent(MotionEvent ev){
       ......
      //如果是ACTION_DOWN,就重置FLAG_DISALLOW_INTERCEPT,让ViewGroup一定能执行onInterceptTouchEvent
       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.
                cancelAndClearTouchTargets(ev);
			//重置FLAG_DISALLOW_INTERCEPT
                resetTouchState();
            }

	//当ViewGroup子元素成功处理后,mFirstTouchTarget会被赋值并指向处理事件的子元素(即mFirstTouchTarget!=null)
        // 当事件为ACTION_DOWN时,if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null)肯定为true,一定会执行
        //但是当事件为ACTION_MOVE或者ACTION_UP时,如果子元素没有处理ACTION_DOWN事件,则mFirstTouchTarget==null,
        //if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null)肯定为false
       //则ViewGroup的onInterceptTouchEvent将不会执行,同一序列的其他事件就默认交给他处理了
       //FLAG_DISALLOW_INTERCEPT设置后ViewGroup无法拦截除ACTION_DOWN以外的事情
       //ACTION_DOWN因为上面已经重置FLAG_DISALLOW_INTERCEPT,所以ACTION_DOWN事件时,一定会被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 {
      // There are no touch targets and this action is not an initial down
      // so this view group continues to intercept touches.
      intercepted = true;
     }   

由上可知:ACTION_DOWN事件时,一定会被ViewGroup的onInterceptTouchEvent执行

如果ViewGroup不拦截事件,事件会下发分配给它的子View进行处理

 final View[] children = mChildren;
                        final boolean customOrder = isChildrenDrawingOrderEnabled();
                        for (int i = childrenCount - 1; i >= 0; i--) {
                            final int childIndex = customOrder ?getChildDrawingOrder(childrenCount, i) : i;
                            final View child = children[childIndex];
                            if (!canViewReceivePointerEvents(child)
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                continue;
                            }

                            newTouchTarget = getTouchTarget(child);
                            if (newTouchTarget != null) {
                                // Child is already receiving touch within its bounds.
                                // Give it the new pointer in addition to the ones it is handling.
                                newTouchTarget.pointerIdBits |= idBitsToAssign;
                                break;
                            }

                            resetCancelNextUpFlag(child);
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                // Child wants to receive touch within its bounds.
                                ......
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                               break;
                            }
                        }



首先遍历ViewGroup的所有子元素,判断子元素是否能接收到点击事件。其中,dispatchTransformedTouchEvent方法中里面有这样几行代码

if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                handled = child.dispatchTouchEvent(event);
            }
return handled;


如果handled 返回true,则dispatchTransformedTouchEvent返回true,mFirstTouchTarget就会被赋值并终止对子元素的继续遍历,如果子元素dispatchTransformedTouchEvent返回false,ViewGroup就会把事件分发给下一个子元素,如果下一个子元素不存在,就往上传。


(5)View

这里再来看看View中的dispatchTouchEvent

 public boolean dispatchTouchEvent(MotionEvent event) {
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(event, 0);
        }

        if (onFilterTouchEventForSecurity(event)) {
            //noinspection SimplifiableIfStatement
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                return true;
            }

            if (onTouchEvent(event)) {
                return true;
            }
        }

        .......
        return false;
    }


这里首先判断mOnTouchListener ,如果mOnTouchListener.onTouch返回True,则onTouchEvent不会被调用,所以OnTouchListener优先级高于onTouchEvent.

再来看看onTouchEvent

View在DISABLED状态下,也可以消耗点击事件,只要View的CLICKABLE和LONG_CLICKABLE有一个为true,它就消耗这个事件,即

onTouche返回true,不管view是不是DISABLE状态。

如果View设置了OnclickListener,那么performClick方法会调用他的onClick方法。

   public boolean performClick() {
        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);

        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            li.mOnClickListener.onClick(this);
            return true;
        }

        return false;
    }


View的LONG_CLICKABLE默认为false,其中Button的CLICKABLE默认为true,TextView的CLICKABLE默认为false。另外setOnClickListener会自动将View的CLICKABLE

设为true,setOnLongClickListener会自动将View的LONG_CLICKABLE设为true

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

public void setOnLongClickListener(OnLongClickListener l) {
        if (!isLongClickable()) {
            setLongClickable(true);
        }
        getListenerInfo().mOnLongClickListener = l;
    }

3 事件分发机制总结

主要涉及到dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent三个方法

他们的关系如下

public boolean dispatchTouchEvent(MotionEvent ev){

boolean consume=false;

       if(onInterceptTouchEvent(ev)){

       consume=onTouchEvent(ev);

       }else{

consume=child.dispatchTouchEvent(ev);

       }

      return consume;

}

1.对于一个根ViewGroup,点击事件产生后,先产生ACTION_DOWN事件,然后ACTION_DOWN首先传递给ViewGroup

ViewGroup的dispatchTouchEvent方法返回true表示它要拦截当前事件,接着它的onTouchEvent会被调用;如果ViewGroup

的onInterceptTouchEvent方法返回false就表示它不拦截当前事件,事件就继续传递给它的子元素,接着子元素的dispatchTouchEvent

就会被调用(子元素可能是ViewGroup或者View,他们的dispatchTouchEvent复写方法不同),如果子元素消耗事件,则退出遍历。

如果子元素不消耗,则继续遍历其他子元素,直到遍历结束,如果还没有子元素消耗,子元素的父容器onTouchEvent就会被调用。

即 Activity——>Window——>DocorView——>view(子元素可能很多,且子元素也可能有ViewGroup,遍历访问)——>ViewGroup——>Activity


2.另外,如果一个View设置了onTouchListener,那么OnTouchListener的onTouch方法会被调用,如果返回true,onTouchEvent将不会被调用,

所以OnTouchListener优先级比onTouch高。如果设置了onClickListener,那么他的onClick会在onTouchEvent中调用,所以onClickListener

优先级最低。


3 事件序列以down开始,up结束,中间会包含很多move事件


4 一旦一个view拦截了事件(拦截了ACTION_DOWN),那么这个事件序列其他事件都交给他处理


5一个View,如果不消耗ACTION_DOWN事件,则同一时间的其他事件也不会交给他处理


6.






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值