1. 概述
前两篇文章记录了View事件分发的一些理论基础,这篇文章主要 从 View的 dispatchTouchEvent 源码角度 分析下 View事件分发流程;
下边通过一个示例代码来分析
2. 示例如下
创建 activity_main 布局
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/btn1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button"
android:layout_centerInParent="true"
/>
</RelativeLayout>
给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如下:
图片.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<OnLayoutChangeListener> mOnLayoutChangeListeners;
protected OnScrollChangeListener mOnScrollChangeListener;
private CopyOnWriteArrayList<OnAttachStateChangeListener> 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语句 返回 false
if (((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方法 来实现;
比如 ImageView,想监听它的 onTouch事件,有2种方式:
- onTouch方法返回 true;
- 在布局中给 ImageView 增加 android:clickable="true"的属性;
网友评论