View事件分发(四)|View事件分发(四) - View事件分发(源码分析)

1. 概述
前两篇文章记录了View事件分发的一些理论基础,这篇文章主要 从 View的 dispatchTouchEvent 源码角度 分析下 View事件分发流程;
下边通过一个示例代码来分析
2. 示例如下
创建 activity_main 布局


给Button设置 setOnClickListener、setOnTouchListener事件
public class TextViewActivity extends AppCompatActivity {private Button btn1; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_scroll); btn1 = (Button) findViewById(R.id.btn1); btn1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Log.e("TAG" , "onClick") ; } }); btn1.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { Log.e("TAG" , "onTouch , action: "+event.getAction()) ; return false; } }); }}

0代表down、1代表up,2代表move,一般可能有多个move:
onTouch返回true,log如下:
onTouch , action: 0 onTouch , action: 2 onTouch , action: 2 ... onTouch , action: 1

onTouch返回false,log如下:
onTouch , action: 0 onTouch , action: 2 onTouch , action: 2 ... onTouch , action: 1 onClick

现象是:
如果 onTouch 返回true,表示消费事件,就不会向下传递,就不会执行 onClick,只会执行自己的 down、move(多个move)、up事件;
如果 onTouch 返回false,执行 down、move(多个move)、up事件,最后执行 onClick;
下边通过 View的 dispatchTouchEvent 源码 进行分析 View的事件分发;
3. dispatchTouchEvent源码分析
前提知识: 只要触摸任何一个控件,就一定会调用该控件的 dispatchTouchEvent,如果该控件没有,就一路向上查找,直到找到它父类的 dispatchTouchEvent方法然后调用,比如Button如下:

View事件分发(四)|View事件分发(四) - View事件分发(源码分析)
文章图片
图片.png 1>:首先看 View 的 dispatchTouchEvent方法如下:
public boolean dispatchTouchEvent(MotionEvent event) { // 存放所有的 listener信息 ListenerInfo li = mListenerInfo; // mOnTouchListener :只要设置 setOnTouchListener()之后,就不会 null; // mViewFlags & ENABLED_MASK:只要 该控件是可点击的,这个就是 true; // 主要是mOnTouchListener.onTouch(this, event),这个调用 的是setOnTouchListener // 的 onTouche() 方法,只要 onTouch() 返回 false,就会执行下边的 onTouchEvent(event) if (li != null && mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && mOnTouchListener.onTouch(this, event)) { return true; } return onTouchEvent(event); }static class ListenerInfo { protected OnFocusChangeListener mOnFocusChangeListener; private ArrayList mOnLayoutChangeListeners; protected OnScrollChangeListener mOnScrollChangeListener; private CopyOnWriteArrayList mOnAttachStateChangeListeners; public OnClickListener mOnClickListener; protected OnLongClickListener mOnLongClickListener; protected OnContextClickListener mOnContextClickListener; protected OnCreateContextMenuListener mOnCreateContextMenuListener; private OnKeyListener mOnKeyListener; private OnTouchListener mOnTouchListener; private OnHoverListener mOnHoverListener; private OnGenericMotionListener mOnGenericMotionListener; private OnDragListener mOnDragListener; private OnSystemUiVisibilityChangeListener mOnSystemUiVisibilityChangeListener; OnApplyWindowInsetsListener mOnApplyWindowInsetsListener; }

从 dispatchTouchEvent 方法中可知:首先是if判断,如果 mOnTouchListener != null、mViewFlags & ENABLED_MASK== ENABLED、mOnTouchListener.onTouch(this, event) 这3个 条件 都为真,就 返回 true,否则 执行 onTouchEvent(event)方法并返回;
第一个条件:mOnTouchListener != null:
public void setOnTouchListener(OnTouchListener l) { mOnTouchListener = l; }

可以看到 mOnTouchListener 在 setOnTouchListener() 方法中被赋值,也就是说 只要给控件 设置 setOnTouchListener后, mOnTouchListener 就 会被赋值;
第二个条件:mViewFlags & ENABLED_MASK== ENABLED:判断当前点击 控件是否是 enable的,Button 默认是 enable的,ImageView 、TextView不是,所以这个 条件是 true;
第三个条件:mOnTouchListener.onTouch(this, event):
public interface OnTouchListener { boolean onTouch(View v, MotionEvent event); }

调用的就是 setOnTouchListener中的 onTouch() 方法,可以看到:
如果 onTouch 返回 true,那么 这3个条件 都为 true,从而整个方法返回 true;
如果 onTouch 返回 false,就 执行下边的 onTouchEvent() 方法;
从这3个条件可知:
前两个条件肯定都为 true,所以 在 dispatchTouchEvent方法中最先执行 setOnTouchListener的onTouch() 方法, 优先级顺序: onTouch > onClick;
如果 onTouch 返回 true,dispatchTouchEvent() 整个方法就 返回 true,不会往下执行,onClick 就不会执行;
onTouchEvent源码如下:
public boolean onTouchEvent(MotionEvent event) {// 此处省略一些代码 ......// 从这里可知: // 如果该 控件是可点击的,比如 Button ,就会进入 switch 判断,在 Action_Up的 case 语句中, // 在 经过一系列判断后,会进入到performClick()方法; // 不管当前 的 action 是什么,在 switch 语句外层,都会 返回 true,// 如果该 控件 不是可点击的,比如 ImageView、TextView,就不会进入 if判断,更不会进入 switch 语句, // 直接在最外层的if语句 返回 falseif (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {switch (action) { case MotionEvent.ACTION_UP:if (!focusTaken) { // 此处省略一些判断条件 if (!post(mPerformClick)) { performClick(); } } } break; case MotionEvent.ACTION_DOWN: // 此处省略 down 的一些代码 break; case MotionEvent.ACTION_CANCEL: // 此处省略 cancel 的一些代码 break; case MotionEvent.ACTION_MOVE: // 此处省略 move 的一些代码 break; } return true; } return false; }

performClick()源码如下:
public boolean performClick() { final boolean result; final ListenerInfo li = mListenerInfo; if (li != null && li.mOnClickListener != null) { li.mOnClickListener.onClick(this); result = true; } else { result = false; } sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); return result; }

如果 mOnClickListener 不为 null,就会 调用 它的 onClick() 方法,mOnClickListener在 setOnClickListener中赋值的:
public void setOnClickListener(@Nullable OnClickListener l) { if (!isClickable()) { setClickable(true); } getListenerInfo().mOnClickListener = l; }

也就是说 只要调用 setOnClickListener时,就会给 mOnClickListener 赋值,只要 Button被点击,就会调用 performClick() 中的 onClick() 方法;
分析Button:
在 View的 dispatchTouchEvent()方法中,对于 那3个条件,第三个条件的 setOnTouchListener中的 onTouch()如果返回 false,此时进入 onTouchEvent()方法,这个方法中,因为 Button 是可以点击的,所以就会 进入到 onTouchEvent()的 if (((viewFlags & CLICKABLE) == CLICKABLE () 语句,会发现 不管当前 action 是什么,都会返回true,这个是系统帮我们返回的;
分析ImageView:
给 ImageView 设置 setOnTouchListener(),然后给 它的 onTouch()返回 false,此时进入 onTouchEvent()方法,因为 ImageView 是不可点击的,所以就不会进入 onTouchEvent()的 if (((viewFlags & CLICKABLE) == CLICKABLE () 语句,直接 在 这个 if() 语句 最外层就返回 false
4. 结论
1. touch事件层级传递,就是给控件设置 setOnTouchListener():
如果给 控件 设置 setOnTouchListener(),就会 触发一系列的 down、move、up事件,如果 down 中返回false,后边的 move、up等一系列事件均不会执行,意思就是 要想触发后边的 某个 move 或者 up事件执行,前边的 down事件就要返回true;
2. onTouch() 与 onTouchEvent() 区别,如何使用?:
这两个方法 都是 在 View 的 dispatchTouchEvent() 方法中调用的 , 这里的 onTouch() 其实就是 dispatchTouchEvent() 中的 第三个条件;
优先级: setOnTouchListener 的onTouch > onTouchEvent();
如果 onTouch() 返回 true,表示消费事件,那 3个 条件 都为 true,就不会执行下边的 onTouchEvent();
onTouch 要执行的条件有2个:
第一:mOnTouchListener不为null,意思就是 给 该 控件 设置了 setOnTouchListener();
第二:该控件要是 可点击的,就是 enable的;
如果 点击控件是 非enable的,setOnTouchListener的 onTouch()不会执行,比如ImageView、TextView等, 对于 这类控件,如果想监听它的 onTouch事件,就 需要在 该控件中重写 onTouchEvent方法 来实现;
【View事件分发(四)|View事件分发(四) - View事件分发(源码分析)】比如 ImageView,想监听它的 onTouch事件,有2种方式:
  1. onTouch方法返回 true;
  2. 在布局中给 ImageView 增加 android:clickable="true"的属性;

    推荐阅读