前言
点击事件的事件分发,其实就是对MotionEvent事件的分发过程,即当一个MotionEvent产生了以后,系统需要把这个事件传递给一个具体的View,而这个传递的过程就是分发过程。
基础概念
涉及到类
事件分发过程实际上就是Activity、ViewGroup、View对MotionEvent事件的分发,拦截,消费的过程。 其中,只有ViewGroup能进行事件拦截,因此只有ViewGroup有onInterceptTouchEvent方法。
类 | 方法 |
---|
Activity | 拥有dispathTouchEvent和onTouchEvent方法 | ViewGroup | 拥有dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent方法 | View | 拥有dispathTouchEvent和onTouchEvent方法 |
事件类型 | 含义 |
---|
ACTION_DOWN | 按下屏幕时,触发此事件,只会触发一次 | ACTION_MOVE | 移动手指时,触发此事件,会多次触发 | ACTION_UP | 手指离开屏幕时,触发此事件,只会触发一次 | ACTION_CANCEL | 取消事件时,触发此事件,只会触发一次 |
涉及到方法
方法 | 含义 |
---|
dispathTouchEvent | 事件分发 | onInterceptTouchEvent | 事件拦截 | onTouchEvent | 事件消费 |
方法详解:
用来进行事件的分发。如果事件能够传递给当前ViewGrop或者View,那么此方法一定会被调用,返回结果受当前ViewGrop或者View的onTouchEvent和下级View的dispatchTouchEvent方法的影响,表示是否消耗当前事件。
用来判断是否拦截某个事件,如果当前ViewGrop拦截了某个事件,那么在同一个事件序列当中,此方法不会被再次调用,返回结果表示是否拦截当前事件。
用来处理点击事件,返回结果是否消耗当前事件,如果不消耗,则在同一个事件序列中,当前ViewGrop或者View无法再次接收事件。
小结
当一个点击事件产生后,它的传递过程遵循如下顺序:Activity—>Window---->DecorView ,即事件总是先传递给Activity,Activity再传递给Window,最后Window再传递给DecorView 。DecorView 接收到事件后,就会按照事件分发机制去分发事件。
事件分发过程源码解析
点击事件传递到Activity过程
当我们点击屏幕时,通过Android消息机制,从Looper从MessageQueue中取出该事件,发送给WindowInputEventReceiver。
final class WindowInputEventReceiver extends InputEventReceiver {
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()) {
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);
}
}
}
void enqueueInputEvent(InputEvent event,
InputEventReceiver receiver, int flags, boolean processImmediately) {
QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);
QueuedInputEvent last = mPendingInputEventTail;
if (last == null) {
mPendingInputEventHead = q;
mPendingInputEventTail = q;
} else {
last.mNext = q;
mPendingInputEventTail = q;
}
mPendingInputEventCount += 1;
Trace.traceCounter(Trace.TRACE_TAG_INPUT, mPendingInputEventQueueLengthCounterName,
mPendingInputEventCount);
if (processImmediately) {
doProcessInputEvents();
} else {
scheduleProcessInputEvents();
}
}
WindowInputEventReceiver是ViewRootImpl的内部类,通过enqueueInputEvent方法,将输入事件加入输入事件队列中,doProcessInputEvents() 方法并进行处理和转发事件。
void doProcessInputEvents() {
while (mPendingInputEventHead != null) {
...
deliverInputEvent(q);
}
...
}
private void deliverInputEvent(QueuedInputEvent q) {
...
InputStage stage;
if (q.shouldSendToSynthesizer()) {
stage = mSyntheticInputStage;
} else {
stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
}
if (stage != null) {
handleWindowFocusChanged();
stage.deliver(q);
} else {
finishInputEvent(q);
}
}
final class ViewPostImeInputStage extends InputStage {
...
@Override
protected int onProcess(QueuedInputEvent q) {
if (q.mEvent instanceof KeyEvent) {
return processKeyEvent(q);
} else {
final int source = q.mEvent.getSource();
if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
return processPointerEvent(q);
} else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
return processTrackballEvent(q);
} else {
return processGenericMotionEvent(q);
}
}
}
private int processPointerEvent(QueuedInputEvent q) {
final MotionEvent event = (MotionEvent)q.mEvent;
mAttachInfo.mUnbufferedDispatchRequested = false;
mAttachInfo.mHandlingPointerEvent = true;
boolean handled = mView.dispatchPointerEvent(event);
maybeUpdatePointerIcon(event);
maybeUpdateTooltip(event);
mAttachInfo.mHandlingPointerEvent = false;
if (mAttachInfo.mUnbufferedDispatchRequested && !mUnbufferedInputDispatch) {
mUnbufferedInputDispatch = true;
if (mConsumeBatchedInputScheduled) {
scheduleConsumeBatchedInputImmediately();
}
}
return handled ? FINISH_HANDLED : FORWARD;
}
}
InputStage 是个抽象类,具体工作交由它的子类ViewPostImeInputStage来负责处理。ViewPostImeInputStage 类的onProcess()方法判断事件是哪种类型的。根据判断手指点击屏幕条件,会调用processPointerEvent()方法,事件会交由DecorView的dispatchPointerEvent()向下级View 进行分发处理。 接着往下看
public final boolean dispatchPointerEvent(MotionEvent event) {
if (event.isTouchEvent()) {
return dispatchTouchEvent(event);
} else {
return dispatchGenericMotionEvent(event);
}
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
final Window.Callback cb = mWindow.getCallback();
return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
}
从上面的代码看,事件下发到View的dispatchPointerEvent()方法。ViewGroup重写View的dispatchTouchEvent()方法,事件传递到ViewGroup的dispatchTouchEvent()方法。DecorView是ViewGroup的子类,并且重写了dispatchTouchEvent()方法。通过Window回调Callback方法,事件传递到Activity的dispatchTouchEvent()方法中。
Activity的事件分发过程
点击事件用MotionEvent来表示,当一个点击操作发生时,事件最先传递给当前Activity,由Activity的dispatchTouchEvent来进行事件派发,具体的工作是由Activity内部的Window来完成。Window 会将事件传递给DecorView,DecorView一般就是当前界面的底层容器(即 setContentView 所设置的View父容器),通过Activity.getWindow.getDecorView()可以获得。
public class Activity extends ContextThemeWrapper
implements LayoutInflater.Factory2,
Window.Callback, KeyEvent.Callback,
OnCreateContextMenuListener, ComponentCallbacks2,
Window.OnWindowDismissedCallback,
AutofillManager.AutofillClient, ContentCaptureManager.ContentCaptureClient {
private Window mWindow;
....
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
...
public Window getWindow() {
return mWindow;
}
public boolean onTouchEvent(MotionEvent event) {
if (mWindow.shouldCloseOnTouch(this, event)) {
finish();
return true;
}
return false;
}
}
上面的代码来看,事件开始交给Activity所附属的Window进行分发,如果返回true,整个事件循环就结束了。返回false意味着事件没人处理,所有View的onTouchEvent都返回false,那么Activity的onTouchEvent就会被调用。
public abstract class Window {
....
public abstract boolean superDispatchTouchEvent(MotionEvent event);
....
}
public class PhoneWindow extends Window implements MenuBuilder.Callback {
....
private DecorView mDecor;
....
@Override
public final @NonNull View getDecorView() {
if (mDecor == null || mForceDecorInstall) {
installDecor();
}
return mDecor;
}
....
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
....
}
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
....
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
....
}
从上面的代码来看,主要是Window将事件传递给ViewGroup的过程。Window是个抽象类,而Window的superDispatchTouchEvent()方法也是个抽象方法。实际上是由PhoneWindow来具体实现的,而PhoneWindow将事件直接传递给了DecorView。由于DecorView继承自FrameLayout且是父View,所以最终事件传递给View。从这里开始,事件已经传递到顶级View了,即在Activity 中通过setContentView所设置的View,另外顶级View也叫根View,顶级View一般来说都是ViewGroup。
ViewGroup的事件分发过程
ViewGroup 对点击事件的分发过程,其主要实现在ViewGroup 的dispatchTouchEvent() 方法中。这个方法比较长,分段说明一下。
if (onFilterTouchEventForSecurity(ev)) {
....
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action);
} else {
intercepted = false;
}
} else {
intercepted = true;
}
}
上面这段代码主要是当前ViewGroup是否拦截点击事件这个逻辑。注释1: FLAG_DISALLOW_INTERCEPT标记位是通过requestDisallowInterceptTouchEvent 方法来设置的,一般用于子View中。FLAG_DISALLOW_INTERCEPT一旦设置后,ViewGroup将无法拦截除了ACTION_DOWN以外的其他点击事件。原因是,因为ViewGroup在分发事件时,如果是ACTION_DOWN就会重置FLAG_DISALLOW_INTERCEPT标记位。因此,当面对ACTION_DOWN事件时,ViewGroup总是会调用自己的onInterceptTouchEvent 方法来询问自己是否要拦截事件。
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
resetTouchState();
}
从上面的代码分析来看,当ViewGroup决定拦截事件后,后续的点击事件将默认交给ViewGroup 处理并且不再调用它的onInterceptTouchEvent方法。 注意:onInterceptTouchEvent不是每次事件都会被调用的,如果想提前处理所有的点击事件,要选择dispatchTouchEvent 方法,只有这个方法能确保每次调用。
当ViewGroup 不拦截事件的时候,事件会向下分发交由它的子View 进行处理,源码如下:
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
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);
if (!child.canReceivePointerEvents()
|| !isTransformedTouchPointInView(x, y, child, null)) {
continue;
}
ewTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
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 = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
ev.setTargetAccessibilityFocus(false);
}
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;
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;
}
}
return handled;
}
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
final int oldAction = event.getAction();
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;
}
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
} else {
handled = child.dispatchTouchEvent(transformedEvent);
}
transformedEvent.recycle();
return handled;
}
小结:
-
点击事件达到ViewGroup,会调用ViewGroup 的dispatchTouchEvent 方法。如果ViewGroup拦截事件(onInterceptTouchEvent)返回true,则事件由ViewGroup处理。 -
如果ViewGroup的mOnTouchListener被设置,则onTouch会被调用,否则onTouchEvent会被调用。也就是如果都设置的话,onTouch会屏蔽掉onTouchEvent。在onTouchEvent中,如果设置了mOnClickListener,则onClick会被调用。 -
如果ViewGroup不拦截事件,则事件会传递给它所在的点击事件链上的子View,这时子View的dispatchTouchEvent会被调用。到此为止,事件已经从ViewGroup传递到下一层View,接下来的传递过程和ViewGroup 是一致的,如此循环,完成整个事件的分发。
View 的事件分发过程
View(这里不包含ViewGroup)是一个单独元素,它没有子元素因此无法向下传递事件,所以它只能自己处理事件。
public boolean dispatchTouchEvent(MotionEvent event) {
....
boolean result = false;
....
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
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;
}
注释1:判断有没有设置OnTouchListener,如果OnTouchListener 中的onTouch方法返回true,那么onTouchEvent就不会调用;反之就不会调用。可见OnTouchListener的优先级高于onTouchEvent。
接着再分析onTouchEvent 的实现。
public boolean onTouchEvent(MotionEvent event) {
....
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
return clickable;
}
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
if ((viewFlags & TOOLTIP) == TOOLTIP) {
handleTooltipUp();
}
if (!clickable) {
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
break;
}
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (prepressed) {
setPressed(true, x, y);
}
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
removeLongPressCallback();
if (!focusTaken) {
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClickInternal();
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
mUnsetPressedState.run();
}
removeTapCallback();
}
mIgnoreNextUpEvent = false;
break;
case MotionEvent.ACTION_DOWN:
if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
}
mHasPerformedLongPress = false;
if (!clickable) {
checkForLongClick(
ViewConfiguration.getLongPressTimeout(),
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
break;
}
if (performButtonActionOnTouchDown(event)) {
break;
}
boolean isInScrollingContainer = isInScrollingContainer();
if (isInScrollingContainer) {
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
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)) {
removeLongPressCallback();
long delay = (long) (ViewConfiguration.getLongPressTimeout()
* mAmbiguousGestureMultiplier);
delay -= event.getEventTime() - event.getDownTime();
checkForLongClick(
delay,
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
}
touchSlop *= mAmbiguousGestureMultiplier;
}
if (!pointInView(x, y, touchSlop)) {
removeTapCallback();
removeLongPressCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
}
final boolean deepPress =
motionClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS;
if (deepPress && hasPendingLongPressCallback()) {
removeLongPressCallback();
checkForLongClick(
0 ,
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS);
}
break;
}
return true;
}
}
总体调用流程
Activity 层分析
Activity.dispatchTouchEvent()
在自己的Activity中重写了父类的dispatchTouchEvent(MotionEvent ev)方法,因此不会再调用getWindow().superDispatchTouchEvent(ev)方法了,即不会将事件传递到View层,同时也不会调用onTouchEvent(ev);该事件直接被消费了。后续再来的MOVE和UP事件也不会再向下分发,而是会直接经过DecorView.dispatchTouchEvent方法,通过回调交给Activity的dispatchTouchEvent,由于返回了true,会直接消费,也不会将事件交给onTouchEvent了。
直接消费了,不会将事件向下传了,也不会将事件交给onTouchEvent了
- 第三种情况:返回super.dispatchTouchEvent(ev)
如果调用默认的方法,即不对dispatchTouchEvent做任何处理,那么就会调用父类Activity的dispatchTouchEvent方法,将会调用getWindow().superDispatchTouchEvent(ev)方法,将event事件传递给decorView(ViewGroup)的dispatchTouchEvent,如果getWindow().superDispatchTouchEvent(ev)返回true,表示有子view消费了此事件,那么Activity的dispatchTouchEvent会返回true,事件到此结束,否则,会将事件传递给Activity的onTouchEvent,不论onTouchEvent返回值如何,事件分发都到此结束。
Activity.onTouchEvent()
直接消费了,终结了事件传递
直接消费了,终结了事件传递
ViewGroup 层分析
ViewGroup.dispatchTouchEvent()
在自定义的ViewGroup重写了dispatchTouchEvent,并返回true,就不会走父类中分发、拦截、消费的流程,直接告诉父容器自己处理了这个事件。
在自定义的ViewGroup重写了dispatchTouchEvent,并返回返回false,同样也不会走父类中分发、拦截、消费的流程,直接告诉父容器自己不处理这个事件,将事件交给父容器自己处理。
- 第三种情况:返回super.dispatchTouchEvent(ev)
在自定义的ViewGroup重写了dispatchTouchEvent,并返回super.dispatchTouchEvent,即会走父类默认的分发流程。
ViewGroup.onInterceptTouchEvent()
表示拦截事件,交给自己的onTouchEvent处理
表示不拦截事件,会将事件分发给子view
- 第三种情况:返回super.onInterceptTouchEvent(ev)
表示不拦截事件,会将事件分发给子view
ViewGroup.onTouchEvent()
在自定义ViewGroup重写onTouchEvent,并返回true,表示消费事件。
在自定义ViewGroup重写onTouchEvent,并返回false,表示不消费事件。事件交由父容器处理。
- 第三种情况:返回super.onTouchEvent(ev)
在自定义ViewGroup重写onTouchEvent,并返回super.onTouchEvent(ev),表示调用父类默认的事件处理方法。
View 层分析
View.dispatchTouchEvent()
在自定义View重写了dispatchTouchEvent,并返回true,就不会走分发,消费的流程,直接告诉父容器自己处理了这个事件。
在自定义View重写了dispatchTouchEvent,并返回false,直接告诉父容器自己不处理这个事件。
- 第三种情况:返回super.dispatchTouchEvent(ev)
在自定义View重写了dispatchTouchEvent,并返回super.dispatchTouchEvent,即会走父类View默认的分发流程。
View.onTouchEvent()
在自定义view重写onTouchEvent,并返回true,表示消费事件。
在自定义view重写onTouchEvent,并返回false,表示不消费事件。事件交由父容器处理。
- 第三种情况:返回super.onTouchEvent(ev)
在自定义view重写onTouchEvent,并返回super.onTouchEvent,表示调用父类默认的事件处理方法。
滑动冲突解决
常见的滑动冲突场景
- 外部滑动方向和内部滑动方向不一致
- 外部滑动方向和内部滑动方向一致
- 上面2种情况的嵌套
解决方法
内部拦截法
内部拦截法是指父容器不拦截任何事件,所有的事件都传递给子元素,如果子元素需要此事件就直接消耗掉,否则就交由父容器进行处理。这种方法需要配合requestDisallowInterceptTouchEvent 方法才能正常工作。
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN){
return false;
}
return true;
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
float x = ev.getX();
float y = ev.getY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
getParent().requestDisallowInterceptTouchEvent(true);
lastX = x;
lastY = y;
break;
case MotionEvent.ACTION_MOVE:
float moveX = lastX - x;
float moveY = lastY - y;
if (父容器需要当前的点击事件) {
getParent().requestDisallowInterceptTouchEvent(false);
}
lastX = ev.getX();
lastY = ev.getY();
break;
case MotionEvent.ACTION_UP:
break;
default:
break;
}
return super.dispatchTouchEvent(ev);
}
上面的代码是内部拦截法的典型逻辑,当面对不同的滑动策略只需要修改里面的条件即可
外部拦截法
外部拦截法指点击事件都先经过父容器的拦截处理,如果父容器需要此事件就拦截,如果不需要此事件就不拦截。这种方法需要重写父容器的onInterceptTouchEvent 方法,在内部做相应的拦截即可。
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
float x = ev.getX();
float y = ev.getY();
boolean intercept = false;
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
intercept = false;
break;
case MotionEvent.ACTION_MOVE:
if (父容器需要当前的点击事件){
intercept = true;
} else {
intercept = false;
}
break;
case MotionEvent.ACTION_UP:
intercept = false;
break;
default:
break;
}
return intercept;
}
上面的代码是外部拦截法的典型逻辑,针对不同的滑动冲突,只需要修改父容器需要当前的点击事件这个条件即可。在onInterceptTouchEvent方法中,首先ACTION_DOWN这个事件,父容器必须返回false,即不拦截ACTION_DOWN事件,这是因为一旦父容器拦截了ACTION_DOWN,那么后续的ACTION_MOVE和ACTION_UP事件都会交由父容器处理。这个时候事件没法再传递给子元素了;其次是ACTION_MOVE事件,这个事件可以根据需要来决定是否拦截,如果父容器需要拦截就返回true,否则返回false;最后是ACTION_UP事件,这里必须要返回false。
考虑一种情况,假设事件交由子元素处理,如果父容器在ACTION_UP时返回了true,就会导致子元素无法接收到ACTION_UP事件,这个时候子元素中的onClick事件就无法触发,一旦父容器开始拦截任何一个事件,那么后续的事件都会交给它处理。
结论
-
同一个事件序列是指从手指接触屏幕的那一刻起,到手指离开屏幕的那一刻结束,在这个过程中所产生的一系列事件,这个事件序列以down事件开始,中间含有数量不定的move事件,最终以up 事件结束。 -
某个ViewGroup一旦决定拦截,那么这一个事件序列都只能由它来处理(如果事件序列能够传递给它的话),并且它的onInterceptTouchEvent不会再被调用。就是说当一个ViewGroup决定拦截一个事件后,那么系统会把同一个事件序列内其他方法都直接交给它来处理,因此就不再调用这个ViewGroup的onInterceptTouchEvent 去询问他是否要拦截了。 -
某个View 一旦开始处理事件,如果它不消耗ACTION_DOWN事件(onTouchEvent返回了false),那么同一事件序列中的其他事件都不会再交给它来处理,并且事件将重新交由它的父元素去处理,即父元素的onTouchEvent会被调用。意思就是事件一旦交给一个View处理, 那么它就必须消耗掉,否则同一事件序列中剩下的事件就不再交给它来处理了。 -
如果View不消耗除ACTION_DOWN以外的其他事件,那么这个点击事件会消失,此时父元素的onTouchEvent并不会被调用,并且当前View可以持续收到后续的事件,最终这些消失的点击事件会传递给Activity处理。 -
ViewGroup 默认不拦截任何事件。Android 源码中ViewGroup的onInterceptTouchEvent 方法默认返回false。 -
View 没有onInterceptTouchEvent方法,一旦有点击事件传递给它,那么它的onTouchEvent方法就会被调用。 -
View 的onTouchEvent默认都会消耗事件(返回true),除非它是不可点击的(clickable和longClickable 同时为false)。 -
View的enable 属性不影响onTouchEvent的默认返回值。哪怕一个View是disable 状态的,只要它的clickable或longClickable有一个为true,那么它的onTouchEvent返回true。 -
onClick 会发生的前提是当前View是可点击的,并且它收到了down和up的事件。 -
事件传递过程是由外向内的,即事件总是先传递给父元素,然后再由父元素分发子view,通过requestDisallowInterceptTouchEvent方法可以在子元素中干预父元素的事件分发过程,但ACTION_DOWN事件除外。
参考资料
|