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

被折叠的 条评论
为什么被折叠?



