首先Android的事件分发是基于责任链设计模式的 如果不理解责任链设计 可以参考:
https://blog.csdn.net/u011109881/article/details/59631314
提示:本文结合自己的代码实践更有助于理解
一 几个典型案例
public class MyView extends View {
private static final String TAG = "MyView";
public MyView(Context context) {
this(context, null);
}
public MyView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Log.d(TAG, "onClick: ");
}
});
this.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.d(TAG, "onTouch: " + event.getAction());
return false;
}
});
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d(TAG, "onTouchEvent: " + event.getAction());
return super.onTouchEvent(event);
}
}
此时打印log如下
12-19 15:09:35.292 8259 8259 D MyView : onTouch: 0
12-19 15:09:35.292 8259 8259 D MyView : onTouchEvent: 0
12-19 15:09:35.341 8259 8259 D MyView : onTouch: 2
12-19 15:09:35.341 8259 8259 D MyView : onTouchEvent: 2
12-19 15:09:35.359 8259 8259 D MyView : onTouch: 2
12-19 15:09:35.359 8259 8259 D MyView : onTouchEvent: 2
12-19 15:09:35.374 8259 8259 D MyView : onTouch: 2
12-19 15:09:35.374 8259 8259 D MyView : onTouchEvent: 2
12-19 15:09:35.391 8259 8259 D MyView : onTouch: 2
12-19 15:09:35.391 8259 8259 D MyView : onTouchEvent: 2
12-19 15:09:35.413 8259 8259 D MyView : onTouch: 2
12-19 15:09:35.413 8259 8259 D MyView : onTouchEvent: 2
12-19 15:09:35.459 8259 8259 D MyView : onTouch: 2
12-19 15:09:35.459 8259 8259 D MyView : onTouchEvent: 2
12-19 15:09:35.459 8259 8259 D MyView : onTouch: 1
12-19 15:09:35.459 8259 8259 D MyView : onTouchEvent: 1
12-19 15:09:35.459 8259 8259 D MyView : onClick:
按照责任链的思路 似乎onTouch的出在责任链发起端 onTouchEvent接着 最后是 onClick事件
二 分析原因 View的事件分发源码分析
二.一 OnTouchListener中的onTouch返回值可以决定onTouchEvent是否可以走到
//事件分发的起点
public boolean dispatchTouchEvent(MotionEvent event) {
...
//是否消费 false意味着不消费 事件继续向责任链后端传递
// true意味着消费事件(吃掉事件) 事件无法向责任链后端传递
boolean result = false;
...
if (actionMasked == MotionEvent.ACTION_DOWN) {//手势为下落的时候 停掉嵌套view的滚动
// Defensive cleanup for new gesture
stopNestedScroll();
}
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)) {
//要执行onTouch 需要满足条件
//1 li != null 2 li.mOnTouchListener != null 3 (mViewFlags & ENABLED_MASK) == ENABLED
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
}
if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
// Clean up after nested scrolls if this is the end of a gesture;
// also cancel it if we tried an ACTION_DOWN but we didn't want the rest
// of the gesture.
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}
return result;
}
根据我们之前的分析 来到责任链的最前端 line22的onTouch事件
1 li != null
2 li.mOnTouchListener != null
3 (mViewFlags & ENABLED_MASK) == ENABLED
要执行这个代码 需要三个条件
为此我们要先看看li的类型ListenerInfo究竟是个啥
static class ListenerInfo {
/**
* Listener used to dispatch focus change events.
* This field should be made private, so it is hidden from the SDK.
* {@hide}
*/
@UnsupportedAppUsage
protected OnFocusChangeListener mOnFocusChangeListener;
/**
* Listeners for layout change events.
*/
private ArrayList<OnLayoutChangeListener> mOnLayoutChangeListeners;
protected OnScrollChangeListener mOnScrollChangeListener;
/**
* Listeners for attach events.
*/
private CopyOnWriteArrayList<OnAttachStateChangeListener> mOnAttachStateChangeListeners;
/**
* Listener used to dispatch click events.
* This field should be made private, so it is hidden from the SDK.
* {@hide}
*/
@UnsupportedAppUsage
public OnClickListener mOnClickListener;
/**
* Listener used to dispatch long click events.
* This field should be made private, so it is hidden from the SDK.
* {@hide}
*/
@UnsupportedAppUsage
protected OnLongClickListener mOnLongClickListener;
/**
* Listener used to dispatch context click events. This field should be made private, so it
* is hidden from the SDK.
* {@hide}
*/
protected OnContextClickListener mOnContextClickListener;
/**
* Listener used to build the context menu.
* This field should be made private, so it is hidden from the SDK.
* {@hide}
*/
@UnsupportedAppUsage
protected OnCreateContextMenuListener mOnCreateContextMenuListener;
@UnsupportedAppUsage
private OnKeyListener mOnKeyListener;
@UnsupportedAppUsage
private OnTouchListener mOnTouchListener;
@UnsupportedAppUsage
private OnHoverListener mOnHoverListener;
@UnsupportedAppUsage
private OnGenericMotionListener mOnGenericMotionListener;
@UnsupportedAppUsage
private OnDragListener mOnDragListener;
private OnSystemUiVisibilityChangeListener mOnSystemUiVisibilityChangeListener;
OnApplyWindowInsetsListener mOnApplyWindowInsetsListener;
OnCapturedPointerListener mOnCapturedPointerListener;
private ArrayList<OnUnhandledKeyEventListener> mUnhandledKeyListeners;
private WindowInsetsAnimationListener mWindowInsetsAnimationListener;
/**
* This lives here since it's only valid for interactive views.
*/
private List<Rect> mSystemGestureExclusionRects;
/**
* Used to track {@link #mSystemGestureExclusionRects}
*/
public RenderNode.PositionUpdateListener mPositionUpdateListener;
}
我们看到ListenerInfo里面有我们熟悉的OnClickListener以及mOnTouchListener 那么我们在代码里调用setOnClickListener setOnTouchListener源码做了什么呢
public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
public void setOnTouchListener(OnTouchListener l) {
getListenerInfo().mOnTouchListener = l;
}
@UnsupportedAppUsage
ListenerInfo getListenerInfo() {
if (mListenerInfo != null) {
return mListenerInfo;
}
mListenerInfo = new ListenerInfo();
return mListenerInfo;
}
哦吼,发现了ListenerInfo是一系列Listener的集合,在我们调用setOnTouchListener的时候有两个条件已经满足
1 li != null 2 li.mOnTouchListener != null 那么剩下的条件3 (mViewFlags & ENABLED_MASK) == ENABLED如何判断呢
(mViewFlags & ENABLED_MASK) == ENABLED
是判断当前点击的控件是否是enable的,默认都是enable的(这点我是听视频里面说的 有个实验可以验证这一点 就是在代码里面调用View的setEnabled(false)方法 发现即使注册了OnTouchListener 里面的onTouch也不会执行了)
说到这里前面三个条件都成立了 那么li.mOnTouchListener.onTouch(this, event)开始执行,这就调用到我们代码里面的onTouch代码块了,而他的返回值决定了result(是否消费/吃掉事件),也就是事件是否继续执行,如果我们在onTouch返回了false(不吃掉事件)result维持默认值false,那么事件继续传递 line28 if (!result && onTouchEvent(event))得以继续执行,否则result=true onTouch无法继续执行(可以实验在OnTouchListener 里面的onTouch返回true看看)
二.二onTouchEvent中的返回值可以决定onClick是否可以走到
同样是基于之前责任链设计思路 onClick必定在onTouchEvent之后调用,那么是在onTouchEvent内部呢还是在onTouchEvent调用之后呢?我们查看onTouchEvent的内部代码可以发现答案是前者
public boolean onTouchEvent(MotionEvent event) {
...
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
...
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:
...
break;
case MotionEvent.ACTION_CANCEL:
...
break;
case MotionEvent.ACTION_MOVE:
...
break;
}
return true;
}
return false;
}
private final class PerformClick implements Runnable {
@Override
public void run() {
recordGestureClassification(TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP);
performClickInternal();
}
}
private boolean performClickInternal() {
// Must notify autofill manager before performing the click actions to avoid scenarios where
// the app has a click listener that changes the state of views the autofill service might
// be interested on.
notifyAutofillManagerOnClick();
return performClick();
}
public boolean performClick() {
// We still need to call this method to handle the cases where performClick() was called
// externally, instead of through performClickInternal()
notifyAutofillManagerOnClick();
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);//最终目的地
result = true;
} else {
result = false;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
notifyEnterOrExitForAutoFillIfNeeded(true);
return result;
}
可以看到在MotionEvent.ACTION_UP会调用PerformClick方法 其中会performClickInternal,最后调用的performClick中会调用到onClick事件
因此总结下来 View的事件分发的顺序是这样的
dispatchTouchEvent->OnTouchListener onTouch DOWN事件->onTouchEvent DOWN事件
->OnTouchListener onTouchMOVE事件->onTouchEvent MOVE事件
->OnTouchListener onTouchUP事件->onTouchEventUP事件
->OnClickListener onClick
其中任意一个环节 事件被吃掉(返回true)则后续的链子都断掉(收不到后续事件),不过这仅限于后面的层级,(不是直接把链条中间断开,后面的事件都接收不到的意思)
换一种说法,我们可以可以将OnTouchListener onTouchEvent OnClickListener 想象成3层不透光的纸,上面有个开关,return false可以打开开关 让光投到下一层,在上面的一系列事件中,比如我们 OnTouchListener onTouch MOVE的时候关闭了开关,那么后面两层的事件都无法接受到后续事件,但是注意,OnTouchListener onTouchUP事件还是可以接收到的,开关只影响后面的层级。说到这个开关 其实只有OnTouchListener onTouchEvent 有而已,活用这个开关,可以让我们在事件中只接受特定的事件。
https://blog.csdn.net/fengluoye2012/article/details/83782042
这里有个参考链接 里面的图片很适合理解事件分发