事件分发

View与ViewGroup事件分析

区别 diapatchTouchEvent
View:只能进行事件处理,即要么处理事件,要不处理事件。主要实现onDraw方法,对需求的View进行自定义绘制。
ViewGroup:可以进行事件分发,即先进行事件分发,再进行事件处理。主要实现onMeasure方法和onLayout方法,对需求的多View进行测量以及布局。
事件处理分发流程图
ViewGroup分发分析:首先从任意一个Activity的onCreat方法一路向前找到最后的父类Activity类,在该类有dispatchTouchEvent()方法,

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

在这里插入代码片该方法调用处理触摸屏事件,返回true意为消耗了该事件,返回false可以将触摸屏事件分发给window。在安卓中只有一个window即PhoneWindow,由getWindow().superDispatchTouchEvent(ev)可以找到PhoneWindow的superDispatchTouchEvent方法

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

对象mDecor是DecorView,紧接着可以找到DecorView的superDispatchTouchEvent方法

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

因为DecorView继承FrameLayout,而FrameLayout又继承自ViewGroup,所以接着可以追踪到VIewGroup里面的dispatchTouchEvent方法,该方法进行事件分发。

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;
   }
}

在ViewGroup中,首先判断的是MotionEvent.ACTION_DOWN事件是否需要拦截,disallowIntercept 默认一开始是false,所以会执行onInterceptTouchEvent方法来决定是否进行拦截。当intercepted返回true,表示要拦截,会跳过if (!canceled && !intercepted) 的代码块,直接执行下面的代码来判断自己要不要处理事件。

if (mFirstTouchTarget == null) {
   // No touch targets so treat this as an ordinary view.
   handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS);
}

dispatchTransformedTouchEvent方法表示要不要处理,该方法传递第三个参数的子View为null,其方法内有如下一段代码,最终会调用到其父类的View的dispatchTouchEvent方法,将是否处理事件的权力交给其执行。(后续有解释)

if (child == null) {
    handled = super.dispatchTouchEvent(transformedEvent);
}

intercepted返回false,表示不要拦截,要进行分发,会执行if (!canceled && !intercepted)的代码块,该代码块中有会首先判断该事件是否是MotionEvent.ACTION_DOWN 事件,只是MotionEvent.ACTION_DOWN事件才会继续往下分发,紧接着会判断是否其还有子Viewif (newTouchTarget == null && childrenCount != 0) newTouchTarget 初始化默认为null,如果其还有子View,会执行buildTouchDispatchChildList方法,该方法是对所有的子View进行排序(依据xml文件里的属性值translationZ来决定是那个view先接受事件,如果没有设置translationZ值,就按上下顺序,那个View先写,那个先接受事件)

for (int i = 0; i < childrenCount; i++) {
    // add next child (in child order) to end of list
    final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
    final View nextChild = mChildren[childIndex];
    final float currentZ = nextChild.getZ();//对应xml的translationZ属性

    // insert ahead of any Views with greater Z
    int insertIndex = i;//xml的顺序
    while (insertIndex > 0 && mPreSortedChildren.get(insertIndex - 1).getZ() > currentZ) {
        insertIndex--;
    }
    mPreSortedChildren.add(insertIndex, nextChild);
}

经过for循环倒序取出child,判断child是否能够接受事件和事件是否在View的范围内。在View中可找到canReceivePointerEvents方法的实现,child能够接受事件的条件有两个,child必须可见或者不可见但是有动画(平移之后原位置还是可以响应事件)

if (!child.canReceivePointerEvents() || !isTransformedTouchPointInView(x, y, child, null)) {
    ev.setTargetAccessibilityFocus(false);
     continue;
}
protected boolean canReceivePointerEvents() {
    return (mViewFlags & VISIBILITY_MASK) == VISIBLE || getAnimation() != null;
}

如果canReceivePointerEvents和isTransformedTouchPointInView方法都返回true,将会执行下面是否处理事件的核心代码,带有child的dispatchTransformedTouchEvent方法。如果要处理事件,将会执行下面代码块,需要注意几个两句代码newTouchTarget = addTouchTarget(child, idBitsToAssign);alreadyDispatchedToNewTouchTarget = true;执行addTouchTarget方法后newTouchTarget == mFirstTouchTarget != null。跳出循环,依据newTouchTarget, alreadyDispatchedToNewTouchTarget ,target.next,mFirstTouchTarget来继续往下执行之后的逻辑

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;
    }
    LastTouchDownX = ev.getX();
    mLastTouchDownY = ev.getY();
    newTouchTarget = addTouchTarget(child, idBitsToAssign);
    alreadyDispatchedToNewTouchTarget = true;
    break;
}

由于child不等于空,所以会执行dispatchTransformedTouchEvent方法的else模块方法,其中child.dispatchTouchEvent(transformedEvent)会走ViewGroup的dispatchTouchEvent方法,进行递归(如果递归完,事件还没有被处理,相当于被拦截)。

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);
}

View处理分析
dispatchTouchEvent的核心代码,但是View和ViewGroup的处理不一样

if (onFilterTouchEventForSecurity(event)) {
     if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
          result = true;
      }
     //noinspection SimplifiableIfStatement
     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;
    }
}

一办重写的onTouch方法,即就是OnTouchListener的onTouch方法li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED一般都是为true,所以重写的onTouch事件返回true,即走result = true,返回false就继续往下走。(!result && onTouchEvent(event))result如果为true,onTouchEvent(event)不会执行,如果为false,onTouchEvent(event)才会执行。而onTouchEvent方法里面就有触摸事件处理MotionEvent.ACTION_UP、 MotionEvent.ACTION_DOWN 、MotionEvent.ACTION_CANCEL、MotionEvent.ACTION_MOVE

事件冲突

解决事件冲突,只能是拦截MotionEvent.ACTION_MOVE 事件:
外部拦截法:父容器去拦截
根据逻辑判断是否需要去拦截,拦截返回true,不拦截返回false
内部拦截法:子容器去拦截

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;
}

如果父容器的onInterceptTouchEvent方法返回true,子容器将拿不到事件,这时想要使用内部拦截只能使disallowIntercept为true,改变他的值就需要祭出子View的尚方宝剑requestDisallowInterceptTouchEvent()方法。在子View中重写dispatchTouchEvent方法的MotionEvent.ACTION_DOWN事件下调用其父类的requestDisallowInterceptTouchEvent(boolean)方法可以对disallowIntercept进行赋值为true,进而直接执行else代码块intercepted = false;,不进行拦截。

@Override
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
    if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
        // We're already in this state, assume our ancestors are too
        return;
    }
    if (disallowIntercept) {
        mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
    } else {
        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
    }
    // Pass it up to our parent
    if (mParent != null){
        mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
    }
}

但是子View依然不会拿到事件,因为在ViewGroup的dispatchTouchEvent事件首先会执行如下一段代码,它表明了在MotionEvent.ACTION_DOWN事件下会恢复所有的初始值,mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;因此disallowIntercept必定为true;依然会执行intercepted = onInterceptTouchEvent(ev);

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);
   resetTouchState();
}

要想子View能拿到事件,在父容器必定不能拦截MotionEvent.ACTION_DOWN事件,必须返回false,可以如下操作

@Override
publick boolean onInterceptTouchEvent(MotionEvent event) {
    if (event.getAction() == MotionEvent.ACTION_DOWN){
        super.onInterceptTouchEvent(event);
        return false;
    }
    return true;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值