網(wǎng)上關(guān)于Android事件分發(fā)機(jī)制的資料有許多旬盯,看過(guò)很多次,但是每次過(guò)一段時(shí)間就會(huì)忘記沙廉,感覺(jué)還是自己研究的不夠深入畴博,這一次,決定自己根據(jù)源碼蓝仲,來(lái)好好梳理一遍Android事件分發(fā)機(jī)制的知識(shí),本文絕對(duì)的簡(jiǎn)單易懂官疲,不像其他博客一樣袱结,上來(lái)就畫(huà)事件分發(fā)流程圖,完全看不懂呀有沒(méi)有⊥举欤現(xiàn)在將自己的理解分享出來(lái)希望對(duì)初學(xué)者有所幫助垢夹,只要你耐心看下去,定有收獲维费。
注:文中源碼基于android-26果元,版本略有差異,但大致流程是一致的犀盟。
前言
基本知識(shí)介紹而晒。
1.事件分發(fā)的”事件“是指什么?
即Touch事件阅畴,觸摸過(guò)程中產(chǎn)生的一系列Touch事件被封裝成一組事件列倡怎,并由底層向上傳遞到View。所謂的事件列贱枣,即指從手指接觸屏幕至手指離開(kāi)屏幕這個(gè)過(guò)程產(chǎn)生的一系列事件监署。一般情況下,事件列都是以DOWN事件開(kāi)始纽哥、UP事件結(jié)束钠乏,中間有無(wú)數(shù)的MOVE事件。
2.事件分發(fā)的本質(zhì)
事件分發(fā)機(jī)制春塌,總的來(lái)說(shuō)就是對(duì)一次屏幕觸摸事件的響應(yīng)過(guò)程晓避,從觸摸行為開(kāi)始簇捍,到觸摸行為結(jié)束,將點(diǎn)擊事件(MotionEvent)傳遞到某個(gè)具體的View & 處理的整個(gè)過(guò)程够滑。
3.事件列有哪些事件類型
事件分發(fā)過(guò)程涉及到的事件類型包括:
ACTION_DOWN | 按下屏幕時(shí)垦写,觸發(fā)此事件,只會(huì)觸發(fā)一次 |
---|---|
ACTION_MOVE | 移動(dòng)手指時(shí)彰触,觸發(fā)此事件梯投,會(huì)多次觸發(fā) |
ACTION_UP | 手指離開(kāi)屏幕時(shí),觸發(fā)此事件况毅,只會(huì)觸發(fā)一次 |
ACTION_CANCEL | 取消事件時(shí)分蓖,觸發(fā)此事件,只會(huì)觸發(fā)一次 |
對(duì)于ACTION_CANCEL事件的觸發(fā)時(shí)機(jī)尔许,會(huì)在后面給出么鹤,這里先簡(jiǎn)要介紹下。當(dāng)當(dāng)前view消費(fèi)了ACTION_DOWN之后味廊,在后續(xù)的ACTION_MOVE過(guò)程中蒸甜,調(diào)用了requestDisallowInterceptTouchEvent(false),使得父容器攔截了事件余佛,這時(shí)候柠新,就會(huì)先將當(dāng)前這個(gè)ACTION_MOVE轉(zhuǎn)變成ACTION_CANCEL事件,發(fā)送給之前消費(fèi)ACTION_DOWN事件的View辉巡,同時(shí)會(huì)將mFirstTouchTarget置為null恨憎,這樣的話,在下一個(gè)ACTION_MOVE到來(lái)時(shí)郊楣,就不會(huì)走攔截和分發(fā)流程憔恳,直接將ACTION_MOVE事件交給父容器處理,這也是事件沖突處理的主要理論知識(shí)净蚤。
//第一部分钥组,判斷事件是否需要攔截,disallowIntercept的值可由子view調(diào)用requestDisallowInterceptTouchEvent
//來(lái)改變今瀑,可以看出子類調(diào)用requestDisallowInterceptTouchEvent(false)時(shí)者铜,disallowIntercept置為false,
//允許父容器攔截放椰,onInterceptTouchEvent返回true時(shí)作烟,intercepted = true,
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
//...省略...
final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted;
//intercepted為true時(shí)砾医,cancelChild為true拿撩,dispatchTransformedTouchEvent調(diào)用時(shí),傳入true
if (dispatchTransformedTouchEvent(ev, cancelChild,target.child, target.pointerIdBits)) {
handled = true;
}
//消費(fèi)ACTION_CANCEL之后如蚜,將mFirstTouchTarget 置空,因此在后面的MOVE事件來(lái)到時(shí)压恒,不會(huì)走入第一部分的攔截判斷
//會(huì)走入else分支影暴,intercepted = true;將后續(xù)MOVE交給自己處理探赫。
if (cancelChild) {
mFirstTouchTarget 置空
}
//cancel為true
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
// cancel為true時(shí)型宙,event.setAction(MotionEvent.ACTION_CANCEL),將MOVE事件變成ACTION_CANCEL事件伦吠。
// child為消費(fèi)ACTION_DOWN的view妆兑。child不為空,即消費(fèi)ACTION_CANCEL毛仪。
// 需要注意的是搁嗓,手指移動(dòng)時(shí)ACTION_MOVE會(huì)多次觸發(fā),這里消費(fèi)的是當(dāng)前轉(zhuǎn)換來(lái)的MOVE事件箱靴,
// 當(dāng)下一個(gè)MOVE事件來(lái)的時(shí)候腺逛,mFirstTouchTarget已經(jīng)為空了,所以會(huì)將后續(xù)的ACTION_MOVE事件交給父容器處理衡怀。
final int oldAction = event.getAction();
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
//轉(zhuǎn)換為CANCEL事件并消費(fèi)后棍矛,重新設(shè)置回ACTION_MOVE類型。
event.setAction(oldAction);
return handled;
}
}
4.事件分發(fā)涉及到的類和方法抛杨。
4.1 事件分發(fā)由哪些對(duì)象完成
先來(lái)看一下Activity的大體層級(jí)够委。事件分發(fā)過(guò)程實(shí)際上就是Activity、ViewGroup蝶桶、View對(duì)MotionEvent事件的分發(fā),攔截掉冶,消費(fèi)的過(guò)程真竖。
其中,只有ViewGroup能進(jìn)行事件攔截厌小,因此只有ViewGroup有onInterceptTouchEvent方法恢共。
Activity | 擁有dispathTouchEvent和onTouchEvent方法 |
---|---|
ViewGroup | 擁有dispatchTouchEvent、onTouchEvent璧亚、onInterceptTouchEvent |
View | 擁有dispathTouchEvent和onTouchEvent方法 |
4.2 事件分發(fā)由哪些方法完成
dispatchTouchEvent(MotionEvent ev) | 事件分發(fā) |
---|---|
onInterceptTouchEvent(MotionEvent ev) | 事件攔截 |
onTouchEvent(MotionEvent event) | 事件消費(fèi) |
三個(gè)方法都有三種返回類型讨韭,true、false癣蟋、super.xxx透硝,不同返回值,觸發(fā)的流程會(huì)有所差異疯搅,在后面會(huì)進(jìn)行詳細(xì)的分析濒生。
觸摸事件是如何傳遞到Activity的?
1.首先幔欧,當(dāng)我們觸摸屏幕時(shí)罪治,通過(guò)Android消息機(jī)制丽声,從Looper從MessageQueue中取出該事件,發(fā)送給WindowInputEventReceiver觉义。
2.WindowInputEventReceiver是ViewRootImpl的內(nèi)部類雁社,通過(guò)enqueueInputEvent方法,將輸入事件加入輸入事件隊(duì)列中晒骇,并進(jìn)行處理和轉(zhuǎn)發(fā)霉撵。
3.ViewPostImeInputStage收到輸入事件,將事件傳遞給DecorView的dispatchPointerEvent()方法(是View的方法)
4.dispatchPointerEvent()方法通過(guò)DecorView中的dispatchTouchEvent()方法厉碟,通過(guò)回調(diào)喊巍,調(diào)用了Activity的dispatchTouchEvent()方法。
到此事件進(jìn)入Activity中箍鼓!
一個(gè)事件的整體流程:
ViewRootImpl.processPointerEvent--->DecorView.dispatchTouchEvent--->Activity.dispatchTouchEvent--->PhoneWindow.superDispatchTouchEvent--->DecorView.superDispatchTouchEvent--->ViewGroup.dispatchTouchEvent--->view...
具體流程如下圖崭参,想詳細(xì)了解的,可參考文章:原來(lái)Android觸控機(jī)制竟是這樣的款咖?http://www.reibang.com/p/b7cef3b3e703
觸摸事件是如何從Activity一層層傳到View的何暮?
上面我們知道了系統(tǒng)是如何將觸摸行為封裝成MotionEvent事件,并傳遞給Activity中去的铐殃,下面介紹下海洼,系統(tǒng)如何將事件由Activity一步步傳到我們的View中去的。我們知道富腊,Activity的dispatchTouchEvent方法坏逢,將事件傳給window,而window是在Activity調(diào)用attach方法的時(shí)候赘被,創(chuàng)建的是整,唯一實(shí)現(xiàn)子類是PhoneWindow。
//Activity
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor,
Window window, ActivityConfigCallback activityConfigCallback) {
attachBaseContext(context);
//....省略...
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
//....省略...
}
//分發(fā)事件
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
而在PhoneWindow中調(diào)用了mDecor.superDispatchTouchEvent(MotionEvent event)民假,可以看到PhoneWindow只是起中轉(zhuǎn)作用浮入,將事件交給了DecorView,PhoneWindow中的DecorView是在setContentView時(shí)生成并綁定的羊异。
//PhoneWindow
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
DecorView是一個(gè)繼承FrameLayout的ViewGroup事秀,在superDispatchTouchEvent(MotionEvent event)方法中調(diào)用super.dispatchTouchEvent(event),即將event事件傳遞到了ViewGroup層野舶,實(shí)現(xiàn)Activity到ViewGroup的傳遞易迹。
//DecorView
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
由上可知,因?yàn)镈ecorView是一個(gè)繼承自FrameLayout的ViewGroup平道,因此實(shí)現(xiàn)了MotionEvent事件赴蝇,由Activity傳遞到ViewGroup中去的過(guò)程,接下來(lái)事件就在ViewGroup和子View中進(jìn)行分發(fā)巢掺,消費(fèi)句伶。
事件分發(fā)流程
先來(lái)個(gè)傳說(shuō)中的U型事件圖鎮(zhèn)樓:后續(xù)的分析結(jié)合這個(gè)圖劲蜻,會(huì)更好理解。
1.Activity的事件分發(fā)
先看下源碼:
//Activity dispatchTouchEvent分發(fā)事件
public boolean dispatchTouchEvent(MotionEvent ev) {
//step 1 當(dāng)DOWN事件來(lái)臨時(shí)考余,做一些特殊處理先嬉,onUserInteraction默認(rèn)是空實(shí)現(xiàn),你可以自己定義想要操作的事件楚堤。
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
//step 2 將事件交給Window疫蔓,進(jìn)而交給decorView處理。
// 若getWindow().superDispatchTouchEvent(ev)的返回true
// 則Activity.dispatchTouchEvent()就返回true身冬,則方法結(jié)束衅胀。即 :該點(diǎn)擊事件停止往下傳遞 & 事件傳遞過(guò)程結(jié)束
// 否則:繼續(xù)往下調(diào)用Activity.onTouchEvent
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
//step 3 如果在step 2中,整個(gè)View Tree都不處理事件酥筝,那么就將事件交給Activity的onTouchEvent(ev)處理滚躯,不論
//onTouchEvent(ev)返回值如何,都表示Activity消費(fèi)了該事件嘿歌,后續(xù)的MOVE和UP事件掸掏,也會(huì)直接交給Activity處理,不在向下分發(fā)宙帝。
return onTouchEvent(ev);
}
//Activity onTouchEvent消費(fèi)事件
public boolean onTouchEvent(MotionEvent event) {
//shouldCloseOnTouch主要是對(duì)于處理邊界外點(diǎn)擊事件的判斷:是否是DOWN事件丧凤,event的坐標(biāo)是否在邊界
//內(nèi)等,所以Activity的onTouchEvent事件步脓,默認(rèn)是返回false的愿待,除非你重寫(xiě)了。
if (mWindow.shouldCloseOnTouch(this, event)) {
finish();
return true;
}
//默認(rèn)是返回false的
return false;
}
Activity的事件分發(fā)與消費(fèi)比較簡(jiǎn)單靴患,總結(jié)如下:
1.1 Activity.dispatchTouchEvent(MotionEvent ev)分析--針對(duì)DOWN事件(后續(xù)分析CANCEL和UP事件)
1.當(dāng)事件由底層傳到Activity時(shí)仍侥,進(jìn)入Activity.dispatchTouchEvent(MotionEvent ev),在Activity中有三種返回值蚁廓,true访圃,false厨幻,以及super.dispatchTouchEvent(ev);
//我們自己的Activity相嵌,進(jìn)行了重寫(xiě)
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// return true;
// return false;
return super.dispatchTouchEvent(ev);
}
再來(lái)看下父類Activity的dispatchTouchEvent方法。
//父類的Activity
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
第一種情況:返回true况脆;
由于我們?cè)谧约旱腁ctivity中重寫(xiě)了父類的dispatchTouchEvent(MotionEvent ev)方法饭宾,因此不會(huì)再調(diào)用getWindow().superDispatchTouchEvent(ev)方法了,即不會(huì)將事件傳遞到View層格了,同時(shí)也不會(huì)調(diào)用onTouchEvent(ev);該事件直接被消費(fèi)了看铆。后續(xù)再來(lái)的MOVE和UP事件也不會(huì)再向下分發(fā),而是會(huì)直接經(jīng)過(guò)DecorView.dispatchTouchEvent方法盛末,通過(guò)回調(diào)交給Activity的dispatchTouchEvent弹惦,由于返回了true否淤,會(huì)直接消費(fèi),也不會(huì)將事件交給onTouchEvent了棠隐。
第二種情況:返回false石抡;
同第一種情況,也是直接消費(fèi)了助泽,不會(huì)將事件向下傳了啰扛,也不會(huì)將事件交給onTouchEvent了。
第三種情況:返回super.dispatchTouchEvent(ev)嗡贺;
如果調(diào)用默認(rèn)的方法隐解,即不對(duì)dispatchTouchEvent做任何處理,那么就會(huì)調(diào)用父類Activity的dispatchTouchEvent方法诫睬,將會(huì)調(diào)用getWindow().superDispatchTouchEvent(ev)方法煞茫,將event事件傳遞給decorView(ViewGroup)的dispatchTouchEvent,如果getWindow().superDispatchTouchEvent(ev)返回true岩臣,表示有子view消費(fèi)了此事件溜嗜,那么Activity的dispatchTouchEvent會(huì)返回true,事件到此結(jié)束架谎,否則炸宵,會(huì)將事件傳遞給Activity的onTouchEvent,不論onTouchEvent返回值如何谷扣,事件分發(fā)都到此結(jié)束土全。
那么getWindow().superDispatchTouchEvent(ev)什么時(shí)候返回true,什么時(shí)候返回false呢会涎,這就需要分析ViewGroup的事件分發(fā)流程了裹匙。后續(xù)分析,此處先介紹下末秃,Activity的onTouchEvent事件概页。
1.2 Activity.onTouchEvent(MotionEvent ev)事件消費(fèi)
//Activity onTouchEvent消費(fèi)事件
public boolean onTouchEvent(MotionEvent event) {
//shouldCloseOnTouch主要是對(duì)于處理邊界外點(diǎn)擊事件的判斷:是否是DOWN事件,event的坐標(biāo)是否在邊界
//內(nèi)等练慕,所以Activity的onTouchEvent事件惰匙,默認(rèn)是返回false的,除非你重寫(xiě)了铃将。
if (mWindow.shouldCloseOnTouch(this, event)) {
finish();
return true;
}
//默認(rèn)是返回false的
return false;
}
假如Activity中的所有控件都不消費(fèi)觸摸事件DOWN项鬼,即getWindow().superDispatchTouchEvent(ev)返回false,就會(huì)將事件交給Activity.onTouchEvent劲阎,事件到此終結(jié)绘盟,后續(xù)的MOVE和UP就會(huì)直接交給Activity.onTouchEvent處理。
在我們自己的Activity中,onTouchEvent也有三種返回值龄毡,true吠卷,false,以及super.onTouchEvent(ev)沦零,只是不論哪種返回值撤嫩,最終的結(jié)果都是終結(jié)了事件傳遞,對(duì)后續(xù)事件的處理一致蠢终。
2.ViewGroup的事件分發(fā)
2.1 ViewGroup.dispatchTouchEvent(ev)---事件分發(fā)序攘,分段式分析
下面只給出了dispatchTouchEvent的關(guān)鍵代碼,非主要代碼省略寻拂,可自己去看下源碼程奠,這里主要是分析分發(fā)過(guò)程。
//ViewGroup.dispatchTouchEvent
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
//...省略...
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// 分析1 初始化DOWN事件祭钉,每次接收到down事件時(shí)账千,會(huì)先清除mFirstTouchTarget
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Throw away all previous state when starting a new touch gesture.
// The framework may have dropped the up or cancel event for the previous gesture
// due to an app switch, ANR, or some other state change.
cancelAndClearTouchTargets(ev);
resetTouchState();
}
//第一部分威恼,事件攔截传蹈,可以看到只有DOWN事件央拖,或者mFirstTouchTarget不為null的情況下才會(huì)
//進(jìn)行攔截判斷,move垮卓、up事件垫桂,或者mFirstTouchTarget為null時(shí),會(huì)跳過(guò)這一部分粟按。
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//分析2 子view通知父iew是否攔截 requestDisallowInterceptTouchEvent(false)
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
//分析3 判斷是否攔截
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
//...省略...
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
//第二部分诬滩,事件分發(fā)
if (!canceled && !intercepted) {
//...省略....
//分析4 只有Down事件才會(huì)走這里的if語(yǔ)句塊,move事件和up事件都不會(huì)走
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
//...省略...
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
//...省略...
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
//...省略...
//分析4 循環(huán)遍歷子view灭将,只有VISIBLE且包含落點(diǎn)坐標(biāo)的子view才能接收down事件
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
//...省略...
//分析5 將事件交給每一個(gè)view處理疼鸟,返回true,說(shuō)明該子view消費(fèi)了down事件庙曙,
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
//...省略...
//分析6 如果有子view消費(fèi)了down事件空镜,則給mFirstTouchTarget賦值
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
}
//...省略...
}
}
//第三部分,事件處理消費(fèi) Dispatch to touch targets.
//分析7 經(jīng)過(guò)上面的遍歷沒(méi)有找到能夠處理down事件的子view捌朴,只能將事件交給自己處理了吴攒,
//此時(shí)child為null
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
// Dispatch to touch targets, excluding the new touch target if we already
// dispatched to it. Cancel touch targets if necessary.
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
//分析8 在分析6處遍歷找到了能夠處理down事件的子view后,
//alreadyDispatchedToNewTouchTarget為true男旗,在分析5處已經(jīng)處理了舶斧,所以這里直接返回
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
//如果子view調(diào)用requestDisallowInterceptTouchEvent(false)欣鳖,會(huì)將interceptd置true察皇,使得cancelChild為true
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
//分析9 此處主要處理move、up、cancel事件什荣,具體分析后面給出
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
//分析10 如果事件cancel了矾缓,這里會(huì)將mFirstTouchTarget置為null
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
//...省略...
return handled;
}
//分析4處
//ViewGroup.canViewReceivePointerEvents,只有VISIBLE的控件才能接收觸摸事件稻爬,這也解釋了嗜闻,
//為什么INVISILE的控件,能夠measure桅锄,能夠layout琉雳,但是不能響應(yīng)觸摸事件的原因
private static boolean canViewReceivePointerEvents(@NonNull View child) {
return (child.mViewFlags & VISIBILITY_MASK) == VISIBLE
|| child.getAnimation() != null;
}
//分析4處
//ViewGroup.isTransformedTouchPointInView ,如果觸摸事件的xy坐標(biāo)落到了當(dāng)前正在遍歷的子view的
//區(qū)域內(nèi)友瘤,返回true翠肘,表示這個(gè)子view能接收觸摸事件。
protected boolean isTransformedTouchPointInView(float x, float y, View child,
PointF outLocalPoint) {
final float[] point = getTempPoint();
point[0] = x;
point[1] = y;
transformPointToViewLocal(point, child);
final boolean isInView = child.pointInView(point[0], point[1]);
if (isInView && outLocalPoint != null) {
outLocalPoint.set(point[0], point[1]);
}
return isInView;
}
//分析5處辫秧,這個(gè)方法才是真正分發(fā)處理事件的方法束倍,down,move盟戏,up绪妹,cancel都由這個(gè)方法處理
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
// Canceling motions is a special case. We don't need to perform any transformations
// or filtering. The important part is the action, not the contents.
final int oldAction = event.getAction();
//分析11, 如果cancel為true柿究,會(huì)先將MOVE事件換成ACTION_CANCEL邮旷,交給child處理,然后又改成MOVE蝇摸。
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
//...省略...
//分析12廊移, 將DOWN、MOVE探入、UP事件狡孔,交給child處理。
// Perform any necessary transformations and dispatch.
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
} else {
//...省略...
handled = child.dispatchTouchEvent(transformedEvent);
}
// Done.
transformedEvent.recycle();
return handled;
}
//分析6處蜂嗽,如果子view消費(fèi)了down事件苗膝,說(shuō)明有某個(gè)view能夠處理觸摸事件,此時(shí)就會(huì)給mFirstTouchTarget
//賦值植旧,等后面的move和up事件來(lái)到時(shí)辱揭,就會(huì)進(jìn)入第一部分的代碼塊,重新判斷是否需要攔截
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
上面是viewGroup進(jìn)行事件分發(fā)的源碼病附,幾個(gè)重要的地方已經(jīng)給出了說(shuō)明问窃,這里將各種情況進(jìn)行總結(jié)。
在我們自己定義的viewGroup中完沪,可以重寫(xiě)dispatchTouchEvent來(lái)自己定義是否分發(fā)給子view
第一種情況:返回true域庇;
如果直接返回true嵌戈,就不會(huì)走父類中攔截,分發(fā)听皿,消費(fèi)的流程熟呛,直接告訴父容器自己處理了這個(gè)事件。注意尉姨,這句話里說(shuō)的父類和父容器不是同一個(gè)東西庵朝,父容器在分發(fā)事件時(shí),會(huì)遍歷自己子view又厉,即分析5處的代碼九府,調(diào)用子view的dispatchTouchEvent,這時(shí)候覆致,因?yàn)樽约悍祷亓藅rue昔逗,父容器的分發(fā)事件就會(huì)進(jìn)入到分析6處的代碼,給mFirstTouchTarget賦值篷朵。
第二種情況:返回false勾怒;
如果直接返回false,同樣也不會(huì)走父類中攔截声旺,分發(fā)笔链,消費(fèi)的流程,直接告訴父容器自己不處理這個(gè)事件腮猖。父容器在分發(fā)事件時(shí)鉴扫,會(huì)遍歷自己子view,即分析5處的代碼澈缺,調(diào)用子view的dispatchTouchEvent坪创,這時(shí)候,因?yàn)樽约悍祷亓薴alse姐赡,不會(huì)進(jìn)入到分析6處的代碼莱预,不會(huì)給mFirstTouchTarget賦值。在分析7代碼處项滑,由于mFirstTouchTarget為null依沮,會(huì)調(diào)用super.dispatchTouchEvent,即將事件交給父容器自己處理枪狂。子view不處理了危喉,只能自己處理了。
第三種情況:返回super.dispatchTouchEvent州疾;
如果我們自定義的ViewGroup重寫(xiě)了dispatchTouchEvent辜限,并返回super.dispatchTouchEvent,即會(huì)走父類默認(rèn)的分發(fā)流程严蓖。這里也是事件分發(fā)最復(fù)雜的地方薄嫡。
重點(diǎn)分析第三種情況氧急。
這塊我們可以總體把dispatchTouchEvent分為三部分,第一部分用于判斷ViewGroup是否攔截事件岂座,第二部分用于遍歷子view,對(duì)DOWN事件進(jìn)行分發(fā)杭措,找到能夠處理觸摸事件的子View费什。第三部分則是處理事件,主要是處理MOVE手素、UP鸳址、CANCEL事件。
ACTION_DOWN事件分發(fā)流程:
1.當(dāng)DOWN事件由Activity分發(fā)到ViewGroup的dispatchTouchEvent方法之后泉懦,分析1處稿黍,會(huì)將之前的觸摸重新初始化,mFirstTouchTarget置為null崩哩,接著進(jìn)入第一部分巡球,判斷是否攔截,且只有當(dāng)事件類型是DOWN事件邓嘹,或者mFirstTouchTarget不為空的情況下酣栈,才會(huì)進(jìn)行攔截判斷。
2.由于是第一個(gè)DOWN事件汹押,mFirstTouchTarget一定為空矿筝,進(jìn)入到攔截判斷,即分析3處的判斷棚贾,onInterceptTouchEvent返回值決定是否攔截窖维,即變量intercepted的賦值,DOWN事件時(shí)妙痹,intercepted賦值false铸史,此時(shí)會(huì)進(jìn)入到第二部分代碼中去,即在分析5處怯伊,遍歷子View沛贪,對(duì)DOWN事件進(jìn)行處理。
3.如果分析5處震贵,找到了能夠消費(fèi)DOWN事件的子View利赋,就會(huì)進(jìn)入分析6處,將該View賦值給mFirstTouchTarget猩系,如果所有子View都不消費(fèi)DOWN事件媚送,則mFirstTouchTarget為null。
4.第三部分代碼是對(duì)事件的處理寇甸,如果第二步中的執(zhí)行結(jié)果塘偎,使得mFirstTouchTarget為null疗涉,則在第三部分就會(huì)進(jìn)入分析7的代碼,將DOWN事件交給自己處理吟秩。
5.如果mFirstTouchTarget不為null咱扣,表示已經(jīng)有View消費(fèi)了DOWN事件,此時(shí)代碼會(huì)走到分析8處涵防,直接將handle置為true闹伪。表示消費(fèi)了DOWN事件。
ACTION_MOVE壮池、ACTION_UP事件分發(fā)流程:
上面是對(duì)DOWN事件的處理偏瓤,主要是用于找出是否有子View消費(fèi)了DOWN事件。
1.當(dāng)MOVE或UP事件來(lái)到時(shí)椰憋,由于不是DOWN事件厅克,因此不會(huì)走分析1處發(fā)初始化。當(dāng)走到第一部分判斷是否攔截時(shí)橙依,由于不是DOWN事件证舟,只能由mFirstTouchTarget決定是否進(jìn)行判斷。
2.如果在處理DOWN事件時(shí)窗骑,找到了消費(fèi)事件的View褪储,即mFirstTouchTarget不為null,則需要根據(jù)onInterceptTouchEvent返回值決定是否攔截慧域,不論攔截與否鲤竹,都不會(huì)進(jìn)入到第二部分的事件分發(fā)。因?yàn)榈诙糠址职l(fā)只處理DOWN事件昔榴。如果mFirstTouchTarget為null辛藻,則intercepted = true。
3.第三部分執(zhí)行時(shí)互订,如果mFirstTouchTarget為null吱肌,繼續(xù)講MOVE和UP事件交給自己處理。
4.第三部分執(zhí)行時(shí)仰禽,如果mFirstTouchTarget不為null氮墨,則會(huì)進(jìn)入到分析9的代碼分支。如果在步驟2中onInterceptTouchEvent返回了false吐葵,即intercepted = false规揪,cancelChild也為false,就將MOVE和UP事件交給mFirstTouchTarget處理温峭。
ACTION_CANCEL事件分發(fā)流程:
在分析ACTION_MOVE猛铅、ACTION_UP事件時(shí),第4步中凤藏,如果mFirstTouchTarget不為null奸忽,則會(huì)進(jìn)入到分析9的代碼分支堕伪。如果在步驟2中onInterceptTouchEvent返回了true,即intercepted = true栗菜,此時(shí)cancelChild也為true欠雌,分析9調(diào)用dispatchTransformedTouchEvent(ev, true, target.child, target.pointerIdBits),傳入?yún)?shù)cancelChild為true疙筹。
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
// Canceling motions is a special case. We don't need to perform any transformations
// or filtering. The important part is the action, not the contents.
final int oldAction = event.getAction();
//分析10富俄, 如果cancel為true,會(huì)先將MOVE事件換成ACTION_CANCEL腌歉,交給child處理蛙酪,然后又改成MOVE齐苛。
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
return handled;
}
由源碼可知翘盖,當(dāng)cance為true時(shí),event.setAction(MotionEvent.ACTION_CANCEL);會(huì)將MOVE事件改成CANCEL事件凹蜂,并交給mFirstTouchTarget處理馍驯,處理后又設(shè)置回MOVE事件。這就是ACTION_CANCEL事件觸發(fā)的時(shí)機(jī)玛痊,即之前有子VIew消費(fèi)了DOWN事件汰瘫,但是后來(lái)MOVE事件被攔截了。
在這一個(gè)MOVE事件裝換成CANCEL事件被消費(fèi)后擂煞,分析10處混弥,會(huì)將mFirstTouchTarget置為null。那么在下一個(gè)MOVE來(lái)臨時(shí)对省,mFirstTouchTarget為null蝗拿,同時(shí)又不是DOWN事件,ViewGroup.dispatchTouchEvent會(huì)直接走入到分析7的代碼分支中去蒿涎,由自己消費(fèi)后續(xù)的MOVE事件哀托。
至此,ViewGroup的事件分發(fā)分析結(jié)束劳秋。
2.2 ViewGroup.onInterceptTouchEvent(ev)---事件攔截分析
事件攔截方法onInterceptTouchEvent仓手,只在ViewGroup中存在,默認(rèn)情況下玻淑,都是返回false嗽冒,即不攔截。在我們自己定義的viewGroup中补履,可以重寫(xiě)onInterceptTouchEvent來(lái)自己定義是否攔截辛慰,返回true表示攔截,返回false表示不攔截干像,如果返回super帅腌,則表示調(diào)用父類默認(rèn)的方法驰弄,即不攔截,返回false和super速客,都表示不攔截事件戚篙。
第一種情況:返回true;
表示攔截事件溺职,交給自己的onTouchEvent處理岔擂。
第二種情況:返回false;
表示不攔截事件浪耘,會(huì)將事件分發(fā)給子view乱灵。
第三種情況:返回super;
同返回false的情況七冲,表示不攔截事件痛倚,會(huì)將事件分發(fā)給子view。
//ViewGroup 事件攔截
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
&& ev.getAction() == MotionEvent.ACTION_DOWN
&& ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
&& isOnScrollbarThumb(ev.getX(), ev.getY())) {
return true;
}
return false;
}
2.3 ViewGroup.onTouchEvent(ev)---事件消費(fèi)
ViewGroup沒(méi)有重寫(xiě)onTouchEvent方法澜躺,因此蝉稳,還是調(diào)用的父類(即View)的onTouchEvent方法,在View的分析中會(huì)給出掘鄙。
3.View的事件分發(fā)
3.1 View.dispatchTouchEvent(ev)---事件分發(fā)分析
// View.dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent event) {
//...省略...
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//分析1 如果view設(shè)置了mOnTouchListener耘戚,且view是enable的,同時(shí)onTouch返回true操漠,
//則表示消費(fèi)了事件收津,不會(huì)再將事件交給onTouchEvent處理。
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
//分析2 如果沒(méi)有設(shè)置mOnTouchListener浊伙,或者view不是enable的撞秋,或者onTouch返回了false,則會(huì)
//將事件交給View的onTouchEvent處理吧黄。默認(rèn)情況下部服,該方法只要view是clickable或者
//LONG_CLICKABLE,就會(huì)返回true拗慨,表示消費(fèi)了事件
if (!result && onTouchEvent(event)) {
result = true;
}
}
//...省略...
return result;
}
我們自定義View的dispatchTouchEvent廓八,也有三種重寫(xiě)方式。
第一種情況:返回true赵抢;
如果直接返回true剧蹂,就不會(huì)走分發(fā),消費(fèi)的流程烦却,直接告訴父容器自己處理了這個(gè)事件宠叼。父容器在分發(fā)事件時(shí),會(huì)遍歷自己子view,即ViewGroup中分析5處的代碼冒冬,調(diào)用子view的dispatchTouchEvent伸蚯,這時(shí)候,因?yàn)閂iew自己返回了true简烤,父容器的分發(fā)事件就會(huì)進(jìn)入到ViewGroup中分析6處的代碼剂邮,給mFirstTouchTarget賦值,在下一個(gè)MOVE事件或UP事件來(lái)到時(shí)横侦,直接交給這個(gè)消費(fèi)了DOWN事件的View挥萌。
第二種情況:返回false;
如果直接返回false枉侧,同樣也不會(huì)走分發(fā)引瀑,消費(fèi)的流程,直接告訴父容器自己不處理這個(gè)事件榨馁。父容器在分發(fā)事件時(shí)憨栽,會(huì)遍歷自己子view,即ViewGroup中分析5處的代碼辆影,調(diào)用子view的dispatchTouchEvent徒像,這時(shí)候黍特,因?yàn)閂iew自己返回了false蛙讥,不會(huì)進(jìn)入到ViewGroup中分析6處的代碼,不會(huì)給mFirstTouchTarget賦值灭衷。在ViewGroup中分析7代碼處次慢,由于mFirstTouchTarget為null,會(huì)調(diào)用super.dispatchTouchEvent翔曲,即將事件交給父容器自己處理迫像。子view不處理了,只能父容器自己處理了瞳遍。
第三種情況:返回super.dispatchTouchEvent闻妓;
如果我們自定義的View重寫(xiě)了dispatchTouchEvent,并返回super.dispatchTouchEvent掠械,即會(huì)走父類View默認(rèn)的分發(fā)流程由缆。
分析1:在dispatchTouchEvent中,首先會(huì)去查找View是否設(shè)置了mOnTouchListener猾蒂,即是否調(diào)用了
View.setOnTouchListener均唉,且判斷onTouch的返回值,如果返回true肚菠,則表示事件被當(dāng)前這個(gè)View消費(fèi)了舔箭,不會(huì)再把事件傳遞到onTouchEvent中去。
分析2:如果沒(méi)有設(shè)置mOnTouchListener蚊逢,或者view不是enable的层扶,或者onTouch返回了false箫章,則會(huì)將事件交給View的onTouchEvent處理。默認(rèn)情況下镜会,該方法只要view是clickable或者LONG_CLICKABLE炉抒,就會(huì)返回true,表示消費(fèi)了事件稚叹。
3.2 View.onTouchEvent(ev)---事件消費(fèi)
//View.onTouchEvent
public boolean onTouchEvent(MotionEvent event) {
//...省略...
//分析1 只要view是CLICKABLE或LONG_CLICKABLE焰薄,clickable就為true,onTouchEvent就一定返回true
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
//分析2 如果view是disabled扒袖,但clickable為true塞茅,onTouchEvent也返回true
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return clickable;
}
//分析3 如果view是設(shè)置了代理,且代理View的onTouchEvent返回true季率,則自己的onTouchEvent也返回true
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
//分析4 clickable為true野瘦,onTouchEvent一定返回true,表示消費(fèi)了事件飒泻。
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
if ((viewFlags & TOOLTIP) == TOOLTIP) {
handleTooltipUp();
}
if (!clickable) {
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
break;
}
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
// take focus if we don't have it already and we should in
// touch mode.
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (prepressed) {
// The button is being released before we actually
// showed it as pressed. Make it show the pressed
// state now (before scheduling the click) to ensure
// the user sees it.
setPressed(true, x, y);
}
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// This is a tap, so remove the longpress check
removeLongPressCallback();
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
//分析5 如果設(shè)置了點(diǎn)擊事件鞭光,在收到ACTION_UP事件時(shí),執(zhí)行click事件
if (!post(mPerformClick)) {
performClick();
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
mUnsetPressedState.run();
}
removeTapCallback();
}
mIgnoreNextUpEvent = false;
break;
case MotionEvent.ACTION_DOWN:
if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
}
mHasPerformedLongPress = false;
if (!clickable) {
checkForLongClick(0, x, y);
break;
}
if (performButtonActionOnTouchDown(event)) {
break;
}
// Walk up the hierarchy to determine if we're inside a scrolling container.
boolean isInScrollingContainer = isInScrollingContainer();
// For views inside a scrolling container, delay the pressed feedback for
// a short period in case this is a scroll.
if (isInScrollingContainer) {
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// Not inside a scrolling container, so show the feedback right away
setPressed(true, x, y);
//分析6 如果設(shè)置了長(zhǎng)按事件泞遗,在收到ACTION_DOWN事件時(shí)惰许,在500ms時(shí),會(huì)檢查執(zhí)行長(zhǎng)按事件
checkForLongClick(0, x, y);
}
break;
case MotionEvent.ACTION_CANCEL:
if (clickable) {
setPressed(false);
}
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
break;
case MotionEvent.ACTION_MOVE:
if (clickable) {
drawableHotspotChanged(x, y);
}
// 注意史辙,如果手指滑出view范圍汹买,會(huì)取消掉長(zhǎng)按事件。
if (!pointInView(x, y, mTouchSlop)) {
// Outside button
// Remove any future long press/tap checks
removeTapCallback();
removeLongPressCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
}
break;
}
return true;
}
return false;
}
我們自定義View時(shí)聊倔,可以重寫(xiě)onTouchEvent方法晦毙,來(lái)自己定義是否消費(fèi)事件,返回true表示消費(fèi)耙蔑,返回false表示不消費(fèi)见妒,如果返回super,則表示調(diào)用父類默認(rèn)的方法甸陌。
第一種情況:返回true须揣;
表示消費(fèi)事件,此時(shí)邀层,dispatchTouchEvent返回true返敬,事件終結(jié)。
第二種情況:返回false寥院;
表示不消費(fèi)事件劲赠,此時(shí),子view的dispatchTouchEvent返回false,父容器沒(méi)有找到能夠處理down事件的子view凛澎,此時(shí)父容器的mFirstTouchTarget就為null霹肝,將事件交給自己處理,詳見(jiàn)ViewGroup的分析7塑煎。
第三種情況:返回super沫换;
如果自定義view返回了super.onTouchEvent,表示調(diào)用父類默認(rèn)的事件處理方法最铁。
這種情況下讯赏,只要View是clickable的,就會(huì)返回true冷尉,表示消費(fèi)了漱挎。總結(jié)下代碼中的多出分析點(diǎn):
分析1:只要view是CLICKABLE或LONG_CLICKABLE雀哨,clickable就為true磕谅,onTouchEvent就一定返回true
分析2:如果view是disabled,但clickable為true雾棺,onTouchEvent也返回true
分析3:如果view是設(shè)置了代理膊夹,且代理View的onTouchEvent返回true,則自己的onTouchEvent也返回true捌浩。
分析4:如果clickable為true放刨,就會(huì)進(jìn)入處理DOWN、MOVE嘉栓、UP事件的代碼塊中宏榕,最終onTouchEvent一定返回true拓诸,表示消費(fèi)了事件侵佃。
分析5:在處理事件時(shí),如果設(shè)置了點(diǎn)擊事件奠支,在收到ACTION_UP事件時(shí)馋辈,會(huì)執(zhí)行click事件
分析6:如果設(shè)置了長(zhǎng)按事件,在收到ACTION_DOWN事件時(shí)倍谜,會(huì)啟動(dòng)一個(gè)runnable迈螟,在500ms時(shí),會(huì)檢查執(zhí)行長(zhǎng)按事件尔崔。
由上面的分析可知答毫,View想要消費(fèi)事件,必須要是clickable的季春,默認(rèn)情況下洗搂,View的LONG_CLICKABLE為false,而CLICKABLE則就View類型有關(guān),比如TextView默認(rèn)不可點(diǎn)耘拇,Button默認(rèn)是可點(diǎn)擊的撵颊。另外在設(shè)置監(jiān)聽(tīng)的時(shí)候,是會(huì)將CLICKABLE和LONG_CLICKABLE置為true的惫叛,也就是說(shuō)倡勇,設(shè)置了監(jiān)聽(tīng),就一定會(huì)消費(fèi)事件嘉涌。
//設(shè)置點(diǎn)擊事件
public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
//設(shè)置長(zhǎng)按事件
public void setOnLongClickListener(@Nullable OnLongClickListener l) {
if (!isLongClickable()) {
setLongClickable(true);
}
getListenerInfo().mOnLongClickListener = l;
}
根據(jù)上面的分析可知妻熊,onClick事件是在View.onTouchEvent收到UP事件的時(shí)候,才會(huì)觸發(fā)仑最,而View.onTouchEvent能夠執(zhí)行的先決條件就是固耘,View的沒(méi)有設(shè)置OnTouchListener,或者雖然設(shè)置了OnTouchListener词身,但是其onTouch返回了false厅目。另外如果onlongclick返回的是true,那么onclick是會(huì)被屏蔽掉的法严,只有在onlongclick被取消损敷,或者返回false,且是up事件時(shí)深啤,才會(huì)觸發(fā)onclick拗馒,由此可知View幾個(gè)事件的執(zhí)行順序是:
onTouch ---> onTouchEvent ---> onLongClick(false)---> onClick
U型事件流和L型事件流
經(jīng)過(guò)上面的理論分析,再來(lái)看這個(gè)圖是不是就很清楚了溯街,分發(fā)的時(shí)候诱桂,如果都不攔截,處理的時(shí)候呈昔,都不處理挥等,那么完整的事件流就是個(gè)U型事件流。文中的事件圖是參考的別人的博客堤尾,只是為了讓大家加深理解肝劲,如果想了解更多,可以參考Kelin大神的文章:圖解 Android 事件分發(fā)機(jī)制
如果在消費(fèi)事件的時(shí)候郭宝,存在某一個(gè)子View消費(fèi)了DOWN事件辞槐,那么接下來(lái)的ACTION_MOVE 和 ACTION_UP 事件就會(huì)直接交給該子View的onTouchEvent處理,即就是我們常說(shuō)的L型事件流粘室。
紅色的箭頭代表ACTION_DOWN 事件的流向
藍(lán)色的箭頭代表ACTION_MOVE 和 ACTION_UP 事件的流向
總結(jié)下:
對(duì)于在onTouchEvent消費(fèi)事件的情況:在哪個(gè)View的onTouchEvent 返回true榄檬,那么ACTION_MOVE和ACTION_UP的事件從上往下傳到這個(gè)View后就不再往下傳遞了,而直接傳給自己的onTouchEvent 并結(jié)束本次事件傳遞過(guò)程衔统。
對(duì)于ACTION_MOVE鹿榜、ACTION_UP總結(jié):ACTION_DOWN事件在哪個(gè)控件消費(fèi)了(return true)先朦, 那么ACTION_MOVE和ACTION_UP就會(huì)從上往下(通過(guò)dispatchTouchEvent)做事件分發(fā)往下傳,就只會(huì)傳到這個(gè)控件犬缨,不會(huì)繼續(xù)往下傳喳魏,如果ACTION_DOWN事件是在dispatchTouchEvent消費(fèi),那么事件到此為止停止傳遞怀薛,如果ACTION_DOWN事件是在onTouchEvent消費(fèi)的刺彩,那么會(huì)把ACTION_MOVE或ACTION_UP事件傳給該控件的onTouchEvent處理并結(jié)束傳遞。
如何解決滑動(dòng)沖突
經(jīng)過(guò)前面的分析枝恋,我們知道子View可以通過(guò)調(diào)用父容器的requestDisallowInterceptTouchEvent(true/false)方法控制父容器是否對(duì)事件進(jìn)行攔截创倔,解決滑動(dòng)沖突也是基于這一點(diǎn)進(jìn)行處理的。當(dāng)然DOWN事件是不能攔截的焚碌,因?yàn)槿绻鸇OWN事件被攔截了畦攘,那么子View就永遠(yuǎn)無(wú)法獲取到事件了。
解決滑動(dòng)沖突有兩種方法:一種是內(nèi)部攔截法十电,另一種是外部攔截法知押。
1.內(nèi)部攔截法
是指我們可以重寫(xiě)child的dispatchTouchEvent方法,判斷是否需要讓parent攔截事件鹃骂,需要修改父容器的onInterceptTouchEvent方法台盯,和子View的dispatchTouchEvent,代碼如下:
//內(nèi)部攔截法(父容器的onInterceptTouchEvent)
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
//down事件不能攔截畏线,否則静盅,子view就接收不到事件了
if (ev.getAction() == MotionEvent.ACTION_DOWN){
return false;
}
return true;
}
//2.內(nèi)部攔截法(父容器-->onInterceptTouchEvent, 子View-->dispatchTouchEvent)
private float lastX = 0;
private float lastY = 0;
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
float x = ev.getX();
float y = ev.getY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
//true 會(huì)使得父容器 dispatchTouchEvent中的disallowIntercept為true,
//導(dǎo)致父容器不會(huì)調(diào)用onInterceptTouchEvent寝殴,即不會(huì)攔截
getParent().requestDisallowInterceptTouchEvent(true);
lastX = x;
lastY = y;
break;
case MotionEvent.ACTION_MOVE:
float moveX = lastX - x;
float moveY = lastY - y;
//false 會(huì)使得父容器 dispatchTouchEvent中的disallowIntercept為false蒿叠,
//導(dǎo)致父容器會(huì)調(diào)用onInterceptTouchEvent,即會(huì)攔截事件
if (Math.abs(moveX) > Math.abs(moveY) + 5) {//橫向滑動(dòng)大于縱向滑動(dòng)蚣常,攔截事件
getParent().requestDisallowInterceptTouchEvent(false);
}
break;
case MotionEvent.ACTION_UP:
//此處設(shè)置不設(shè)置都不會(huì)影響后續(xù)的流程市咽,up時(shí),不會(huì)走到這里史隆,都是由父容器處理
getParent().requestDisallowInterceptTouchEvent(true);
break;
default:
break;
}
return super.dispatchTouchEvent(ev);
}
2.外部攔截法(只需修改父容器的onInterceptTouchEvent事件)
是指我們可以重寫(xiě)parent的onInterceptTouchEvent方法魂务,判斷當(dāng)前的事件是否需要攔截,代碼如下:
private float lastX = 0;
private float lastY = 0;
//1.外部攔截法 (父容器-->onInterceptTouchEvent)
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
float x = ev.getX();
float y = ev.getY();
boolean intercept = false;
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
lastX = x;
lastY = y;
intercept = false;
break;
case MotionEvent.ACTION_MOVE:
float moveX = lastX - x;
float moveY = lastY - y;
if (Math.abs(moveX) > Math.abs(moveY) + 5) {//橫向滑動(dòng)大于縱向滑動(dòng)泌射,攔截事件
intercept = true;
} else {
intercept = false;
}
break;
case MotionEvent.ACTION_UP:
intercept = false;
break;
default:
break;
}
return intercept;
}