1. 概述
前兩篇文章記錄了View事件分發(fā)的一些理論基礎(chǔ),這篇文章主要 從 View的 dispatchTouchEvent 源碼角度 分析下 View事件分發(fā)流程;
下邊通過(guò)一個(gè)示例代碼來(lái)分析
2. 示例如下
創(chuàng)建 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設(shè)置 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,一般可能有多個(gè)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
現(xiàn)象是:
如果 onTouch 返回true,表示消費(fèi)事件,就不會(huì)向下傳遞,就不會(huì)執(zhí)行 onClick,只會(huì)執(zhí)行自己的 down、move(多個(gè)move)、up事件;
如果 onTouch 返回false,執(zhí)行 down、move(多個(gè)move)、up事件,最后執(zhí)行 onClick;
下邊通過(guò) View的 dispatchTouchEvent 源碼 進(jìn)行分析 View的事件分發(fā);
3. dispatchTouchEvent源碼分析
前提知識(shí):
只要觸摸任何一個(gè)控件,就一定會(huì)調(diào)用該控件的 dispatchTouchEvent,如果該控件沒(méi)有,就一路向上查找,直到找到它父類的 dispatchTouchEvent方法然后調(diào)用,比如Button如下:

1>:首先看 View 的 dispatchTouchEvent方法如下:
public boolean dispatchTouchEvent(MotionEvent event) {
// 存放所有的 listener信息
ListenerInfo li = mListenerInfo;
// mOnTouchListener :只要設(shè)置 setOnTouchListener()之后,就不會(huì) null;
// mViewFlags & ENABLED_MASK:只要 該控件是可點(diǎn)擊的,這個(gè)就是 true;
// 主要是mOnTouchListener.onTouch(this, event),這個(gè)調(diào)用 的是 setOnTouchListener
// 的 onTouche() 方法,只要 onTouch() 返回 false,就會(huì)執(zhí)行下邊的 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個(gè) 條件 都為真,就 返回 true,否則 執(zhí)行 onTouchEvent(event)方法并返回;
第一個(gè)條件:mOnTouchListener != null:
public void setOnTouchListener(OnTouchListener l) {
mOnTouchListener = l;
}
可以看到 mOnTouchListener 在 setOnTouchListener() 方法中被賦值,也就是說(shuō) 只要給控件 設(shè)置 setOnTouchListener后, mOnTouchListener 就 會(huì)被賦值;
第二個(gè)條件:mViewFlags & ENABLED_MASK== ENABLED:判斷當(dāng)前點(diǎn)擊 控件是否是 enable的,Button 默認(rèn)是 enable的,ImageView 、TextView不是,所以這個(gè) 條件是 true;
第三個(gè)條件:mOnTouchListener.onTouch(this, event):
public interface OnTouchListener {
boolean onTouch(View v, MotionEvent event);
}
調(diào)用的就是 setOnTouchListener中的 onTouch() 方法,可以看到:
如果 onTouch 返回 true,那么 這3個(gè)條件 都為 true,從而整個(gè)方法返回 true;
如果 onTouch 返回 false,就 執(zhí)行下邊的 onTouchEvent() 方法;
從這3個(gè)條件可知:
前兩個(gè)條件肯定都為 true,所以 在 dispatchTouchEvent方法中最先執(zhí)行 setOnTouchListener的onTouch() 方法, 優(yōu)先級(jí)順序: onTouch > onClick;
如果 onTouch 返回 true,dispatchTouchEvent() 整個(gè)方法就 返回 true,不會(huì)往下執(zhí)行,onClick 就不會(huì)執(zhí)行;
onTouchEvent源碼如下:
public boolean onTouchEvent(MotionEvent event) {
// 此處省略一些代碼
......
// 從這里可知:
// 如果該 控件是可點(diǎn)擊的,比如 Button ,就會(huì)進(jìn)入 switch 判斷,在 Action_Up的 case 語(yǔ)句中,
// 在 經(jīng)過(guò)一系列判斷后,會(huì)進(jìn)入到 performClick()方法;
// 不管當(dāng)前 的 action 是什么,在 switch 語(yǔ)句外層,都會(huì) 返回 true,
// 如果該 控件 不是可點(diǎn)擊的,比如 ImageView、TextView,就不會(huì)進(jìn)入 if判斷,更不會(huì)進(jìn)入 switch 語(yǔ)句,
// 直接在 最外層的 if語(yǔ)句 返回 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,就會(huì) 調(diào)用 它的 onClick() 方法,mOnClickListener在 setOnClickListener中賦值的:
public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
也就是說(shuō) 只要調(diào)用 setOnClickListener時(shí),就會(huì)給 mOnClickListener 賦值,只要 Button被點(diǎn)擊,就會(huì)調(diào)用 performClick() 中的 onClick() 方法;
分析Button:
在 View的 dispatchTouchEvent()方法中,對(duì)于 那3個(gè)條件,第三個(gè)條件的 setOnTouchListener中的 onTouch()如果返回 false,此時(shí)進(jìn)入 onTouchEvent()方法,這個(gè)方法中,因?yàn)?Button 是可以點(diǎn)擊的,所以就會(huì) 進(jìn)入到 onTouchEvent()的 if (((viewFlags & CLICKABLE) == CLICKABLE () 語(yǔ)句,會(huì)發(fā)現(xiàn) 不管當(dāng)前 action 是什么,都會(huì)返回true,這個(gè)是系統(tǒng)幫我們返回的;
分析ImageView:
給 ImageView 設(shè)置 setOnTouchListener(),然后給 它的 onTouch()返回 false,此時(shí)進(jìn)入 onTouchEvent()方法,因?yàn)?ImageView 是不可點(diǎn)擊的,所以就不會(huì)進(jìn)入 onTouchEvent()的 if (((viewFlags & CLICKABLE) == CLICKABLE () 語(yǔ)句,直接 在 這個(gè) if() 語(yǔ)句 最外層就返回 false
4. 結(jié)論
1. touch事件層級(jí)傳遞,就是給控件設(shè)置 setOnTouchListener():
如果給 控件 設(shè)置 setOnTouchListener(),就會(huì) 觸發(fā)一系列的 down、move、up事件,如果 down 中返回false,后邊的 move、up等一系列事件均不會(huì)執(zhí)行,意思就是 要想觸發(fā)后邊的 某個(gè) move 或者 up事件執(zhí)行,前邊的 down事件就要返回true;
2. onTouch() 與 onTouchEvent() 區(qū)別,如何使用?:
這兩個(gè)方法 都是 在 View 的 dispatchTouchEvent() 方法中調(diào)用的 , 這里的 onTouch() 其實(shí)就是 dispatchTouchEvent() 中的 第三個(gè)條件;
優(yōu)先級(jí): setOnTouchListener 的onTouch > onTouchEvent();
如果 onTouch() 返回 true,表示消費(fèi)事件,那 3個(gè) 條件 都為 true,就不會(huì)執(zhí)行下邊的 onTouchEvent();
onTouch 要執(zhí)行的條件有2個(gè):
第一:mOnTouchListener不為null,意思就是 給 該 控件 設(shè)置了 setOnTouchListener();
第二:該控件要是 可點(diǎn)擊的,就是 enable的;
如果 點(diǎn)擊控件是 非enable的,setOnTouchListener的 onTouch()不會(huì)執(zhí)行,比如ImageView、TextView等, 對(duì)于 這類控件,如果想監(jiān)聽它的 onTouch事件,就 需要在 該控件中重寫 onTouchEvent方法 來(lái)實(shí)現(xiàn);
比如 ImageView,想監(jiān)聽它的 onTouch事件,有2種方式:
- onTouch方法返回 true;
- 在布局中給 ImageView 增加 android:clickable="true"的屬性;