Android-你必須要懂得事件分發(fā)全解析

一题翻、 事件分發(fā)的對(duì)象是誰(shuí)排宰?

答:事件

當(dāng)用戶觸摸屏幕時(shí)(View或ViewGroup派生的控件),將產(chǎn)生點(diǎn)擊事件(Touch事件)线脚。
Touch事件相關(guān)細(xì)節(jié)(發(fā)生觸摸的位置策菜、時(shí)間、歷史記錄酒贬、手勢(shì)動(dòng)作等)被封裝成MotionEvent對(duì)象
主要發(fā)生的Touch事件有如下四種:

  • MotionEvent.ACTION_DOWN:按下View(所有事件的開(kāi)始)(只會(huì)產(chǎn)生一次)
  • MotionEvent.ACTION_MOVE:滑動(dòng)View
  • MotionEvent.ACTION_CANCEL:非人為原因結(jié)束本次事件
  • MotionEvent.ACTION_UP:抬起View(與DOWN對(duì)應(yīng))(只會(huì)產(chǎn)生一次)

事件列:從手指接觸屏幕至手指離開(kāi)屏幕又憨,這個(gè)過(guò)程產(chǎn)生的一系列事件 任何事件列都是以DOWN事件開(kāi)始,UP事件結(jié)束锭吨,中間有無(wú)數(shù)的MOVE事件蠢莺,如下圖:

事件執(zhí)行順序.png

二、事件分發(fā)的本質(zhì)

答:將點(diǎn)擊事件(MotionEvent)向某個(gè)View進(jìn)行傳遞并最終得到處理

即當(dāng)一個(gè)點(diǎn)擊事件發(fā)生后零如,系統(tǒng)需要將這個(gè)事件傳遞給一個(gè)具體的View去處理躏将。這個(gè)事件傳遞的過(guò)程就是分發(fā)過(guò)程。

三考蕾、事件在哪些對(duì)象之間進(jìn)行傳遞祸憋?

答:Activity、ViewGroup肖卧、View

一個(gè)點(diǎn)擊事件產(chǎn)生后蚯窥,傳遞順序是:Activity(Window) -> ViewGroup -> View
Android的UI界面是由Activity、ViewGroup塞帐、View及其派生類組合而成的

image.png

View是所有UI組件的基類

一般Button拦赠、ImageView、TextView等控件都是繼承父類View

ViewGroup是容納UI組件的容器葵姥,即一組View的集合(包含很多子View和子VewGroup)荷鼠,

其本身也是從View派生的,即ViewGroup是View的子類
是Android所有布局的父類或間接父類:項(xiàng)目用到的布局(LinearLayout榔幸、RelativeLayout等)允乐,都繼承自ViewGroup矮嫉,即屬于ViewGroup子類。

與普通View的區(qū)別:ViewGroup實(shí)際上也是一個(gè)View牍疏,只不過(guò)比起View敞临,它多了可以包含子View和定義布局參數(shù)的功能。

四麸澜、 事件分發(fā)過(guò)程由哪些方法協(xié)作完成?

答:dispatchTouchEvent() 奏黑、onInterceptTouchEvent()和onTouchEvent()

image.png

五炊邦、 總結(jié)

Android事件分發(fā)機(jī)制的本質(zhì)是要解決:

點(diǎn)擊事件由哪個(gè)對(duì)象發(fā)出,經(jīng)過(guò)哪些對(duì)象熟史,最終達(dá)到哪個(gè)對(duì)象并最終得到處理馁害。

這里的對(duì)象是指Activity、ViewGroup蹂匹、View
Android中事件分發(fā)順序:Activity(Window) -> ViewGroup -> View

事件分發(fā)過(guò)程由dispatchTouchEvent() 碘菜、onInterceptTouchEvent()和onTouchEvent()三個(gè)方法協(xié)助完成

經(jīng)過(guò)上述3個(gè)問(wèn)題,相信大家已經(jīng)對(duì)Android的事件分發(fā)有了感性的認(rèn)知限寞,接下來(lái)忍啸,我將詳細(xì)介紹Android事件分發(fā)機(jī)制。

六履植、事件分發(fā)機(jī)制方法介紹

Android事件分發(fā)流程如下:(必須熟記)
Android事件分發(fā)順序:Activity(Window) -> ViewGroup -> View

事件分發(fā)順序.png

super:調(diào)用父類方法(在這里Activity的父類是ViewGroup,ViewGroup的父類是View)
true:消費(fèi)事件计雌,即事件不繼續(xù)往下傳遞
false:不消費(fèi)事件,事件也不繼續(xù)往下傳遞 / 交由給父控件onTouchEvent()處理

看圖總結(jié):首先當(dāng)你觸摸的屏幕的時(shí)候會(huì)一口氣產(chǎn)生至少2個(gè)事件玫霎,一個(gè)down凿滤,一個(gè)up,其余就是你手指移動(dòng)過(guò)程中產(chǎn)生的move事件庶近。首先down事件會(huì)先執(zhí)行activity的dispatchTouchEvent方法翁脆,不管它返回true或者false都會(huì)直接在這里被消費(fèi),不會(huì)繼續(xù)往下傳遞鼻种。只有調(diào)用父類方法的時(shí)候會(huì)執(zhí)行ViewGroup的dispatchTouchEvent方法反番,此時(shí)假如dispatchTouchEvent返回true也代表在此消費(fèi),不會(huì)繼續(xù)傳遞叉钥,假如返回false恬口,表示不分發(fā),則交還給Activity,說(shuō)大家都處理不了沼侣,你自己處理吧祖能。假如dispatchTouchEvent返回父類,則會(huì)調(diào)用ViewGroup的onInterceptTouchEvent蛾洛,返回true表示攔截這個(gè)事件养铸,交給自己的onTouchEvent方法消費(fèi)事件雁芙,假如返回false,表示不攔截,則交給View的dispatchTouchEvent方法钞螟,同樣的做處理兔甘,之后交給View的onTouchEvent方法,返回true代表消費(fèi)鳞滨,返回false洞焙,表示無(wú)法處理,交還給上級(jí)拯啦。

其實(shí)澡匪,這些不需要硬記,實(shí)在開(kāi)發(fā)中遇到不知道哪里返回true褒链,哪里返回false時(shí)唁情,回來(lái)看看這個(gè)順序。最主要的是多去用用就記住了甫匹。下面我們看一般的事件傳遞甸鸟。

七、一般事件傳遞

image.png

7.1 默認(rèn)情況

即不對(duì)控件里的方法(dispatchTouchEvent()兵迅、onTouchEvent()抢韭、onInterceptTouchEvent())進(jìn)行重寫或更改返回值
那么調(diào)用的是這3個(gè)方法的默認(rèn)實(shí)現(xiàn):調(diào)用父類的方法
事件傳遞情況:

從Activity A---->ViewGroup B--->View C,從上往下調(diào)用dispatchTouchEvent()
再由View C--->ViewGroup B --->Activity A恍箭,從下往上調(diào)用onTouchEvent()

7.2處理事件

假設(shè)View C希望處理這個(gè)點(diǎn)擊事件篮绰,即C被設(shè)置成可點(diǎn)擊的(Clickable)或者覆寫了C的onTouchEvent方法返回true。

事件傳遞情況:

DOWN事件被傳遞給C的onTouchEvent方法季惯,該方法返回true吠各,表示處理這個(gè)事件
因?yàn)镃正在處理這個(gè)事件,那么DOWN事件將不再往上傳遞給B和A的onTouchEvent()勉抓;
該事件列的其他事件(Move贾漏、Up)也將傳遞給C的onTouchEvent()

7.3 攔截DOWN事件

假設(shè)ViewGroup B希望處理這個(gè)點(diǎn)擊事件,即B覆寫了onInterceptTouchEvent()返回true藕筋、onTouchEvent()返回true纵散。 事件傳遞情況:

DOWN事件被傳遞給B的onInterceptTouchEvent()方法,該方法返回true隐圾,表示攔截這個(gè)事件伍掀,即自己處理這個(gè)事件(不再往下傳遞)
調(diào)用onTouchEvent()處理事件(DOWN事件將不再往上傳遞給A的onTouchEvent())
該事件列的其他事件(Move、Up)將直接傳遞給B的onTouchEvent()
該事件列的其他事件(Move暇藏、Up)將不會(huì)再傳遞給B的onInterceptTouchEvent方法蜜笤,該方法一旦返回一次true,就再也不會(huì)被調(diào)用了盐碱。

7.4 攔截DOWN的后續(xù)事件

假設(shè)ViewGroup B沒(méi)有攔截DOWN事件(還是View C來(lái)處理DOWN事件)把兔,但它攔截了接下來(lái)的MOVE事件沪伙。

DOWN事件傳遞到C的onTouchEvent方法,返回了true县好。
在后續(xù)到來(lái)的MOVE事件围橡,B的onInterceptTouchEvent方法返回true攔截該MOVE事件,但該事件并沒(méi)有傳遞給B缕贡;這個(gè)MOVE事件將會(huì)被系統(tǒng)變成一個(gè)CANCEL事件傳遞給C的onTouchEvent方法
后續(xù)又來(lái)了一個(gè)MOVE事件翁授,該MOVE事件才會(huì)直接傳遞給B的onTouchEvent()
后續(xù)事件將直接傳遞給B的onTouchEvent()處理
后續(xù)事件將不會(huì)再傳遞給B的onInterceptTouchEvent方法,該方法一旦返回一次true晾咪,就再也不會(huì)被調(diào)用了收擦。
C再也不會(huì)收到該事件列產(chǎn)生的后續(xù)事件。

八禀酱、源碼分析

View中dispatchTouchEvent()的源碼分析

public boolean dispatchTouchEvent(MotionEvent event) {  
    if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&  
            mOnTouchListener.onTouch(this, event)) {  
        return true;  
    }  
    return onTouchEvent(event);  
}

從上面可以看出:

只有以下三個(gè)條件都為真,dispatchTouchEvent()才返回true牧嫉;否則執(zhí)行onTouchEvent(event)方法

  • 第一個(gè)條件:mOnTouchListener != null剂跟;
  • 第二個(gè)條件:(mViewFlags & ENABLED_MASK) == ENABLED;
  • 第三個(gè)條件:mOnTouchListener.onTouch(this, event)酣藻;

下面曹洽,我們來(lái)看看下這三個(gè)判斷條件:

第一個(gè)條件:mOnTouchListener!= null

//mOnTouchListener是在View類下setOnTouchListener方法里賦值的
public void setOnTouchListener(OnTouchListener l) { 

//即只要我們給控件注冊(cè)了Touch事件,mOnTouchListener就一定被賦值(不為空)
    mOnTouchListener = l;  
}

第二個(gè)條件:(mViewFlags & ENABLED_MASK) == ENABLED

該條件是判斷當(dāng)前點(diǎn)擊的控件是否enable
由于很多View默認(rèn)是enable的辽剧,因此該條件恒定為true

第三個(gè)條件:mOnTouchListener.onTouch(this, event)

回調(diào)控件注冊(cè)Touch事件時(shí)的onTouch方法

//手動(dòng)調(diào)用設(shè)置
button.setOnTouchListener(new OnTouchListener() {  
  @Override  
  public boolean onTouch(View v, MotionEvent event) {  
      return false;  
  }  
});

如果在onTouch方法返回true送淆,就會(huì)讓上述三個(gè)條件全部成立,從而整個(gè)方法直接返回true怕轿。

如果在onTouch方法里返回false偷崩,就會(huì)去執(zhí)行onTouchEvent(event)方法。

下面看onTouchEvent(event)**的源碼分析

主要這里面調(diào)用到了performClick()方法撞羽,關(guān)注這個(gè)方法即可

public boolean performClick() {  
    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);  
    if (mOnClickListener != null) {  
        playSoundEffect(SoundEffectConstants.CLICK);  
        mOnClickListener.onClick(this);  
        return true;  
    }  
    return false;  
}

只要mOnClickListener不為null阐斜,就會(huì)去調(diào)用onClick方法;
那么诀紊,mOnClickListener又是在哪里賦值的呢谒出?請(qǐng)繼續(xù)看:

public void setOnClickListener(OnClickListener l) {  
    if (!isClickable()) {  
        setClickable(true);  
    }  
    mOnClickListener = l;  
}

當(dāng)我們通過(guò)調(diào)用setOnClickListener方法來(lái)給控件注冊(cè)一個(gè)點(diǎn)擊事件時(shí),就會(huì)給mOnClickListener賦值(不為空)邻奠,即會(huì)回調(diào)onClick()笤喳。

結(jié)論

onTouch()的執(zhí)行高于onClick()

每當(dāng)控件被點(diǎn)擊時(shí):如果在回調(diào)onTouch()里返回false,就會(huì)讓dispatchTouchEvent方法返回false碌宴,那么就會(huì)執(zhí)行onTouchEvent()杀狡;如果回調(diào)了setOnClickListener()來(lái)給控件注冊(cè)點(diǎn)擊事件的話,最后會(huì)在performClick()方法里回調(diào)onClick()贰镣。

onTouch()返回false(該事件沒(méi)被onTouch()消費(fèi)掉) = 執(zhí)行onTouchEvent() = 執(zhí)行OnClick()
如果在回調(diào)onTouch()里返回true捣卤,就會(huì)讓dispatchTouchEvent方法返回true忍抽,那么將不會(huì)執(zhí)行onTouchEvent(),即onClick()也不會(huì)執(zhí)行董朝;

onTouch()返回true(該事件被onTouch()消費(fèi)掉) = dispatchTouchEvent()返回true(不會(huì)再繼續(xù)向下傳遞) = 不會(huì)執(zhí)行onTouchEvent() = 不會(huì)執(zhí)行OnClick()

onTouch()和onTouchEvent()的區(qū)別

這兩個(gè)方法都是在View的dispatchTouchEvent中調(diào)用鸠项,但onTouch優(yōu)先于onTouchEvent執(zhí)行。
如果在onTouch方法中返回true將事件消費(fèi)掉子姜,onTouchEvent()將不會(huì)再執(zhí)行祟绊。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市哥捕,隨后出現(xiàn)的幾起案子牧抽,更是在濱河造成了極大的恐慌遥赚,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,640評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件凫佛,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡愧薛,警方通過(guò)查閱死者的電腦和手機(jī)晨炕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,254評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門瓮栗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人费奸,你說(shuō)我怎么就攤上這事〗福” “怎么了货邓?”我有些...
    開(kāi)封第一講書人閱讀 165,011評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)四濒。 經(jīng)常有香客問(wèn)我换况,道長(zhǎng),這世上最難降的妖魔是什么盗蟆? 我笑而不...
    開(kāi)封第一講書人閱讀 58,755評(píng)論 1 294
  • 正文 為了忘掉前任戈二,我火速辦了婚禮,結(jié)果婚禮上喳资,老公的妹妹穿的比我還像新娘觉吭。我一直安慰自己,他們只是感情好仆邓,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,774評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布鲜滩。 她就那樣靜靜地躺著伴鳖,像睡著了一般。 火紅的嫁衣襯著肌膚如雪徙硅。 梳的紋絲不亂的頭發(fā)上榜聂,一...
    開(kāi)封第一講書人閱讀 51,610評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音嗓蘑,去河邊找鬼须肆。 笑死,一個(gè)胖子當(dāng)著我的面吹牛桩皿,可吹牛的內(nèi)容都是我干的豌汇。 我是一名探鬼主播,決...
    沈念sama閱讀 40,352評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼泄隔,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼拒贱!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起佛嬉,我...
    開(kāi)封第一講書人閱讀 39,257評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤逻澳,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后巷燥,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體赡盘,經(jīng)...
    沈念sama閱讀 45,717評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡号枕,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,894評(píng)論 3 336
  • 正文 我和宋清朗相戀三年缰揪,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片葱淳。...
    茶點(diǎn)故事閱讀 40,021評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡钝腺,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出赞厕,到底是詐尸還是另有隱情艳狐,我是刑警寧澤,帶...
    沈念sama閱讀 35,735評(píng)論 5 346
  • 正文 年R本政府宣布皿桑,位于F島的核電站毫目,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏诲侮。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,354評(píng)論 3 330
  • 文/蒙蒙 一刮便、第九天 我趴在偏房一處隱蔽的房頂上張望恨旱。 院中可真熱鬧,春花似錦搜贤、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,936評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至卓舵,卻和暖如春膀钠,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背肿嘲。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,054評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工雳窟, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人拇涤。 一個(gè)月前我還...
    沈念sama閱讀 48,224評(píng)論 3 371
  • 正文 我出身青樓鹅士,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親掉盅。 傳聞我的和親對(duì)象是個(gè)殘疾皇子以舒,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,974評(píng)論 2 355