本篇文章咱們專門來研究一下view層的事件分發機制,咱們在學習過程當中總會碰到關於事件分發的各類問題,如onTouch和onTouchEvent的關係,setOnTouchListener和setOnClickListener的關係等等,相似這樣的問題不少,結論咱們都知道,有的時候是死記硬背的,記不長久,本篇文章咱們來從源碼的角度來分析總結一下各類關係,這樣才能理解,便於記憶。javascript
//Android源碼環境
android {
compileSdkVersion 25
buildToolsVersion "25.0.2"
}
//分析工具
Android Studio 2.2.3
Build #AI-145.3537739, built on December 2, 2016
JRE: 1.8.0_112-release-b05 x86_64
JVM: OpenJDK 64-Bit Server VM by JetBrains s.r.o複製代碼
接下來咱們正式分析一下view層的事件分發的源碼。首先要知道一點,對於view層次的,事件分發主要有兩個方法,dispatchTouchEve和onTouchEvent,咱們主要對這兩種方法進行分析。html
咱們先經過自定義一個button來進行分析。自定義的button很簡單,就是重寫了一下dispatchTouchEve和onTouchEvent兩個方法。java
public class MyButton extends Button {
protected static final String TAG = "liji-view-test";
public MyButton(Context context, AttributeSet attrs)
{
super(context, attrs);
}
@Override
public boolean onTouchEvent(MotionEvent event)
{
int action = event.getAction();
switch (action)
{
case MotionEvent.ACTION_DOWN:
Log.d(TAG, "onTouchEvent ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.d(TAG, "onTouchEvent ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.d(TAG, "onTouchEvent ACTION_UP");
break;
default:
break;
}
return super.onTouchEvent(event);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event)
{
int action = event.getAction();
switch (action)
{
case MotionEvent.ACTION_DOWN:
Log.d(TAG, "dispatchTouchEvent ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.d(TAG, "dispatchTouchEvent ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.d(TAG, "dispatchTouchEvent ACTION_UP");
break;
default:
break;
}
return super.dispatchTouchEvent(event);
}
}複製代碼
自定義的MyButton很簡單,就是重寫了view的兩個方法,咱們在這兩個方法中只進行一些log操做,其餘不改變。接着咱們在activity中使用這個自定義的MyButton。android
mMyButton = (MyButton) findViewById(R.id.myButton);
mMyButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d(TAG,"onClick button click");
}
});
mMyButton.setOnTouchListener(new View.OnTouchListener()
{
@Override
public boolean onTouch(View v, MotionEvent event)
{
int action = event.getAction();
switch (action)
{
case MotionEvent.ACTION_DOWN:
Log.d(TAG, "onTouch ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.d(TAG, "onTouch ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.d(TAG, "onTouch ACTION_UP");
break;
default:
break;
}
return false;
}
});複製代碼
能夠看到,在activity中咱們也處理了兩個方法,一個是setOnTouchListener、一個是setOnClickListener,而後運行一下,咱們能夠看看log結果是什麼。git
D/liji-view-test: dispatchTouchEvent ACTION_DOWN
D/liji-view-test: onTouch ACTION_DOWN
D/liji-view-test: onTouchEvent ACTION_DOWN
D/liji-view-test: dispatchTouchEvent ACTION_MOVE
D/liji-view-test: onTouch ACTION_MOVE
D/liji-view-test: onTouchEvent ACTION_MOVE
D/liji-view-test: dispatchTouchEvent ACTION_MOVE
D/liji-view-test: onTouch ACTION_MOVE
D/liji-view-test: onTouchEvent ACTION_MOVE
D/liji-view-test: dispatchTouchEvent ACTION_MOVE
D/liji-view-test: onTouch ACTION_MOVE
D/liji-view-test: onTouchEvent ACTION_MOVE
D/liji-view-test: dispatchTouchEvent ACTION_MOVE
D/liji-view-test: onTouch ACTION_MOVE
D/liji-view-test: onTouchEvent ACTION_MOVE
D/liji-view-test: dispatchTouchEvent ACTION_UP
D/liji-view-test: onTouch ACTION_UP
D/liji-view-test: onTouchEvent ACTION_UP
D/liji-view-test: onClick button click複製代碼
能夠大概看出來事件響應的順序是:github
dispatchTouchEvent -> onTouch -> onTouchEvent -> onClick複製代碼
從上面的log能夠看出來,onTouch是優先於onClick執行的,而且onTouch執行了屢次,一次是ACTION_DOWN,一次是ACTION_UP,還有幾回是ACTION_MOVE。所以事件傳遞的順序是先通過onTouch,再傳遞到onClick。ide
onTouch方法是有返回值的,若是咱們嘗試把onTouch方法裏的返回值改爲true,再運行一次就會發現onClick方法再也不執行了,這是由於onTouch方法返回true就認爲這個事件被onTouch消費掉了,於是不會再繼續向下傳遞。工具
這其中的原因到底是怎麼樣的?咱們經過源碼來一探究竟。view事件分發的順序是從dispatchTouchEvent開始的,因此咱們就從它開始分析:post
首先咱們進入view的dispatchTouchEvent方法中查看。學習
//view.java
public boolean dispatchTouchEvent(MotionEvent event) {
//...
boolean result = false;
//...
if (onFilterTouchEventForSecurity(event)) {
//...
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;
}複製代碼
咱們省略了其中無關的代碼,只看對分析有用的代碼,咱們進入到if中去,首先看到一個對象ListenerInfo的li對象指的是什麼,
static class ListenerInfo {
protected OnFocusChangeListener mOnFocusChangeListener;
protected OnScrollChangeListener mOnScrollChangeListener;
public OnClickListener mOnClickListener;
protected OnLongClickListener mOnLongClickListener;
private OnKeyListener mOnKeyListener;
private OnTouchListener mOnTouchListener;
//...
}複製代碼
看到沒有,其實這個li指的就是咱們設置的一些監聽器,包括onTouchListener、onClickListener等等,咱們接着分析if中的條件
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}複製代碼
能夠確認這裏面的li!=null,因此第一個條件爲true,第二個條件咱們由於設置了onTouchListener事件監聽,因此這裏面的li.mOnTouchListener != null也是爲true,再看第三個條件(mViewFlags & ENABLED_MASK) == ENABLED,由於咱們的button是能夠點擊的,因此這裏面也是爲true,若是碰到不可點擊的,如ImageView,這裏面就是false了,咱們到時候另外再談,咱們接着看下面一句代碼。
li.mOnTouchListener.onTouch(this, event))複製代碼
這句代碼說明什麼?若是咱們在setOnTouchListener裏面返回true的話,那麼咱們將直接返回result=true了,若是返回了false的話,那麼這個if條件就不成立,因此它將會執行下一行代碼if語句端判斷-即它將會執行onTouchEvent事件
if (!result && onTouchEvent(event)) {
result = true;
}複製代碼
由於咱們都是設置的默認返回值,因此在一開始的時候咱們的log日誌顯示的順序是:
dispatchTouchEvent -> onTouch -> onTouchEvent -> onClick複製代碼
這個時候就看看onTouch返回結果了,返回的結果不一樣致使的順序也不一樣。咱們接着看看onTouchEvent的源碼,分析一下里面藏了什麼東西。
public boolean onTouchEvent(MotionEvent event) {
//...
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
(viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
switch (action) {
case MotionEvent.ACTION_UP:
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
boolean focusTaken = false;
//...
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
if (!focusTaken) {
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClick();
}
}
}
//...
break;
case MotionEvent.ACTION_DOWN:
//...
break;
case MotionEvent.ACTION_CANCEL:
//...
break;
case MotionEvent.ACTION_MOVE:
//...
break;
}
return true;
}
return false;
}複製代碼
咱們在onTouchEvent方法中查看一下,省略一些無關的代碼,咱們發現了其中有一個方法就是在手指鬆開的時候action=MotionEvent.ACTION_UP的時候,會調用這個performClick方法。咱們進入performClick方法中繼續查看
public boolean performClick() {
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);
return result;
}複製代碼
看到沒?這裏面就涉及到了onClick事件了,這也間接的證實了,onTouch的事件優先級高於onClick的優先級。
到了這裏,咱們就能夠總結一下關於一開始提出來的幾個問題:
一、onTouch和onTouchEvent有什麼區別,又該如何使用?
從源碼中能夠看出,這兩個方法都是在View的dispatchTouchEvent中調用的,onTouch優先於onTouchEvent執行。若是在onTouch方法中經過返回true將事件消費掉,onTouchEvent將不會再執行。
另外須要注意的是,onTouch可以獲得執行須要兩個前提條件,第一mOnTouchListener的值不能爲空,第二當前點擊的控件必須是enable的。所以若是你有一個控件是非enable的,那麼給它註冊onTouch事件將永遠得不到執行(&&操做符,若是前面的判斷爲false的話,後面就不判斷了)。對於這一類控件,若是咱們想要監聽它的touch事件,就必須經過在該控件中重寫onTouchEvent方法來實現。
二、onTouch和onClick優先級
咱們從源碼中也能夠分析獲得:onTouch的優先級高於onClick的優先級,其中onClick的事件是在onTouchEvent中產生的。
判斷是否發生onTouchEvent事件的條件有三個。(1)設置OnTouchListener監聽,(2)該view是不是enable的,(3)在onTouch方法中返回true
若是上述三個條件有一個沒有知足即爲FALSE的話,那麼它將執行onTouchEvent事件同時將產生onClick事件。
三、touch事件的層級傳遞
咱們都知道若是給一個控件註冊了touch事件,每次點擊它的時候都會觸發一系列的ACTION_DOWN,ACTION_MOVE,ACTION_UP等事件。這裏須要注意,若是你在執行ACTION_DOWN的時候返回了false,後面一系列其它的action就不會再獲得執行了。簡單的說,就是當dispatchTouchEvent在進行事件分發的時候,只有前一個action返回true,纔會觸發後一個action。
說到這裏,不少的朋友確定要有巨大的疑問了。這不是在自相矛盾嗎?前面的例子中,明明在onTouch事件裏面返回了false,ACTION_DOWN和ACTION_UP不是都獲得執行了嗎?其實你只是被假象所迷惑了,讓咱們仔細分析一下,在前面的例子當中,咱們到底返回的是什麼。參考着咱們前面分析的源碼,首先在onTouch事件裏返回了false,就必定會進入到onTouchEvent方法中,而後咱們來看一下onTouchEvent方法的細節。因爲咱們點擊了按鈕,就會進入到第14行這個if判斷的內部,而後你會發現,無論當前的action是什麼,最終都必定會走到第89行,返回一個true。是否是有一種被欺騙的感受?明明在onTouch事件裏返回了false,系統仍是在onTouchEvent方法中幫你返回了true。就由於這個緣由,才使得前面的例子中ACTION_UP能夠獲得執行。
那咱們能夠換一個控件,將按鈕替換成ImageView,而後給它也註冊一個touch事件,並返回false。在ACTION_DOWN執行完後,後面的一系列action都不會獲得執行了。這又是爲何呢?由於ImageView和按鈕不一樣,它是默認不可點擊的,所以在onTouchEvent的內部判斷時沒法進入到if的內部,直接跳到第最後面返回了false,也就致使後面其它的action都沒法執行了。
接下來咱們來總結一下各個事件發生的流程。
針對於view來講,當發生一個事件時(譬如:onTouch事件),這個時候就會調用view的dispatchTouchEvent事件,它擁有boolean類型的返回值,當返回爲true時,順序下發會中斷,也就是說,這個onTouch事件是不會繼續執行下去了,就執行完一個dispatchTouchEvent事件,當它返回false時事件繼續傳遞到onTouchListener中,這個onTouchListener(onTouch事件)也是一個擁有boolean類型的返回值的方法,默認返回false,這個時候就能夠繼續執行onClick(在onTouchEvent事件中)事件了,若是onTouch事件返回了true,那麼就表明這個事件被它本身給消耗掉了,不會再繼續傳遞。
用一張圖來表示下:
對於View中的dispatchTouchEvent方法,在這個方法內,首先是進行了一個判斷,裏面有三個條件,若是這三個條件都知足,就返回true,不然就返回onTouchEvent方法執行的結果。對於第一個條件是一個mOnTouchListener變量,這個變量是在View中的setOnTouchListener方法裏賦值的,也就是說只要咱們給控件註冊了touch事件,mOnTouchListener就必定被賦值了。第二個條件是判斷當前點擊的控件是不是enable的,按鈕默認都是enable的,所以這個條件恆定爲true。第三個條件最爲關鍵,mOnTouchListener.onTouch(this, event),其實也就是去回調控件註冊touch事件時的onTouch方法。也就是說若是咱們在onTouch方法裏返回true,就會讓這三個條件所有成立,從而整個方法直接返回true。若是咱們在onTouch方法裏返回false,就會再去執行onTouchEvent(event)方法。
到這裏,整個view的事件分發就比較清楚了,接下來咱們分析關於viewGroup的事件分發了。
github: github.com/crazyandcod…
博客: crazyandcoder.github.io
一、http://www.cnblogs.com/linjzong/p/4191891.html
二、http://3y.uu456.com/bp_5728a9pduw6m3qo9y5s6_1.html