一题翻、 事件分發(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事件蠢莺,如下圖:
二、事件分發(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及其派生類組合而成的
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()
五炊邦、 總結(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
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è)順序。最主要的是多去用用就記住了甫匹。下面我們看一般的事件傳遞甸鸟。
七、一般事件傳遞
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í)行祟绊。