2019-01-16ViewGroup事件分发
ViewGroup 事件分发
ViewGroup 事件分发主要又三个方法
@Override
//用于分发事件
public boolean dispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
@Override
//判断是否拦截
public boolean onInterceptTouchEvent(MotionEvent ev) {
return super.onInterceptTouchEvent(ev);
}
@Override
//用于消耗事件
public boolean onTouchEvent(MotionEvent event) {
return super.onTouchEvent(event);
}
相比view 增加了onInterceptTouchEvent 方法,这个方法是根据该方法的返回值来判断是否需要拦截事件。onTouchEvent 方法ViewGroup 没有重写,直接使用的View.onTouchEvent,关于onTouchEvent分析请看上一篇
dispatchTouchEvent
来分析dispatchTouchEvent ,这个方法被ViewGroup 重写了
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// 省略代码.......
boolean handled = false;
//整个方法都在这个if 里面,所以肯定会进来
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
//首先判断是否为 DOWN 事件 如果是则做一些初始化操作
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
resetTouchState();
}
//省略代码......
}
先来分析这么多,事件的开始都是从按下开始,所以第一个事件就是ACTION_DOWN,当为 DOWN 事件是,来看看做了什么
/**
* Cancels and clears all touch targets.
* 取消所有的targets
*/
private void cancelAndClearTouchTargets(MotionEvent event) {
if (mFirstTouchTarget != null) {
//省略代码 ......
//清除Targets
clearTouchTargets();
if (syntheticEvent) {
event.recycle();
}
}
}
/**
* Clears all touch targets.
*/
private void clearTouchTargets() {
TouchTarget target = mFirstTouchTarget;
if (target != null) {
do {
TouchTarget next = target.next;
target.recycle();
target = next;
} while (target != null);
//最终将mFirstTouchTarget 置为 null
mFirstTouchTarget = null;
}
}
所以cancelAndClearTouchTargets 方法就是将mFirstTouchTarget 置为null
继续往下看
//用于判断是否拦截
final boolean intercepted;
//第一个事件就是 DOWN 事件,mFirstTouchTarget 还没有赋值,所以为 null
//所以当事件为 DOWN 时,这个判断肯定会为true
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//这一个标志位,是子类请求父类不要拦截事件使用
//在前面 DOWN 事件初始化时候调用方法 resetTouchState() 将 mGroupFlags 置为
//FLAG_DISALLOW_INTERCEPT 所以在子布局没有修改的前提下 disallowIntercept 为false
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
//调用自身的onInterceptTouchEvent 方法
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action);
} else {
//如果子布局请求父布局不拦截,将 intercepted 置为false
intercepted = false;
}
} else {
//如果当前事件不为 DOWN 并且 mFirstTouchTarget == null,直接将 intercepted 置为 true
intercepted = true;
}
对于 disallowIntercept 这个值,当我们想父类不拦截事件是可以这样子写
//请求父类不要拦截事件
getParent().requestDisallowInterceptTouchEvent(true);
//ViewGroup 中,这个方法只是用来改变mGroupFlags 的值
@Override
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
//如果 mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0 为true,说明 mGroupFlags 已经修改了
//直接返回
if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
// We're already in this state, assume our ancestors are too
return;
}
//修改 mGroupFlags 的值
if (disallowIntercept) {
mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
} else {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
// Pass it up to our parent
if (mParent != null) {
mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
}
}
当我们请求父类不拦截事件时 intercepted 的值就恒为true
继续往下看
// Check for cancelation.
//判断是否取消,不需要,为true
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
// mGroupFlags 的值为 FLAG_DISALLOW_INTERCEPT 所以 split 为 true
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
// 来这里 canceled 默认为true intercepted 的值根据上面执行的方法得到,现在假设为false
if (!canceled && !intercepted) {
final int childrenCount = mChildrenCount;
//newTouchTarget 为null 子view 的数量不为0
if (newTouchTarget == null && childrenCount != 0) {
//倒序循环子view
for (int i = childrenCount - 1; i >= 0; i--) {
// getTouchTarget 里面代码简单,可以知道newTouchTarget还是为null
newTouchTarget = getTouchTarget(child);
//所以进不来
if (newTouchTarget != null) {
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
//注意看这里,后面分析
//第二个参数为false child不为null
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// addTouchTarget 里面相当于new 了一个对象分别给mFirstTouchTarget 和newTouchTarget 赋值
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
// The accessibility focus didn't handle the event, so clear
// the flag and do a normal dispatch to all children.
ev.setTargetAccessibilityFocus(false);
}
}
}
来看dispatchTransformedTouchEvent 这个方法干了什么
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
//将当前的事件赋值给 oldAction
final int oldAction = event.getAction();
//注意这里,是用来处理 ACTION_CANCEL 事件的
//根据传过来的参数,cancel 为false 事件为DOWN 所以不会进来这个判断
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;
}
//省略代码.....
//根据传过来的 child 不为 null
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());
}
//调用 child 的 dispatchTouchEvent 方法
handled = child.dispatchTouchEvent(transformedEvent);
}
// 释放
transformedEvent.recycle();
//返回子布局的 dispatchTouchEvent 的返回值
return handled;
}
所以到这里,当按下屏幕产生一个 down 事件,这个事件会从最外层的ViewGroup 中传递下来,调用子view 的dispatchTouchEvent 方法,如果当前子view 也为一个ViewGroup ,并且dispatchTouchEvent 方法没有返回true,那么该子view 又会调用自身子view 的dispatchTouchEvent方法,直到最后一个子 view 调完dispatchTouchEvent 方法。这是一个责任链的设计模式。回到前面
//当 dispatchTransformedTouchEvent 返回true 时才会进来,
// dispatchTransformedTouchEvent 的返回值又由 onTouchEvent 等方法决定
//可以看上一片view的事件分发
//这里面主要的操作就是给mFirstTouchTarget 赋值
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
//省略代码.....
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
// addTouchTarget 方法主要就是创建一个 TouchTarget 并赋值给 mFirstTouchTarget
//并将该 TouchTarget 返回,所以当前DOWN事件来说 newTouchTarget == mFirstTouchTarget
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
现在假定dispatchTransformedTouchEvent 返回true ,此时mFirstTouchTarget 不为null,继续往下
// 此时mFirstTouchTarget 不为null
if (mFirstTouchTarget == null) {
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
//根据上面可以直到newTouchTarget == target
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
}
predecessor = target;
//next 为null 跳出循环
target = next;
}
}
小总结 : 到这里一个 DOWN 事件完结,当 DOWN 事件发生时:这个事件会到达最外层的ViewGroup中,根据mGroupFlags的值来判断是否有子类需要消耗事件,如果没有,则调用自身的onInterceptTouchEvent方法,根据该方法的返回值判断是否需要拦截该事件。
- 如果返回 true :说明拦截改事件,就不会调用子类布局的 dispatchTouchEvent 方法,mFirstTouchTarget 的值 null ,后面因为mFirstTouchTarget 为null 调用 dispatchTransformedTouchEvent 方法是传的child为null,就会调用 super.dispatchTouchEvent 也就是view的 dispatchTouchEvent,之后就是view的事件分发流程
- 如果返回false:说明不需要拦截事件,则会调用子类布局的 dispatchTouchEvent ,即将事件分发到子布局中,之后对于自身来说,根据子布局的 dispatchTouchEvent 方法的返回值,判断是否需要给 mFirstTouchTarget 赋值,再之后根据 mFirstTouchTarget 的值来判断是否调用自身的 super.dispatchTouchEvent 方法
当 down 事件完成,来看第二个事件move事件
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
//当前不为ACTION_DOWN ,所以不会将mFirstTouchTarget 置为null
//根据上面假设的 子布局 dispatchTouchEvent 方法返回true,所以 mFirstTouchTarget 的值不为null
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
resetTouchState();
}
final boolean intercepted;
//mFirstTouchTarget != null 为true 所以会进入这个判断
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//判断标志位 mGroupFlags
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
//调用自身的 onInterceptTouchEvent 方法
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;
}
//根据 intercepted 判断是否调用子view的 dispatchTouchEvent 事件
if (!canceled && !intercepted) {
//当前为 MOVE 事件,第一个判断为false
// split 为false 第二个判断为false
//当前为 MOVE 事件 第三个判断为false
//所以这一段不会进来
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
//省略代码....
}
}
//当前 mFirstTouchTarget 不为null
if (mFirstTouchTarget == null) {
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
//newTouchTarget 没有被赋值 所以为null ,所以这个判断为false
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
//intercepted 为false ,resetCancelNextUpFlag 也为false
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
//调用 dispatchTransformedTouchEvent 方法 child 不为null
//其实就是调用子view 的 dispatchTouchEvent 方法,假设返回true
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
// cancelChild 为false
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
//返回 handled 为true
return handled;
}
到此 MOVE 事件也从父布局分发到子布局中。那么父布局拦截了其中一个事件,之后的事件子布局都收不到,这是怎么实现的呢?比如,我在父布局中这样子:
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_MOVE) {
Log.e("TAG", "onInterceptTouchEvent: ------>拦截了move事件");
return true;
}
return super.onInterceptTouchEvent(ev);
}
这样会如何呢?注意需要在 onTouchEvent 方法中返回true,否则这个判断是不会进来的。首先分析 DOWN事件,DOWN 事件和前面事件一样分发,到MOVE事件时,
//因为当前 onInterceptTouchEvent 返回true 所以 intercepted 为true
if (!canceled && !intercepted) {
//省略代码.....
}
//省略代码....
//DOWN事件之后 mFirstTouchTarget 不为null
if (mFirstTouchTarget == null) {
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
//newTouchTarget 为null
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
//intercepted 为true 所以 cancelChild 为true
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
//调用 dispatchTransformedTouchEvent 方法,cancelChild 为true,child不为null
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
//cancelChild 为true
if (cancelChild) {
//predecessor 为null
if (predecessor == null) {
//next 为null,所以 MOVE 事件之后 mFirstTouchTarget 为null
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
//将target 赋值为null 跳出循环
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
来看 dispatchTransformedTouchEvent 方法,当cancelChild 为true ,child 不为null时
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
//oldAction 为MOVE
final int oldAction = event.getAction();
//cancel 为true,所以会进来
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
//将当前事件设置为ACTION_CANCEL
event.setAction(MotionEvent.ACTION_CANCEL);
//child 不为null
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
//调用 dispatchTouchEvent 方法,注意当前的 event 为 ACTION_CANCEL
handled = child.dispatchTouchEvent(event);
}
//将当前的事件设置为 MOVE
event.setAction(oldAction);
return handled;
}
//省略代码....
}
所以子布局会收到一次 ACTION_CANCEL 事件。再时候会将 mFirstTouchTarget 置为null,子布局就收不到事件了。