本篇文章我們專(zhuān)門(mén)來(lái)研究一下view層的事件分發(fā)機(jī)制帝火,我們?cè)趯W(xué)習(xí)過(guò)程中總會(huì)碰到關(guān)于事件分發(fā)的各種問(wèn)題烹困,如onTouch和onTouchEvent的關(guān)系嘲更,setOnTouchListener和setOnClickListener的關(guān)系等等揪漩,類(lèi)似這樣的問(wèn)題很多畜眨,結(jié)論我們都知道鸣驱,有的時(shí)候是死記硬背的泛鸟,記不長(zhǎng)久,本篇文章我們來(lái)從源碼的角度來(lái)分析總結(jié)一下各種關(guān)系踊东,這樣才能理解北滥,便于記憶。
分析工具
//Android源碼環(huán)境
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
接下來(lái)我們正式分析一下view層的事件分發(fā)的源碼闸翅。首先要知道一點(diǎn)再芋,對(duì)于view層次的,事件分發(fā)主要有兩個(gè)方法坚冀,dispatchTouchEve和onTouchEvent济赎,我們主要對(duì)這兩種方法進(jìn)行分析。
一记某、實(shí)例引入
我們先通過(guò)自定義一個(gè)button來(lái)進(jìn)行分析司训。自定義的button很簡(jiǎn)單,就是重寫(xiě)了一下dispatchTouchEve和onTouchEvent兩個(gè)方法液南。
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很簡(jiǎn)單壳猜,就是重寫(xiě)了view的兩個(gè)方法,我們?cè)谶@兩個(gè)方法中只進(jìn)行一些log操作滑凉,其他不改變统扳。接著我們?cè)赼ctivity中使用這個(gè)自定義的MyButton喘帚。
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中我們也處理了兩個(gè)方法咒钟,一個(gè)是setOnTouchListener吹由、一個(gè)是setOnClickListener,然后運(yùn)行一下朱嘴,我們可以看看log結(jié)果是什么倾鲫。
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
可以大概看出來(lái)事件響應(yīng)的順序是:
dispatchTouchEvent -> onTouch -> onTouchEvent -> onClick
從上面的log可以看出來(lái),onTouch是優(yōu)先于onClick執(zhí)行的腕够,并且onTouch執(zhí)行了多次级乍,一次是ACTION_DOWN舌劳,一次是ACTION_UP帚湘,還有幾次是ACTION_MOVE。因此事件傳遞的順序是先經(jīng)過(guò)onTouch甚淡,再傳遞到onClick大诸。
onTouch方法是有返回值的,如果我們嘗試把onTouch方法里的返回值改成true,再運(yùn)行一次就會(huì)發(fā)現(xiàn)onClick方法不再執(zhí)行了,這是因?yàn)閛nTouch方法返回true就認(rèn)為這個(gè)事件被onTouch消費(fèi)掉了贯卦,因而不會(huì)再繼續(xù)向下傳遞资柔。
這其中的緣由究竟是怎么樣的?我們通過(guò)源碼來(lái)一探究竟撵割。view事件分發(fā)的順序是從dispatchTouchEvent開(kāi)始的贿堰,所以我們就從它開(kāi)始分析:
二、源碼探究
首先我們進(jìn)入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;
}
我們省略了其中無(wú)關(guān)的代碼羹与,只看對(duì)分析有用的代碼,我們進(jìn)入到if中去庶灿,首先看到一個(gè)對(duì)象ListenerInfo的li對(duì)象指的是什么纵搁,
static class ListenerInfo {
protected OnFocusChangeListener mOnFocusChangeListener;
protected OnScrollChangeListener mOnScrollChangeListener;
public OnClickListener mOnClickListener;
protected OnLongClickListener mOnLongClickListener;
private OnKeyListener mOnKeyListener;
private OnTouchListener mOnTouchListener;
//...
}
看到?jīng)]有往踢,其實(shí)這個(gè)li指的就是我們?cè)O(shè)置的一些監(jiān)聽(tīng)器腾誉,包括onTouchListener、onClickListener等等峻呕,我們接著分析if中的條件
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
可以確認(rèn)這里面的li!=null利职,所以第一個(gè)條件為true,第二個(gè)條件我們因?yàn)樵O(shè)置了onTouchListener事件監(jiān)聽(tīng)瘦癌,所以這里面的li.mOnTouchListener != null也是為true猪贪,再看第三個(gè)條件(mViewFlags & ENABLED_MASK) == ENABLED,因?yàn)槲覀兊腷utton是可以點(diǎn)擊的佩憾,所以這里面也是為true哮伟,如果碰到不可點(diǎn)擊的干花,如ImageView,這里面就是false了楞黄,我們到時(shí)候另外再談池凄,我們接著看下面一句代碼。
li.mOnTouchListener.onTouch(this, event))
這句代碼說(shuō)明什么鬼廓?如果我們?cè)趕etOnTouchListener里面返回true的話肿仑,那么我們將直接返回result=true了,如果返回了false的話碎税,那么這個(gè)if條件就不成立尤慰,所以它將會(huì)執(zhí)行下一行代碼if語(yǔ)句端判斷-即它將會(huì)執(zhí)行onTouchEvent事件
if (!result && onTouchEvent(event)) {
result = true;
}
因?yàn)槲覀兌际窃O(shè)置的默認(rèn)返回值,所以在一開(kāi)始的時(shí)候我們的log日志顯示的順序是:
dispatchTouchEvent -> onTouch -> onTouchEvent -> onClick
這個(gè)時(shí)候就看看onTouch返回結(jié)果了雷蹂,返回的結(jié)果不同導(dǎo)致的順序也不同伟端。我們接著看看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;
}
我們?cè)趏nTouchEvent方法中查看一下责蝠,省略一些無(wú)關(guān)的代碼,我們發(fā)現(xiàn)了其中有一個(gè)方法就是在手指松開(kāi)的時(shí)候action=MotionEvent.ACTION_UP的時(shí)候萎庭,會(huì)調(diào)用這個(gè)performClick方法霜医。我們進(jìn)入performClick方法中繼續(xù)查看
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;
}
看到?jīng)]?這里面就涉及到了onClick事件了驳规,這也間接的證明了肴敛,onTouch的事件優(yōu)先級(jí)高于onClick的優(yōu)先級(jí)。
到了這里吗购,我們就可以總結(jié)一下關(guān)于一開(kāi)始提出來(lái)的幾個(gè)問(wèn)題:
1医男、onTouch和onTouchEvent有什么區(qū)別,又該如何使用巩搏?
從源碼中可以看出昨登,這兩個(gè)方法都是在View的dispatchTouchEvent中調(diào)用的,onTouch優(yōu)先于onTouchEvent執(zhí)行贯底。如果在onTouch方法中通過(guò)返回true將事件消費(fèi)掉丰辣,onTouchEvent將不會(huì)再執(zhí)行。
另外需要注意的是禽捆,onTouch能夠得到執(zhí)行需要兩個(gè)前提條件笙什,第一mOnTouchListener的值不能為空,第二當(dāng)前點(diǎn)擊的控件必須是enable的胚想。因此如果你有一個(gè)控件是非enable的琐凭,那么給它注冊(cè)onTouch事件將永遠(yuǎn)得不到執(zhí)行(&&操作符,如果前面的判斷為false的話浊服,后面就不判斷了)统屈。對(duì)于這一類(lèi)控件胚吁,如果我們想要監(jiān)聽(tīng)它的touch事件,就必須通過(guò)在該控件中重寫(xiě)onTouchEvent方法來(lái)實(shí)現(xiàn)愁憔。
2腕扶、onTouch和onClick優(yōu)先級(jí)
我們從源碼中也可以分析得到:onTouch的優(yōu)先級(jí)高于onClick的優(yōu)先級(jí),其中onClick的事件是在onTouchEvent中產(chǎn)生的吨掌。
判斷是否發(fā)生onTouchEvent事件的條件有三個(gè)半抱。(1)設(shè)置OnTouchListener監(jiān)聽(tīng),(2)該view是否是enable的膜宋,(3)在onTouch方法中返回true
如果上述三個(gè)條件有一個(gè)沒(méi)有滿足即為FALSE的話窿侈,那么它將執(zhí)行onTouchEvent事件同時(shí)將產(chǎn)生onClick事件。
3秋茫、touch事件的層級(jí)傳遞
我們都知道如果給一個(gè)控件注冊(cè)了touch事件史简,每次點(diǎn)擊它的時(shí)候都會(huì)觸發(fā)一系列的ACTION_DOWN,ACTION_MOVE学辱,ACTION_UP等事件乘瓤。這里需要注意环形,如果你在執(zhí)行ACTION_DOWN的時(shí)候返回了false策泣,后面一系列其它的action就不會(huì)再得到執(zhí)行了。簡(jiǎn)單的說(shuō)抬吟,就是當(dāng)dispatchTouchEvent在進(jìn)行事件分發(fā)的時(shí)候萨咕,只有前一個(gè)action返回true,才會(huì)觸發(fā)后一個(gè)action火本。
說(shuō)到這里危队,很多的朋友肯定要有巨大的疑問(wèn)了。這不是在自相矛盾嗎钙畔?前面的例子中茫陆,明明在onTouch事件里面返回了false,ACTION_DOWN和ACTION_UP不是都得到執(zhí)行了嗎擎析?其實(shí)你只是被假象所迷惑了簿盅,讓我們仔細(xì)分析一下,在前面的例子當(dāng)中揍魂,我們到底返回的是什么桨醋。參考著我們前面分析的源碼,首先在onTouch事件里返回了false现斋,就一定會(huì)進(jìn)入到onTouchEvent方法中喜最,然后我們來(lái)看一下onTouchEvent方法的細(xì)節(jié)。由于我們點(diǎn)擊了按鈕庄蹋,就會(huì)進(jìn)入到第14行這個(gè)if判斷的內(nèi)部瞬内,然后你會(huì)發(fā)現(xiàn)迷雪,不管當(dāng)前的action是什么,最終都一定會(huì)走到第89行虫蝶,返回一個(gè)true振乏。是不是有一種被欺騙的感覺(jué)?明明在onTouch事件里返回了false秉扑,系統(tǒng)還是在onTouchEvent方法中幫你返回了true慧邮。就因?yàn)檫@個(gè)原因,才使得前面的例子中ACTION_UP可以得到執(zhí)行舟陆。
那我們可以換一個(gè)控件误澳,將按鈕替換成ImageView,然后給它也注冊(cè)一個(gè)touch事件秦躯,并返回false忆谓。在ACTION_DOWN執(zhí)行完后,后面的一系列action都不會(huì)得到執(zhí)行了踱承。這又是為什么呢倡缠?因?yàn)镮mageView和按鈕不同,它是默認(rèn)不可點(diǎn)擊的茎活,因此在onTouchEvent的內(nèi)部判斷時(shí)無(wú)法進(jìn)入到if的內(nèi)部昙沦,直接跳到第最后面返回了false,也就導(dǎo)致后面其它的action都無(wú)法執(zhí)行了载荔。
三盾饮、總結(jié)
接下來(lái)我們來(lái)總結(jié)一下各個(gè)事件發(fā)生的流程。
針對(duì)于view來(lái)說(shuō)懒熙,當(dāng)發(fā)生一個(gè)事件時(shí)(譬如:onTouch事件)丘损,這個(gè)時(shí)候就會(huì)調(diào)用view的dispatchTouchEvent事件,它擁有boolean類(lèi)型的返回值工扎,當(dāng)返回為true時(shí)徘钥,順序下發(fā)會(huì)中斷,也就是說(shuō)肢娘,這個(gè)onTouch事件是不會(huì)繼續(xù)執(zhí)行下去了呈础,就執(zhí)行完一個(gè)dispatchTouchEvent事件,當(dāng)它返回false時(shí)事件繼續(xù)傳遞到onTouchListener中蔬浙,這個(gè)onTouchListener(onTouch事件)也是一個(gè)擁有boolean類(lèi)型的返回值的方法猪落,默認(rèn)返回false,這個(gè)時(shí)候就可以繼續(xù)執(zhí)行onClick(在onTouchEvent事件中)事件了畴博,如果onTouch事件返回了true笨忌,那么就代表這個(gè)事件被它自己給消耗掉了,不會(huì)再繼續(xù)傳遞俱病。
用一張圖來(lái)表示下:
對(duì)于View中的dispatchTouchEvent方法官疲,在這個(gè)方法內(nèi)袱结,首先是進(jìn)行了一個(gè)判斷,里面有三個(gè)條件途凫,如果這三個(gè)條件都滿足垢夹,就返回true,否則就返回onTouchEvent方法執(zhí)行的結(jié)果维费。對(duì)于第一個(gè)條件是一個(gè)mOnTouchListener變量果元,這個(gè)變量是在View中的setOnTouchListener方法里賦值的,也就是說(shuō)只要我們給控件注冊(cè)了touch事件犀盟,mOnTouchListener就一定被賦值了而晒。第二個(gè)條件是判斷當(dāng)前點(diǎn)擊的控件是否是enable的,按鈕默認(rèn)都是enable的阅畴,因此這個(gè)條件恒定為true倡怎。第三個(gè)條件最為關(guān)鍵,mOnTouchListener.onTouch(this, event)贱枣,其實(shí)也就是去回調(diào)控件注冊(cè)touch事件時(shí)的onTouch方法监署。也就是說(shuō)如果我們?cè)趏nTouch方法里返回true,就會(huì)讓這三個(gè)條件全部成立纽哥,從而整個(gè)方法直接返回true钠乏。如果我們?cè)趏nTouch方法里返回false,就會(huì)再去執(zhí)行onTouchEvent(event)方法昵仅。
到這里缓熟,整個(gè)view的事件分發(fā)就比較清楚了,接下來(lái)我們分析關(guān)于viewGroup的事件分發(fā)了摔笤。