标签:
引言:这部分会分三个模块来讲,先讲View对Touch的处理,再讲ViewGroup的事件分发,最后讲如何解决滑动冲突。
我习惯通过在源码中添加注释来理解源码,以下是我提取出来几个重要方法,将不重要的部分删掉,并且添加了中文注释。
如果一个View(比如Button)接收到Touch,那么该Touch事件首先会传入到它的dispatchTouchEvent( )方法,所以我们从这里开始学习View对Touch事件的处理。
// 返回值表示Touch事件是否被该View消费
public boolean dispatchTouchEvent(MotionEvent event) {
//result的值决定最后该方法的返回值,也就是决定Touch事件是否被消费
boolean result = false;
/***/
if (onFilterTouchEventForSecurity(event)) {
ListenerInfo li = mListenerInfo;
//该if判断中一共包含了4个条件,必须同时满足时才表示Touch事件被消费
//在这四个条件中,我们通常最关心的就是最后一个:TouchListener的onTouch()方法。假如这四个条件中的任意一个不满足,那么result仍为false;则进入下一步调用自身的onTouchEvent()
if (li != null && li.mOnTouchListener != null &&
(mViewFlags&ENABLED_MASK)==ENABLED && li.mOnTouchListener.onTouch(this,event)) {
result = true;
}
//调用View自身的onTouchEvent()处理Touch事件,由onTouchEvent的返回值决定result的值(当然,如果在上一步中,如果已经将result设为true,就不会去判断onTouchEvent()了)
if (!result && onTouchEvent(event)) {
result = true;
}
}
/***/
return result;
}
onTouchEvent()是决定事件是否被消耗的最后一道门,如果返回false,则它的父View的onTouchEvent会被调用,否则不会;
先来看看重要部分的源码:
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
//如果一个View是disable的,CLICKABLE,LONG_CLICKABLE,CONTEXT_CLICKABLE消耗掉,且不会触发onClick事件回调
if ((viewFlags & ENABLED_MASK) == DISABLED) {
return (((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
}
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
//如果View不是disable的,会继续执行,对CLICK,LONG_CLICK,CONTEXT_CLICKABLE进行处理
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
(viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
switch (action) {
case MotionEvent.ACTION_DOWN:
/***/
break;
case MotionEvent.ACTION_MOVE:
/***/
break;
case MotionEvent.ACTION_CANCEL:
/***/
break;
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
/***/
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
/***/
if (!focusTaken) {
/***/
if (!post(mPerformClick)) {
//performClick()中会回调onClick,所以我们平时常见的onClick回调都是在ACTION_UP的时候触发的
performClick();
}
}
}
}
/***/
}
//这里表示,只要View是enable的,Touch事件都会被消耗掉
return true;
}
return false;
}
引用一张谷歌的小弟画的流程图:
需要注意的点:
onTouch()与onTouchEvent()以及click三者的区别和联系 :
View没有事件的拦截(onInterceptTouchEvent( )),ViewGroup才有,请勿混淆
事件分发体系最重要的几个方法:
dispatchTouchEvent(event)
onInterceptTouchEvent(Event)
onTouchEvent(Event)
他们的关系可以这样表示:
public boolean dispatchTouchEvent(MotionEvent e) {
if(onInterceptTouchEvent(ev)) {
//如果拦截,就自己处理,调用自己的onTouchEvent,如果onTouchEvent返回true就消费掉,如果返回false就传给上层处理
return onTouchEvent(ev);
} else {
//如果不拦截,就走子view的分发流程
return child.dispatchTouchEvent(ev);
}
}
public boolean dispatchTouchEvent(MotionEvent ev) {
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
if (actionMasked == MotionEvent.ACTION_DOWN) {
//如果是DOWN事件,进行初始化和还原操作
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// 判断是否需要拦截事件,根据intercepted的值确定
// mFirstTouchTarget用于多点触控
// mFirstTouchTarget不为空,表示有子View消费了Touch事件
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
// 如果是DOWN或者有子View消费,则根据onInterceptTouchEvent判断是否拦截
// ViewGroup的onInterceptTouchEvent默认返回false,也就是不拦截
// 所以我们往往可以在自定义控件中重写这个方法,来决定什么情况下拦截事件
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action);
} else {
// 如果disallowIntercept == false,就是不允许拦截,可以在子View设置不允许父View拦截
intercepted = false;
}
} else {
// 执行到这里,说明mFirstTouchTarget为null或者不是DOWN事件,需要ViewGroup自己处理此次Touch事件
// 也就是拦截本次Touch事件
intercepted = true;
}
// 如果Touch事件没有被取消也没有被拦截,那么ViewGroup将类型为ACTION_DOWN的Touch事件分发给子View。
if (!canceled && !intercepted) {
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
// 根据Touch事件的坐标,找到触摸到了哪个子View
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
final ArrayList<View> preorderedList = buildOrderedChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = customOrder
? getChildDrawingOrder(childrenCount, i) : i;
final View child = (preorderedList == null)
? children[childIndex] : preorderedList.get(childIndex);
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
/***/
// 找到之后,调用dispatchTransformedTouchEvent,传入子view
// 子View没有消费Touch事件则该方法的返回值为false,此时mFirstTouchTarget仍为null
// 如果子View消费掉了Touch事件那么该方法的返回值为true,然后执行newTouchTarget = addTouchTarget(child, idBitsToAssign);
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
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消费掉了Touch事件那么该方法的返回值为true,然后执行
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
}
}
}
// mFirstTouchTarget为空,表示没有子View消耗Touch事件,需要ViewGroup自己处理
if (mFirstTouchTarget == null) {
// 同样也会调用dispatchTransformedTouchEvent,但是传入Null,标明由父View自己处理这次Touch事件
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
/***/
}
/***/
}
}
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
/***/
// 如果child == null,表明没有子View消费这次Touch事件
if (child == null) {
// 所以会调用super.dispatchTouchEvent,此时,ViewGroup就化身为了普通的View,它会在自己的onTouch(),onTouchEvent()中处理Touch
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());
}
// 如果child不为空,表示有子View处理这次Touch事件,直接调用child的dispatchTouchEvent
// 当然该view可能是一个View也可能是一个ViewGroup
handled = child.dispatchTouchEvent(transformedEvent);
}
// Done.
transformedEvent.recycle();
return handled;
}
最后会递归的执行子View的这套流程,或者被ViewGroup自身拦截掉,亲自用onTouchEvent处理这次事件
onInterceptTouchEvent()表示是否拦截此次事件,ViewGroup的默认实现是不拦截,return false;所以我们往往可以在自定义控件中重写这个方法,来决定什么情况下拦截事件
总结下来就是:
Touch事件的传递顺序为 :
Activity–>外层ViewGroup–>内层ViewGroup–>View
如果Touch事件在中间某一层被拦截了,DOWN事件将不会再传递给更底层的View
Touch事件的消费顺序为 :
View–>内层ViewGroup–>外层ViewGroup–>Activity
如果Touch事件在中间某一层被消费了,将不会再通知更上层的View,只有当所有子View都不消费Touch事件,顶层ViewGroup才会自己处理这次Touch事件。
引发原因:两个可以滑动的View互相嵌套,且滑动方向相同,则会产生滑动冲突。
有两个比较常见的解决方案:
1、在父View中准确地进行事件分发和拦截
2、使用Google在support.v4包提供的两个支持嵌套滚动的接口:onNestedScrollChild、onNestedScrollParent。(有一个例子,在我的Github上一个快速开发框架里的下拉刷新SwipeLayout中有用到,贴上地址:https://github.com/miomin/Shareward)
一个事件序列指的是从手指按下的一刻起直到手指放开,通常情况下,一个事件序列只能被一个View拦截或消耗,拦截和消耗通常都是在DOWN事件进行,如果不对DOWN时间进行消费,则不会有机会消耗后续的MOVE事件,如果消耗了DOWN事件,后续的MOVE和UP事件同样由这个View消费。(support.v4包中的NestedScrolling接口可以打破这个原则,允许多个View同时处理同一个事件序列)
ViewGroup的onInterceptTouchEvent默认返回false,也就是默认不会拦截事件,交给下层的View来处理。
View没有onInterceptTouchEvent方法,一旦有事件到达,就会调用onTouchEvent。
可点击的View的onTouchEvent默认返回true,也就是消耗事件。
标签:
原文地址:http://blog.csdn.net/mxm691292118/article/details/51815519