一、事件的接收
在上篇View的绘制流程中,我们提到在ViewRootImpl的setView方法中关于事件接收的一段代码:
mInputEventReceiver = new WindowInputEventReceiver(inputChannel,
Looper.myLooper());
final class WindowInputEventReceiver extends InputEventReceiver {
public WindowInputEventReceiver(InputChannel inputChannel, Looper looper) {
super(inputChannel, looper);
}
@Override
public void onInputEvent(InputEvent event) {//🌟 主要方法
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "processInputEventForCompatibility");
List<InputEvent> processedEvents;
try {
processedEvents =
mInputCompatProcessor.processInputEventForCompatibility(event);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
if (processedEvents != null) {
if (processedEvents.isEmpty()) {
// InputEvent consumed by mInputCompatProcessor
finishInputEvent(event, true);
} else {
for (int i = 0; i < processedEvents.size(); i++) {
enqueueInputEvent( //🌟主要流程
processedEvents.get(i), this,
QueuedInputEvent.FLAG_MODIFIED_FOR_COMPATIBILITY, true);
}
}
} else {
enqueueInputEvent(event, this, 0, true); //🌟主要流程
}
}
......
}
WindowInput Receiver明显是一个事件接收器,其主要流程方法是 enqueueInputEvent,下面我们看enqueueInputEvent方法:
void enqueueInputEvent(InputEvent event,
InputEventReceiver receiver, int flags, boolean processImmediately) {
......
doProcessInputEvents();
......
}
void doProcessInputEvents() {
......
deliverInputEvent(q);
......
}
private void deliverInputEvent(QueuedInputEvent q) {
......
InputStage stage;
......
stage.deliver(q);
......
}
public final void deliver(QueuedInputEvent q) {
......
result = onProcess(q);
......
}
上面分析了,从事件接收器,到InputStage的一个调用流程,最后会调用InputStage的onProcess方法,InputStage是一个抽象类,所以我们要找到其子类,在ViewRootImpl中的setView方法中:
InputStage mSyntheticInputStage
mSyntheticInputStage = new SyntheticInputStage();
InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);
InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage,
"aq:native-post-ime:" + counterSuffix);
InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage);
InputStage imeStage = new ImeInputStage(earlyPostImeStage,
"aq:ime:" + counterSuffix);
InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage);
InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage,
"aq:native-pre-ime:" + counterSuffix);
有这几种InputStage,其中ViewPostImeInputStage这个InputStage是专门处理按键触摸的,所以上面调用的stage.deliver方法,之后调用的onProcess方法,是ViewPostImeInputStage的onProcess方法:
protected int onProcess(QueuedInputEvent q) {
......
return processPointerEvent(q);
......
}
private int processPointerEvent(QueuedInputEvent q) {
......
boolean handled = mView.dispatchPointerEvent(event);
......
}
到这里就出现了我们熟悉的mView,其实就是DecorView,调用的是DecorView的dispatchPointerEvent方法,经过查找,DecorView中没有这个方法,那在DecorView的父类中查找,一直到View,才找到这个方法:
public final boolean dispatchPointerEvent(MotionEvent event) {
if (event.isTouchEvent()) {
return dispatchTouchEvent(event); //🌟
} else {
return dispatchGenericMotionEvent(event);
}
}
之后会调用dispatchTochEvent方法,注意这里的多态,调用者是DecorView,因为自己没有dispatchPointerEvent才调用超类View的dispatchPointerEvent方法,dispatchPointerEvent方法中调用了dispatchTouchEvent,这个dispatchTouchEvent应该是DecorView中的dispatchTouchEvent方法:
public boolean dispatchTouchEvent(MotionEvent ev) {
final Window.Callback cb = mWindow.getCallback();
return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
}
这里的cb就是Activity,所以cb.dispatchTouchEvent(ev)就是调用Activity的dispatchTouchEvent(ev)方法,所以事件的传递是先到DecorView的dispatchTouchEvent再到Activity的dispatchTouchEvent
下面我们看Activity的dispatchTochEvnent方法:
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {//🌟
return true;
}
return onTouchEvent(ev);
}
通过getWindow().superDispatchTouchEvent方法从Activity向下分发:
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
先把事件Event分发到了DecorView,调用了DecorView的superDispatchTouchEvent方法:
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
DecorView的super就是ViewGroup,接下来就是大戏,从VIewGroup到N个ViewGroup再到View的事件分发
二、ViewGroup到N个ViewGroup最后到View的事件的分发
1.先整体分析ViewGroup的dispatchTouchEvent源码
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
// If the event targets the accessibility focused view and this is it, start
// normal event dispatch. Maybe a descendant is what will handle the click.
if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
ev.setTargetAccessibilityFocus(false);
}
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Handle an initial down.
//🌟 每一个Action_Down事件来都会重新设置状态
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();
}
// Check for interception.
final boolean intercepted;
//🌟只有是Action_Down事件或者mFirstTouchTarget不为null才有权决定拦不拦截事件
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//🌟通过mGroupFlags推算出disallowIntercept这个值,来决定有没有权利执行
//onInterceptTouchEvent,如果没有权利执行,则直接给interceped赋值为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;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
// If intercepted, start normal event dispatch. Also if there is already
// a view that is handling the gesture, do normal event dispatch.
if (intercepted || mFirstTouchTarget != null) {
ev.setTargetAccessibilityFocus(false);
}
// Check for cancelation.
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
// Update list of touch targets for pointer down, if needed.
final boolean isMouseEvent = ev.getSource() == InputDevice.SOURCE_MOUSE;
//是否允许多指默认是true允许,在后面这个split值就当作是true
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0
&& !isMouseEvent;
//🌟这个变量需要记一下
TouchTarget newTouchTarget = null;
//🌟这个变量也需要记一下
boolean alreadyDispatchedToNewTouchTarget = false;
//🌟如果不取消也不拦截,进入下面的逻辑
if (!canceled && !intercepted) {
// If the event is targeting accessibility focus we give it to the
// view that has accessibility focus and if it does not handle it
// we clear the flag and dispatch the event to all children as usual.
// We are looking up the accessibility focused host to avoid keeping
// state since these events are very rare.
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
? findChildWithAccessibilityFocus() : null;
//🌟只有是Down事件才进入判断逻辑
if (actionMasked == MotionEvent.ACTION_DOWN
//ACTION_POINTER_DOWN是多指的Down事件
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
//这个是鼠标事件我们一般不用管
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int actionIndex = ev.getActionIndex(); // always 0 for down
//这个是手指代表的Id,Int型,有32位,后面的位运算代表1位是一个手指,所以多指最多是32个手指,谷歌工程师预留的,这里了解一下即可
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
// Clean up earlier touch targets for this pointer id in case they
// have become out of sync.
removePointersFromTouchTargets(idBitsToAssign);
//🌟获取当前ViewGroup有多少个孩子
final int childrenCount = mChildrenCount;
//🌟如果newTouchTarget,并且有子view
if (newTouchTarget == null && childrenCount != 0) {
final float x =
isMouseEvent ? ev.getXCursorPosition() : ev.getX(actionIndex);
final float y =
isMouseEvent ? ev.getYCursorPosition() : ev.getY(actionIndex);
// Find a child that can receive the event.
// Scan children from front to back.
//🌟这个需要具体分析,概括就是把View按层级(可以理解成FrameLayout的xml的顺序)放到List中
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
//🌟倒着取List里的view,List越在后面的,也就是越在上层,所以倒着取
//view就能先取最上层的子View,我们了解的事件传递也是先从上层传递
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
// If there is a view that has accessibility focus we want it
// to get the event first and if not handled we will perform a
// normal dispatch. We may do a double iteration but this is
// safer given the timeframe.
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}
//🌟这是在循环从最上层开始往下获取子View,如果这个子view不具有
//接收事件的能力,或者这个Action_Down事件的坐标不在这个子View的范围内则直接continue,直接判断
//下一个子View
if (!child.canReceivePointerEvents()
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
//这里单指操作为null,多指才不为null
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);
//🌟需要具体分析,询问child是否处理事件,如果处理则命中if进入
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;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
//🌟这里需要具体分析,上面说记住的newTouchTarget,概括来说这里就是当前child处理的事件,用mFirstTouchTarget和newTouchTarget保存这个处理事件的View
newTouchTarget = addTouchTarget(child, idBitsToAssign);
//🌟上面说记住的alreadyDispatchedToNewTouchTarget,表示已经有view处理事件了,后面会用到
alreadyDispatchedToNewTouchTarget = true;
//🌟事件命中之后,找到了处理事件的子View,就不需要再循环那
个子View的List了,目的已经达到了所以break掉
break;
}
// The accessibility focus didn't handle the event, so clear
// the flag and do a normal dispatch to all children.
ev.setTargetAccessibilityFocus(false);
}
if (preorderedList != null) preorderedList.clear();
}
//这是一个多指的处理
if (newTouchTarget == null && mFirstTouchTarget != null) {
// Did not find a child to receive the event.
// Assign the pointer to the least recently added target.
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
// Dispatch to touch targets.
//🌟当mFirstTouchTarget为null时说明,没有孩子要处理这个事件
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
//🌟询问自己是否处理这个事件,如果处理handled是true,如果不处理handled是
//false,这个handled是dispatchTouchEvent方法的返回值,如果是true说明有View处理,如果返回False说明没有VIew处理
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
//🌟如果mFirstTouchTarget不为null,说明已经有子View处理事件了
// Dispatch to touch targets, excluding the new touch target if we already
// dispatched to it. Cancel touch targets if necessary.
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
//这个循环是为了多指,如果是单指这个View可以当作if,只会执行一次
while (target != null) {
final TouchTarget next = target.next;
//🌟上面在子View处理事件的时候赋值为true,也就是说如果事件已经有子View
//处理这里直接给下面handled赋值为true,最后整个方法的返回值就是这个handled变量,handled是true代表有View处理事件,handled是false代表没有View处理这个事件
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
//🌟具体分析,需要重新询问,处理事件的View,是否要处理这个事件
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
//如果取消是true则取消当前处理事件的这个View的事件
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
// Update list of touch targets for pointer up or cancel, if needed.
if (canceled
|| actionMasked == MotionEvent.ACTION_UP
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
resetTouchState();
} else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
final int actionIndex = ev.getActionIndex();
final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
removePointersFromTouchTargets(idBitsToRemove);
}
}
if (!handled && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
}
//最后返回的handled
return handled;
}
2.把上面ViewGroup的dispatchTouchEvent源码拆分,详细分析,并且结合一些事例来了解事件分发
状态重制:
//🌟 每一个Action_Down事件来都会重新设置状态
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();
}
我们都知道事件一般都是: Action_Down后面跟N个Action_Move(N可以为0),最后又一个Action_Up,所以一般来说Action_Down可以作为一系列事件的开头,这里通过判断如果事件是Action_Down则重新设置状态,状态初始化
我们看着两个方法都干了什么:
private void cancelAndClearTouchTargets(MotionEvent event) {
......
clearTouchTargets();
......
}
private void resetTouchState() {
clearTouchTargets();
resetCancelNextUpFlag(this);
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
mNestedScrollAxes = SCROLL_AXIS_NONE;
}
private void clearTouchTargets() {
TouchTarget target = mFirstTouchTarget;
if (target != null) {
do {
TouchTarget next = target.next;
target.recycle();
target = next;
} while (target != null);
mFirstTouchTarget = null;
}
}
主要就是把mFirstTouchTarget和mGroupFlags给reset到初始值
我们把disPatchTouchEvent的分发流程分三大块来分析
(1).ViewGroup是否拦截事件
// Check for interception.
final boolean intercepted;
//🌟只有是Action_Down事件或者mFirstTouchTarget不为null才有权决定拦不拦截事件
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//🌟通过mGroupFlags推算出disallowIntercept这个值,来决定有没有权利执行
//onInterceptTouchEvent,如果没有权利执行,则直接给interceped赋值为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;
}
} 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或者mFirestTouchTarget不为null的时候,当前的View才有权决定拦截或者不拦截。Action_down是一轮事件的起始,mFirstTouchTarget是正在处理这轮事件的View(理解这个mFirstTouchTarget需要看第二大块的分析),也就是说只有是一轮事件的起始Action_Down或着已经有处理这轮事件的View的前提下才能有权决定是否拦截,否则直接算作拦截。
有权决定是否拦截之后,命中if语句,继续分析下面的内容
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; }
disallowIntercept翻译为不允许拦截,所以当disallowIntercept是true的时候不允许拦截事件,当disallowIntercept为false的时候,命中if(!disallowIntercept)继续执行intercepted = onInterceptTouchEvent(ev);,这一句就是通过执行当前View的onInterceptTouchEvent方法,我们可以重写onInterceptTouchEvent方法去决定是否拦截。
这里有几个知识点:
1.disallowIntercept默认是false,它是通过mGroupFlags这个值计算出来的,在reset状态的时候会重置mGroupFlags这个值
2.disallowIntercept这个flag多被子View通过调用getParent().requestDisallowInterceptTouchEvent这个方法,来通知父View要不要拦截,滑动冲突的外部拦截法就是借助这个方法,在文章的最后会详细介绍
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);
}
}
(2).在不拦截的情况下,遍历子View,询问子View是否处理事件,只有是Action_Down事件,才会进入给子View分发的逻辑
只能分发Down事件
//🌟这个变量需要记一下
TouchTarget newTouchTarget = null;
//🌟这个变量也需要记一下
boolean alreadyDispatchedToNewTouchTarget = false;
//🌟如果不取消也不拦截,进入下面的逻辑
if (!canceled && !intercepted) {
// If the event is targeting accessibility focus we give it to the
// view that has accessibility focus and if it does not handle it
// we clear the flag and dispatch the event to all children as usual.
// We are looking up the accessibility focused host to avoid keeping
// state since these events are very rare.
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
? findChildWithAccessibilityFocus() : null;
//🌟只有是Down事件才进入判断逻辑
if (actionMasked == MotionEvent.ACTION_DOWN
//ACTION_POINTER_DOWN是多指的Down事件
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
//这个是鼠标事件我们一般不用管
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int actionIndex = ev.getActionIndex(); // always 0 for down
//这个是手指代表的Id,Int型,有32位,后面的位运算代表1位是一个手指,所以多指最多是32个手指,谷歌工程师预留的,这里了解一下即可
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
// Clean up earlier touch targets for this pointer id in case they
// have become out of sync.
removePointersFromTouchTargets(idBitsToAssign);
//🌟获取当前ViewGroup有多少个孩子
final int childrenCount = mChildrenCount;
//🌟如果newTouchTarget,并且有子view
if (newTouchTarget == null && childrenCount != 0) {
final float x =
isMouseEvent ? ev.getXCursorPosition() : ev.getX(actionIndex);
final float y =
isMouseEvent ? ev.getYCursorPosition() : ev.getY(actionIndex);
// Find a child that can receive the event.
// Scan children from front to back.
//🌟这个需要具体分析,概括就是把View按层级(可以理解成FrameLayout的xml的顺序)放到List中
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
//🌟倒着取List里的view,List越在后面的,也就是越在上层,所以倒着取
//view就能先取最上层的子View,我们了解的事件传递也是先从上层传递
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
// If there is a view that has accessibility focus we want it
// to get the event first and if not handled we will perform a
// normal dispatch. We may do a double iteration but this is
// safer given the timeframe.
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}
//🌟这是在循环从最上层开始往下获取子View,如果这个子view不具有
//接收事件的能力,或者这个Action_Down事件的坐标不在这个子View的范围内则直接continue,直接判断
//下一个子View
if (!child.canReceivePointerEvents()
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
//这里单指操作为null,多指才不为null
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);
//🌟需要具体分析,询问child是否处理事件,如果处理则命中if进入
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;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
//🌟这里需要具体分析,上面说记住的newTouchTarget,概括来说这里就是当前child处理的事件,用mFirstTouchTarget和newTouchTarget保存这个处理事件的View
newTouchTarget = addTouchTarget(child, idBitsToAssign);
//🌟上面说记住的alreadyDispatchedToNewTouchTarget,表示已经有view处理事件了,后面会用到
alreadyDispatchedToNewTouchTarget = true;
//🌟事件命中之后,找到了处理事件的子View,就不需要再循环那
个子View的List了,目的已经达到了所以break掉
break;
}
// The accessibility focus didn't handle the event, so clear
// the flag and do a normal dispatch to all children.
ev.setTargetAccessibilityFocus(false);
}
if (preorderedList != null) preorderedList.clear();
}
//这是一个多指的处理
if (newTouchTarget == null && mFirstTouchTarget != null) {
// Did not find a child to receive the event.
// Assign the pointer to the least recently added target.
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
if (actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
只有是Action_Down事件,才会进入给子View分发的逻辑,这就会让人有点疑惑,那Action_Move和Action_up事件呢?之后会详细说明,我们带着问题分析源码
如何分发Action_Down事件的:
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
这段代码是把View按层级从低到高存储在List中:
public ArrayList<View> buildTouchDispatchChildList() {
return buildOrderedChildList();
}
ArrayList<View> buildOrderedChildList() {
final int childrenCount = mChildrenCount;
if (childrenCount <= 1 || !hasChildWithZ()) return null;
if (mPreSortedChildren == null) {
mPreSortedChildren = new ArrayList<>(childrenCount);
} else {
// callers should clear, so clear shouldn't be necessary, but for safety...
mPreSortedChildren.clear();
mPreSortedChildren.ensureCapacity(childrenCount);
}
final boolean customOrder = isChildrenDrawingOrderEnabled();
//循环遍历所有的子View,按照层级从低到高保存在List中
for (int i = 0; i < childrenCount; i++) {
// add next child (in child order) to end of list
//perorder 先序,意思是从低到高的获取View的Index
final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
final View nextChild = mChildren[childIndex];
//获取z轴的值,如果不设置默认是0
final float currentZ = nextChild.getZ();
// insert ahead of any Views with greater Z
int insertIndex = i;
//如果Z轴不是0,就在列表中找到合适的位置,即找到第一个比currentZ大的,排在这个比他大的后面,插入法排序
while (insertIndex > 0 && mPreSortedChildren.get(insertIndex - 1).getZ() > currentZ) {
insertIndex--;
}
//如果正常,就按照从低到高层次保存View
mPreSortedChildren.add(insertIndex, nextChild);
}
return mPreSortedChildren;
}
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
倒着遍历,从之前按层级从低到高的List中取View,就是先取最上层的子View:
if (!child.canReceivePointerEvents() || !isTransformedTouchPointInView(x, y, child, null)) { ev.setTargetAccessibilityFocus(false); continue; }
这里判断的是,这个子View能不能处理点击事件,如果子View不可见并且也没有在执行animation动画说明View是隐藏的,肯定就不具备处理点击事件的能力,或者点击事件的坐标没有在这个子View范围内,那也不可能交给它处理,这种View就直接略过,继续遍历判断下一个子View,代码执行完这里还能往下执行,说明这个子View具备处理事件的能力,继续往下
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
这一句概括来说就是,询问这个子View是否处理事件,如果处理则命中if,我们简单分析一下这个dispatchTransformedTouchEvent方法:
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
......
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可能是ViewGroup也可能是View,如果是ViewGroup就是递归,如果是View,我们后面分析
handled = child.dispatchTouchEvent(transformedEvent);
}
......
}
只有是Action_Down事件,才会进入给子View分发的逻辑,因为子View可能是ViewGroup,所以这个询问的过程,可能是递归好多ViewGroup(好好理解这个递归),注意:ViewGroup也是View,直到有一个子View要处理这个事件(后面我们会分析事件分发到View,如何代表着View要处理这个事件),命中if则执行if中的逻辑:
newTouchTarget = addTouchTarget(child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true; break;
这三句一句一句分析
第一句:
newTouchTarget = addTouchTarget(child, idBitsToAssign);这句:
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
//🌟创建一个TouchTarget对象,保存当前处理事件的View
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
//TouchTarget是一个链表,多指的时候,每一个节点保存不同手指的Down事件各自处理的View,,我们可以先不管
target.next = mFirstTouchTarget;
//🌟这里mFirstTouchTarget指向了target,target保存了处理事件的View
mFirstTouchTarget = target;
return target;
}
newTouchTarget这个终于出现赋值的地方了,newTouchTarget和mFirstTouchTarget保存了处理Down事件的View
第二句:
alreadyDispatchedToNewTouchTarget = true;
用一个变量表示,当前已经有一个View(ViewGroup也是View)处理事件了
第三句:
break;
当前是在循环遍历所有的子View来寻找谁来处理这个Down事件,现在找到了一个要处理Down事件的View,就不需要再循环查找了,直接break跳出循环。
到这里,第二大块,循环查找哪个子View来处理Action_Down事件
总结:只有是Action_Down事件时才会进入这个环节,当事件没有被拦截时,按层级从上往下的遍历每一个子View,先把不显示的和Down事件坐标之外的这类不具备处理Down事件的View略过,然后询问具备处理这个事件的能力的View是否处理,如果有View想处理这个事件,则用newTouchTarget和mFirstTouchTarget同时保存这个View,newTouchTarget和mFirstTouchTarget的意义就是保存处理Down事件的View,alreadyDispatchedToNewTouchTarget赋值为true。表明已经有View处理这个Down事件了,并且跳出循环,这里注意有一个递归返回的点,如果最后是View处理的这个Down事件,那他的父亲ViewGroup0的mFirstTouchTarget就指向这个View,因为是递归ViewGroup的父亲ViewGroup1的mFrestTouchTarget会指向ViewGroup0
如果循环遍历了一遍没有View要处理这个Down事件,则newTouchTarget和mFirstTouchTarget都是null,alreadyDispatchedToNewTouchTarget是false
(3).如果没有子View处理Down事件,询问自己是否处理,有子View处理的流程
//🌟当mFirstTouchTarget为null时说明,没有孩子要处理这个事件
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
//🌟询问自己是否处理这个事件,如果处理handled是true,如果不处理handled是
//false,这个handled是dispatchTouchEvent方法的返回值,如果是true说明有View处理,如果返回False说明没有VIew处理
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
//🌟如果mFirstTouchTarget不为null,说明已经有子View处理事件了
// Dispatch to touch targets, excluding the new touch target if we already
// dispatched to it. Cancel touch targets if necessary.
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
//这个循环是为了多指,如果是单指这个View可以当作if,只会执行一次
while (target != null) {
final TouchTarget next = target.next;
//🌟上面在子View处理事件的时候赋值为true,也就是说如果事件已经有子View
//处理这里直接给下面handled赋值为true,最后整个方法的返回值就是这个handled变量,handled是true代表有View处理事件,handled是false代表没有View处理这个事件
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
//🌟具体分析,需要重新询问,处理事件的View,是否要处理这个事件
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
//如果取消是true则取消当前处理事件的这个View的事件
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
// Update list of touch targets for pointer up or cancel, if needed.
if (canceled
|| actionMasked == MotionEvent.ACTION_UP
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
resetTouchState();
} else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
final int actionIndex = ev.getActionIndex();
final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
removePointersFromTouchTargets(idBitsToRemove);
}
}
if (!handled && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
}
//最后返回的handled
return handled;
1.如果没有子View处理Down事件
if (mFirstTouchTarget == null) { // No touch targets so treat this as an ordinary view. handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); }
...... return handled;
如果 mFirstTouchTarget是null,说明所有的子View(View包括ViewGroup)都不想处理这个Donw事件,那就询问自己是否想处理,还是这个dispatchTransformedTouchEvent方法,只不过这个child参数是null:
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
......
f (child == null) {
//这次走的是这个分支,当前的类是ViewGroup,ViewGroup的Super是View,所以就是调用
//当前View的dispatchTouchEvent,也就是自己作为View的dispatchTouchEvent,询问自己处理不处理Down
//事件,如果处理handled是true,如果不处理handled是false
handled = super.dispatchTouchEvent(transformedEvent);
} else {
......
}
......
}
这里注意ViewGroup和VIew的区别,当从上往下分发时ViewGroup就是只具备分发作用,如果没有子View想要处理这个Down事件,ViewGroup会把自己的身份转换成View(其实本来VIewGroup也是View的子类),这时就会询问自己是否处理这个事件,本质是ViewGroup和View的dispatchTouchEvent实现不一样,当ViewGroup作为ViewGroup的时候只具备事件分发的能力,只有ViewGroup作为View的时候才具备处理实践的能力。
2.如果有子View处理Down事件
else { // Dispatch to touch targets, excluding the new touch target if we already // dispatched to it. Cancel touch targets if necessary. TouchTarget predecessor = null; TouchTarget target = mFirstTouchTarget; while (target != null) { final TouchTarget next = target.next; if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { handled = true; } else { final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted; if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) { handled = true; } if (cancelChild) { if (predecessor == null) { mFirstTouchTarget = next; } else { predecessor.next = next; } target.recycle(); target = next; continue; } } predecessor = target; target = next; } }
如果当前还是Down事件,到这个分支肯定是有子View处理了,alreadyDispatchedToNewTouchTarget是true,当前事件的目标View是同一个,会直接走
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { handled = true; }
如果不是Down事件,alreadyDispatchedToNewTouchTarget重置为false,会走这段逻辑:
{ ...... if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) { handled = true; } ...... }
当处理Move事件的时候,上面第二大块分发逻辑不会执行,但这里会调用dispatchTransformedTouchEvent,由这个方法递归的去找到之前处理Down事件的View,接着让它处理Move事件
回答上面第二段之分发Down事件,Move事件和Up事件怎么办:
我记得有一个结论,Down事件是谁处理的,后面的事件就由谁处理,下面就是根据源码分析:
dispatchTransformedTouchEvent方法可能会有一个递归,这就可能出现ViewGroup1的mFirstTouchTarget是ViewGroup2,ViewGroup2的mFirstTouchTarget是ViewGroup3,然后继续递归,这时会分两种情况:
1.如果之前的Donw事件是View处理的
那后面的Move事件,会从顶层ViewGroup1的mFirstTouchTarget也就是ViewGroup2,然后直到最后mFirstTouchTarget是这个处理Donw事件的View,调用View的dispatchTouchEvent来处理Move事件
2.如果之前的Donw事件是ViewGroup处理的
那后面的Move事件,会从顶层ViewGroup1的mFirst TouchTarget也就是ViewGroup2,直到这个ViewGroup(N),当是ViewGroup(N)处理Donw事件时,说明没有子View要处理事件,所以mFirstTouchTarget是null,我们看过dispatchTransformedTouchEvent这个询问是否处理事件的方法,当child是null的时候,会执行ViewGroup的super.dispatchTouchEvent即View类的dispatchTouchEvent,VIew的dispatchTouchEvent方法就来处理move事件
三、View处理事件的解析,即View类的dispatchTouchEvent方法解析
public boolean dispatchTouchEvent(MotionEvent event) {
......
//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;
}
}
......
return result;
}
分两块分析:
ListenerInfo li = mListenerInfo; if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { result = true; }
这个mListenerInfo在setOnTouchListener的时候初始化方法是:
ListenerInfo getListenerInfo() {
if (mListenerInfo != null) {
return mListenerInfo;
}
mListenerInfo = new ListenerInfo();
return mListenerInfo;
}
它是在调用View的setXXXListener的时候初始化的,例如:
public void setOnTouchListener(OnTouchListener l) {
getListenerInfo().mOnTouchListener = l;
}
由此我们知道,这个mListenerInfo就是用来保存这些Listener的,这个判断的意思是如果View设置了setOnTouchListener就会执行OnTouchListener接口的OnTouch方法,这样就算是VIew处理了事件,result赋值为true,这个result就是dispatchTouchEvent的返回值
分析第二块:
if (!result && onTouchEvent(event)) { result = true; }
如果没有setOnTouchListener就会执行到这个逻辑,result是false,然后会执行onTouchEvent,所以这个onTouchEvent是重点,如果它返回true则dispatchTouchEvent也是true,否则就是false
public boolean onTouchEvent(MotionEvent event) {
......
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// This is a tap, so remove the longpress check
removeLongPressCallback();
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClickInternal();
}
}
}
......
break;
case MotionEvent.ACTION_DOWN:
......
// Not inside a scrolling container, so show the feedback right away
setPressed(true, x, y);
checkForLongClick(
ViewConfiguration.getLongPressTimeout(),
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
}
break;
case MotionEvent.ACTION_CANCEL:
if (clickable) {
setPressed(false);
}
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
break;
case MotionEvent.ACTION_MOVE:
if (clickable) {
drawableHotspotChanged(x, y);
}
final int motionClassification = event.getClassification();
final boolean ambiguousGesture =
motionClassification == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE;
int touchSlop = mTouchSlop;
if (ambiguousGesture && hasPendingLongPressCallback()) {
if (!pointInView(x, y, touchSlop)) {
// The default action here is to cancel long press. But instead, we
// just extend the timeout here, in case the classification
// stays ambiguous.
removeLongPressCallback();
long delay = (long) (ViewConfiguration.getLongPressTimeout()
* mAmbiguousGestureMultiplier);
// Subtract the time already spent
delay -= event.getEventTime() - event.getDownTime();
checkForLongClick(
delay,
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
}
touchSlop *= mAmbiguousGestureMultiplier;
}
// Be lenient about moving outside of buttons
if (!pointInView(x, y, touchSlop)) {
// Outside button
// Remove any future long press/tap checks
removeTapCallback();
removeLongPressCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
}
final boolean deepPress =
motionClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS;
if (deepPress && hasPendingLongPressCallback()) {
// process the long click action immediately
removeLongPressCallback();
checkForLongClick(
0 /* send immediately */,
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS);
}
break;
}
return true;
}
return false;
}
我们把这个方法分成几个步骤分析:
1.总体分析
public boolean onTouchEvent(MotionEvent event) {
......
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
......
}
return true;
}
return false;
}
从事件处理这个角度来说,只要是clickable的View或者(viewFlags & TOOLTIP) == TOOLTIP这个(讲实话不知道是在干什么,不重要)就会返回true,否则返回false,这也是为啥我们Button明明啥setOnXXXListener也没有设置,但是会消耗点击事件,就是这个原因。
2.switch分之Action_down
case MotionEvent.ACTION_DOWN:
......
// Not inside a scrolling container, so show the feedback right away
setPressed(true, x, y);
checkForLongClick(
ViewConfiguration.getLongPressTimeout(),
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
break;
private void checkForLongClick(long delay, float x, float y, int classification) {
if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE || (mViewFlags & TOOLTIP) == TOOLTIP) {
mHasPerformedLongPress = false;
if (mPendingCheckForLongPress == null) {
mPendingCheckForLongPress = new CheckForLongPress();
}
mPendingCheckForLongPress.setAnchor(x, y);
mPendingCheckForLongPress.rememberWindowAttachCount();
mPendingCheckForLongPress.rememberPressedState();
mPendingCheckForLongPress.setClassification(classification);
postDelayed(mPendingCheckForLongPress, delay);
}
}
又一个注意点,这个checkForLongClick是一个判断是否是长按事件的机制,在down事件的时候就
设置一个定时任务,这个时间在Android29包括29之前是500ms,在29之后是400ms,后面如果不取消这个任务,就判定为是长按。
3.switch分之Action_Move
case MotionEvent.ACTION_MOVE:
......
// Be lenient about moving outside of buttons
if (!pointInView(x, y, touchSlop)) {
// Outside button
// Remove any future long press/tap checks
removeTapCallback();
removeLongPressCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
}
......
break;
如果手指移出了View的范围,就是要取消一些响应事件
4.switch分之Action_Up
case MotionEvent.ACTION_UP:
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// This is a tap, so remove the longpress check
removeLongPressCallback();
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClickInternal();
}
}
}
break;
removeLongPressCallback();
在down事件的时候设置了长按事件的延时为400ms,如果到Up事件,按的时间没有到达400ms就取消长按事件的回调,如果多于400ms,长按事件的已经执行,取消就没有意义相当于啥也没做
if (!focusTaken) { // Use a Runnable and post this rather than calling // performClick directly. This lets other visual state // of the view update before click actions start. if (mPerformClick == null) { mPerformClick = new PerformClick(); } if (!post(mPerformClick)) { performClickInternal(); } }
这个performClick会调用到onClickListener的,所以注意这一点onClickListener在Action_up的时候触发,所以如果设置了setOnTouchListener和setOnClickListener,onClickListener不会执行
四、事件冲突的两种解决办法
我们用ViewPager和ListView来处理滑动冲突
1.内部拦截法
由子View根据条件来让事件由谁处理
之前我们提到过,getParent().requestDisallowInterceptTouchEvent可以让子View请求父View拦截不拦截事件
public class MyViewPager extends ViewPager {
public MyViewPager(@NonNull Context context) {
super(context);
}
public MyViewPager(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
//down 事件的时候不能拦截,我们必须先让子View拿到事件,然后再通过子View调用
// getParent().requestDisallowInterceptTouchEvent方法来请求父View拦截不拦截
if (ev.getAction() == MotionEvent.ACTION_DOWN){
super.onInterceptTouchEvent(ev);
return false;
}
//onInterceptTouchEvent方法设置成默认拦截,
// 因为子View只能控制执行不执行onInterceptTouchEvent方法
/* if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}*/
return true;
}
}
子View:
// 子View -- 容器 -- ViewGroup.dispatchTouchEvent --> super.dispatchTouchEvent
public class MyListView extends ListView {
public MyListView(Context context) {
super(context);
}
public MyListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
private int mLastX, mLastY;
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
getParent().requestDisallowInterceptTouchEvent(true);
break;
}
case MotionEvent.ACTION_MOVE: {
int deltaX = x - mLastX;
int deltaY = y - mLastY;
// 这个条件由业务逻辑决定,看什么时候 子View将事件让出去
if (Math.abs(deltaX) > Math.abs(deltaY)) {
getParent().requestDisallowInterceptTouchEvent(false);
}
break;
}
case MotionEvent.ACTION_UP: {
break;
}
default:
break;
}
mLastX = x;
mLastY = y;
return super.dispatchTouchEvent(event);
}
}
2.外部拦截法,父View直接决定是否拦截
// 父容器
public class BadViewPager extends ViewPager {
private int mLastX, mLastY;
public BadViewPager(@NonNull Context context) {
super(context);
}
public BadViewPager(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
// 外部拦截法:一般只需要在父容器处理,根据业务需求,返回true或者false
public boolean onInterceptTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
mLastX = (int) event.getX();
mLastY = (int) event.getY();
break;
}
case MotionEvent.ACTION_MOVE: {
int deltaX = x - mLastX;
int deltaY = y - mLastY;
if (Math.abs(deltaX) > Math.abs(deltaY)) {
return true;
}
break;
}
case MotionEvent.ACTION_UP: {
break;
}
default:
break;
}
return super.onInterceptTouchEvent(event);
}
}
滑动冲突的本质,就是事件分发的错乱,我们只要合理的对事件进行拦截和处理,把事件给到想要处理的那个View上即可。