導(dǎo)讀
- 移動開發(fā)知識體系總章(Java基礎(chǔ)诺祸、Android景埃、Flutter)
- Android 的事件分發(fā)機(jī)制
Android 的事件分發(fā)機(jī)制
- 事件分發(fā)的定義
- 事件分發(fā)的源碼分析
- 通過自定義View分析事件分發(fā)的攔截邏輯
- 疑問:Activity是怎么接收到事件的域那?
- 疑問:ViewGroup是如何判斷該把事件傳遞給那個child的活逆?
- 總結(jié)
事件分發(fā)的定義
在Android中的事件分發(fā),就是將點擊事件傳遞到某個具體的View的進(jìn)行處理的整個過程剪决。
舉個不恰當(dāng)?shù)牧凶恿橥簦愦蜷_手機(jī)通訊錄檀训,找到你的女神,點擊了她的電話享言,然后手機(jī)就會進(jìn)入撥打界面的這一整個過程峻凫。你最終會通過手機(jī)接收到女神是否接了電話都有一個直觀的反饋。
那么我們重點關(guān)注事件分發(fā)的順序览露,和核心的方法:
- 事件分發(fā)的順序
- 事件分發(fā)的核心方法
事件分發(fā)的順序
在Android中UI通常是Activity+ViewGroup+View組合而成:
類型 | 簡介 | 說明 |
---|---|---|
Activity | 控制生命周期蔚晨、處理事件 | 統(tǒng)籌視圖的添加、顯示肛循;與Window、View交互 |
View | 所有UI組件的父類 | Button银择、TextView等控件都是View的子類 |
ViewGroup | 含多個View的容器 | 也是View的子類多糠;是所有布局的父類 |
事件分發(fā)則先到Activity,再到容器(ViewGroup)浩考,再精準(zhǔn)到具體View夹孔。具體的View往往是實際處理者。
事件分發(fā)的核心方法
dispatchTouchEvent() 析孽、onTouchEvent()搭伤、onInterceptTouchEvent()。
方法 | 功能 | 說明 |
---|---|---|
dispatchTouchEvent() | 分發(fā)點擊事件 | 當(dāng)點擊事件能夠傳遞給當(dāng)前View時袜瞬,調(diào)用該方法 |
onTouchEvent() | 處理點擊事件 | 在dispatchTouchEvent()內(nèi)部調(diào)用怜俐,false表示不處理事件 |
onInterceptTouchEvent() | 事件攔截 | 在ViewGroup的dispatchTouchEvent()內(nèi)部調(diào)用,ViewGroup獨有的方法 |
結(jié)合事件分發(fā)的順序和核心方法邓尤,這里引用一張經(jīng)典的流程圖:
總結(jié)一下拍鲤,整體流程就是到了每個層面,攔不攔截汞扎,處不處理季稳,如果不攔截不處理,最終回流到Activity行成閉環(huán)澈魄, 事件分發(fā)機(jī)制就是逐級逐層景鼠,去尋找一個有消費能力的。
舉個不恰當(dāng)?shù)牧凶樱?br> 老板Activity某天醉酒時接了一個“xxxx”相關(guān)的項目痹扇,然后交給了項目總監(jiān)ViewGroup985铛漓,
項目總監(jiān)ViewGroup985一聽這玩意兒沒經(jīng)驗,就問了問下面最得力的項目經(jīng)理ViewGroup996會不會搞
項目經(jīng)理ViewGroup996聽的一臉尷尬鲫构,就叫來了主管ViewGroup035
主管ViewGroup035說我還是問問小組長吧
小組長View251一臉懵逼票渠,就去問了程序員View404
程序員View404給小組長View251說這事搞不了
小組長View251等逐級上報最后項目總監(jiān)ViewGroup985跟老板說這貨公司干不了
妥妥的的一個閉環(huán),其中任何一個環(huán)境說能干的這事兒就執(zhí)行下去了
現(xiàn)在我們對事件分發(fā)機(jī)制有了一個清晰的認(rèn)識芬迄,那么源碼具體是如何實現(xiàn)的呢问顷?
事件分發(fā)的源碼分析
老司機(jī)都知道在Android中的頂層View其實是抽象類Window,具體實現(xiàn)類是PhoneWindow,而PhoneWindow的根本是DecorView杜窄,DecorView則是ViewGroup的子類肠骆,點擊查看關(guān)于Android的組成。故此塞耕,我們通過觀察這些類的關(guān)鍵代碼入手蚀腿。
Activity類
的dispatchTouchEvent(MotionEvent ev)方法:
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
- 第一個if邏輯判斷是ACTION_DOWN新的事件,也就是說扫外,當(dāng)用戶進(jìn)行按下動作的時候莉钙,事件就開始了,這里觸發(fā)了onUserInteraction()方法筛谚,該方法返回void的空方法磁玉,并且有一些不常見的用處,這里就不展開了驾讲。
- 第二個邏輯是去判斷
Window類
下的 superDispatchTouchEvent(ev)方法蚊伞,前面說到了Window類
是抽象類,具體實現(xiàn)類是PhoneWindow類
,那么我們在Android Studio中雙擊SHUFT鍵搜索該類吮铭,并找到superDispatchTouchEvent(ev)方法:
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
- 在
PhoneWindow類
下的superDispatchTouchEvent(ev)方法中直接使用返回了變量mDecor的superDispatchTouchEvent(event)方法时迫,變量mDecor即DecorView類
,這里也不展開了谓晌,繼續(xù)看核心方法superDispatchTouchEvent(event):
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
- 在
DecorView類
下的superDispatchTouchEvent(MotionEvent event)方法中則直接調(diào)用了父類方法掠拳,前面說到了DecorView類
其實是ViewGroup類
的子類,所以我們需要繼續(xù)追蹤ViewGroup類
的dispatchTouchEvent(event)方法纸肉,由于該方法非常的復(fù)雜和龐大碳想,這里先貼上一段偽代碼
:
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean consume = false;
if (onInterceptTouchEvent(ev)) {
consume = onTouchEvent();
}else {
consume = child.dispatchTouchEvent(ev);
}
return consume;
}
- 這段
偽代碼
簡單明了的講清楚了ViewGroup類
接收到事件后的邏輯,即先調(diào)用onInterceptTouchEvent()詢問是否攔截毁靶,攔截則去走自己的onTouchEvent()方法胧奔,反之去走子類的方法。
現(xiàn)在貼上精簡后的ViewGroup類
的onInterceptTouchEvent()源碼:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
...
if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
...
intercepted = onInterceptTouchEvent(ev);
...
}
...
if (!canceled && !intercepted) {
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
...
}
...
}
...
return handled;
}
- 會進(jìn)入第一個if邏輯去執(zhí)行onInterceptTouchEvent(ev)方法獲取是否攔截
- 第二個if邏輯則是不攔截的情況下會進(jìn)入去執(zhí)行dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)方法(后面會說道攔截的情況下)
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
...
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
...
return handled;
}
- super.dispatchTouchEvent(event)表示執(zhí)行自己父類的方法
- child.dispatchTouchEvent(event)表示事件分發(fā)給了子類预吆。比如一個我們這里點擊的是一個AppCompatButton按鈕龙填,最終執(zhí)行的是AppCompatButton的父類
TextView類
下的**onTouchEvent(MotionEvent event) **方法(其他父類并沒有重寫onTouchEvent()方法):
@Override
public boolean onTouchEvent(MotionEvent event) {}
以上便是對源碼的事件分發(fā)機(jī)制的分析,使用的都是系統(tǒng)默認(rèn)提供的ViewGroup
(ConstraintLayout)拐叉、View
(AppCompatButton),正常情況(無攔截事件
)下的分發(fā)流程岩遗,確實符合前面我們分析的情況。下面我們通過簡單的自定義View來繼續(xù)分析凤瘦。
通過自定義View分析事件分發(fā)的攔截邏輯
這里我們進(jìn)行簡單的自定義ViewGroup+自定義View
來分析攔截和不處理(消費)事件宿礁。
簡單的自定義ViewGroup:
public class MyLinearLayout extends LinearLayout {
public MyLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean isTouch = true;
if (event.getAction() == MotionEvent.ACTION_DOWN) {
System.out.println("ViewGroup===onTouchEvent--"+isTouch);
}
return isTouch;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
boolean isIntercept = false;
if (event.getAction() == MotionEvent.ACTION_DOWN) {
System.out.println("ViewGroup===onInterceptTouchEvent--" + isIntercept);
}
return isIntercept;
}
}
- 重構(gòu)onTouchEvent(MotionEvent event)方法,使用isTouch變量控制是否處理事件
- 重構(gòu)了onInterceptTouchEvent(MotionEvent event)方法蔬芥,使用isIntercept 變量控制是否攔截事件
- 這里沒有重構(gòu)dispatchTouchEvent(MotionEvent event)方法梆靖,因為我們不需要去變更
ViewGroup
的分發(fā)機(jī)制
簡單的自定義View:
public class MyAppCompatButton extends AppCompatButton {
public MyAppCompatButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean isTouch=true;
if(event.getAction()==MotionEvent.ACTION_DOWN){
System.out.println("View===onTouchEvent--"+isTouch);
}
return isTouch;
}
}
-重構(gòu)了onTouchEvent(MotionEvent event)方法控汉,使用isTouch變量控制是否處理事件
具體在xml中的使用:
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.futurenavi.demo1.views.MyLinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
tools:ignore="MissingConstraints">
<com.futurenavi.demo1.views.MyLinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
tools:ignore="MissingConstraints">
<com.futurenavi.demo1.views.MyAppCompatButton
android:id="@+id/my_bt1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="button1~"
tools:ignore="MissingConstraints" />
</com.futurenavi.demo1.views.MyLinearLayout>
</com.futurenavi.demo1.views.MyLinearLayout>
<com.futurenavi.demo1.views.MyLinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
tools:ignore="MissingConstraints">
<com.futurenavi.demo1.views.MyAppCompatButton
android:id="@+id/my_btn2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="button2~"
tools:ignore="MissingConstraints" />
</com.futurenavi.demo1.views.MyLinearLayout>
<com.futurenavi.demo1.views.MyAppCompatButton
android:id="@+id/my_btn3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="button3~"
tools:ignore="MissingConstraints" />
</LinearLayout>
在布局文件中應(yīng)用了3組布局
- 自定義ViewGroup+自定義ViewGroup+自定義View(模擬多重ViewGroup嵌套布局頁面)
- 自定義ViewGroup+自定義View(模擬普通常見布局頁面)
- 自定義自定義View(模擬極簡布局頁面)
最終的Acitvity頁面:
public class ActivityG extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_g);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
System.out.println("Activity===onTouchEvent");
System.out.println("========================");
}
return super.onTouchEvent(event);
}
}
- 在
ActivityG
頁面僅重寫了onTouchEvent(MotionEvent event)方法,主要用于檢驗返吻,都沒有處理事件的時候是否閉環(huán)回到Acitvity姑子。
下面是不同情況下的運行情況:
ViewGroup默認(rèn)不攔截,View處理事件時测僵,依次點擊三個按鈕的打印記錄:
-
自定義ViewGroup+自定義ViewGroup+自定義View時:
2層ViewGroup都沒有攔截,View處理事件處理 -
自定義ViewGroup+自定義View:
1層ViewGroup沒有攔截捍靠,View處理事件處理 -
自定義自定義View:
View處理事件處理
ViewGroup進(jìn)行攔截沐旨,ViewGroup自身響應(yīng)事件,View不處理事件時榨婆,依次點擊三個按鈕的打印記錄:
此時需要把
MyLinearLayout類
下的boolean isIntercept = false;變更為true
,MyAppCompatButton類下的boolean isTouch=true;變更為false
磁携。
-
自定義ViewGroup+自定義ViewGroup+自定義View時:
第一層ViewGroup進(jìn)行攔截,并執(zhí)行自身事件處理璃搜。 -
自定義ViewGroup+自定義View:
同上 -
自定義自定義View:
View不處理事件處理拖吼,Activity響應(yīng)事件。
ViewGroup進(jìn)行攔截这吻,ViewGroup自身不處理事件吊档,依次點擊三個按鈕的打印記錄:
此時需要把
MyLinearLayout類
下的boolean isTouch=true;變更為false
- 由于一層兩層ViewGroup都不會進(jìn)行事件分發(fā)唾糯,這里就只點了按鈕1
-
自定義ViewGroup:
事件攔截怠硼,自身不進(jìn)行處理,Activity響應(yīng)事件
到這里移怯,印證了前面的介紹以及經(jīng)典圖上所繪制的流程香璃,即事件會按照順序一層一層進(jìn)行分發(fā),每一層都會都會判斷是否攔截(ViewGroup)舟误,如果不攔截則繼續(xù)往下葡秒,反之則查看當(dāng)前ViewGroup是否消費事件,若最終找不到有能力消費的嵌溢,則返回到Activity形成閉環(huán)眯牧。
當(dāng)然,截止到目前赖草,還有一點沒有說到学少,即在ViewGroup
攔截事件之后,后續(xù)是如何處理的呢秧骑?下面來看看關(guān)鍵源碼:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
...
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
...
intercepted = onInterceptTouchEvent(ev);
...
}
...
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);
}
...
return handled;
}
- 還是
ViewGroup類
下的dispatchTouchEvent(MotionEvent ev)方法 - 第一個if邏輯中會執(zhí)行onInterceptTouchEvent(ev)查詢是否攔截
- 第二個if邏輯中執(zhí)行的是
不攔截
的邏輯版确,同樣會進(jìn)入dispatchTransformedTouchEvent()方法:
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
...
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
...
return handled;
}
- 前面已知事件不攔截時扣囊,最終會執(zhí)行child.dispatchTouchEvent(event)方法。
- 此時阀坏,事件進(jìn)行了攔截如暖,則執(zhí)行super.dispatchTouchEvent(event)方法,由于我們自定義的
MyLinearLayout類
重寫了onTouchEvent()方法:
public class MyLinearLayout extends LinearLayout {
...
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean isTouch = true;
if (event.getAction() == MotionEvent.ACTION_DOWN) {
System.out.println("ViewGroup===onTouchEvent--"+isTouch);
}
return isTouch;
}
...
}
- 此時的
isTouch = true
忌堂,則MyLinearLayout類
的onTouchEvent()方法消費事件,事件分發(fā)到此結(jié)束盒至。 - 若
isTouch = false
呢,自然是繼續(xù)往上最終執(zhí)行到我們的ActivityG類
的**onTouchEvent(MotionEvent event) **方法士修,這里就不貼代碼了
這里再提一提枷遂,如果想要更好的理解源碼是如何進(jìn)行事件分發(fā)的,最好通過DEBUG跟蹤事件分的邏輯棋嘲,
- 使用DUBUG模式啟動項目
- 在父類Activity類下的dispatchTouchEvent()方法內(nèi)打上斷點
- 在DecorView類下dispatchTouchEvent()方法內(nèi)打上斷點
- 在ViewGroup類下的dispatchTouchEvent()酒唉、dispatchTransformedTouchEvent()方法內(nèi)打上斷點
這里發(fā)現(xiàn)android-29和android-28、android-26略有差異沸移,但不影響本篇內(nèi)容事件分發(fā)機(jī)制的痪伦,看到文章的朋友可以試試雹锣。
另外蕊爵,在ViewGroup類的執(zhí)行dispatchTouchEvent()事件分發(fā),對child進(jìn)行判斷的時候饲齐,可以看到系統(tǒng)源碼中并不是簡單的Activity-PhoneWindow(Window)-DecorView-我們的布局Layout-具體的View,而是:
DecorView@ebcfe75[ActivityG]
android.widget.LinearLayout{93999f3 V.E...... ........ 0,0-1440,2712}
android.widget.FrameLayout{17cada3 V.E...... ........ 0,84-1440,2712}
androidx.appcompat.widget.ActionBarOverlayLayout{ea7fb39 V.E...... ........ 0,0-1440,2628 #7f070056 app:id/decor_content_parent}
androidx.appcompat.widget.ContentFrameLayout{32e548 V.E...... ........ 0,196-1440,2628 #1020002 android:id/content}
androidx.constraintlayout.widget.ConstraintLayout{d004903 V.E...... ........ 0,0-1440,2432}
android.widget.LinearLayout{11a8c3f V.E...... ........ 0,0-1440,2432}
com.futurenavi.demo1.views.MyLinearLayout{9d95de9 V.E...... ........ 0,168-1440,336}
...
- 可以確定DecorView是頂層View(Root View)
- ConstraintLayout為我給ActivityG類的xml中設(shè)定的頂層布局
- LinearLayout為我給ActivityG類的xml中設(shè)定的第二次布局
- MyLinearLayout是前面自定義的ViewGroup
- 另外如果沒有View消費事件咨察,則會倒序再執(zhí)行一次摄狱,完成閉環(huán)
有興趣的朋友不妨試試
待續(xù)
- 疑問:Activity是怎么接收到事件的宪迟?
- 疑問:ViewGroup是如何判斷該把事件傳遞給那個child的?
總結(jié)
我們知道了
第一個if邏輯判斷是ACTION_DOWN新的事件,也就是說玖像,當(dāng)用戶進(jìn)行按下動作的時候祖驱,事件就開始了睡互,這里觸發(fā)了onUserInteraction()方法寇壳,可以看到該方法返回void的空方法泞歉,也就是說,如果想在Activity接收到按下事件做些什么的話挺庞,可以直接重寫onUserInteraction()方法處理掖鱼,這里就不展開了戏挡。
-
第二個邏輯觸發(fā)getWindow().superDispatchTouchEvent(ev)方法,那么先看看getWindow()方法:
@UnsupportedAppUsage private Window mWindow; public Window getWindow() { return mWindow; }
getWindow()返回了一個Window類寻歧,繼續(xù)查看Window類:
/** * Abstract base class for a top-level window look and behavior policy. An * instance of this class should be used as the top-level view added to the * window manager. It provides standard UI policies such as a background, title * area, default key processing, etc. * * <p>The only existing implementation of this abstract class is * android.view.PhoneWindow, which you should instantiate when needing a * Window. */ public abstract class Window { ... public void setCallback(Callback callback) { mCallback = callback; } ... }
發(fā)現(xiàn)Window是抽象類,注釋里大意說用于頂層窗口外觀和行為策略的抽象基類,此類的實例應(yīng)用作添加到窗口管理器磷箕。 它提供標(biāo)準(zhǔn)的UI策略呜叫,例如背景盛泡,標(biāo)題區(qū)域,默認(rèn)密鑰處理等,此抽象類的唯一現(xiàn)有實現(xiàn)是
android.view.PhoneWindow
菱属,需要使用Window時應(yīng)實例化。
這里先關(guān)注三個點:- 用于頂層窗口外觀和行為策略赏陵。
- android.view.PhoneWindow饼齿。
- 需要使用Window時應(yīng)實例化。
繼續(xù)去看看PhoneWindow蝙搔,在Android Studio中雙擊Shift搜索PhoneWindow類缕溉,并查看superDispatchTouchEvent()方法:
... private DecorView mDecor; @Override public boolean superDispatchTouchEvent(MotionEvent event) { return mDecor.superDispatchTouchEvent(event); } ...
這里就牽扯出了DecorView對象:
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks { ... public boolean superDispatchTouchEvent(MotionEvent event) { return super.dispatchTouchEvent(event); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { final Window.Callback cb = mWindow.getCallback(); return cb != null && !mWindow.isDestroyed() && mFeatureId < 0 ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev); } ... }
可以看到DecorView是FrameLayout的子類,是一個ViewGroup吃型。
在dispatchTouchEvent() 方法中用到了Window.Callback():public abstract class Window { ... public void setCallback(Callback callback) { mCallback = callback; } public final Callback getCallback() { return mCallback; } public interface Callback {...} ... }
由于這個callback是在Activity中實現(xiàn)的:
public class Activity extends ContextThemeWrapper implements LayoutInflater.Factory2, Window.Callback, ...{ @UnsupportedAppUsage final void attach(Context context, ActivityThread aThread,...,Window window, ...) { ... mWindow.setCallback(this); ... } }
所以前面DecorView的dispatchTouchEvent(MotionEvent ev) 中的判斷是傳遞給給Activity還是直接傳遞給父類(VIewGroup)的結(jié)果是cb.dispatchTouchEvent()证鸥,即將事件傳遞給了Activity,反之則會走super.dispatchTouchEvent(ev)勤晚,即父類VIewGroup的dispatchTouchEvent()的方法枉层,這里后面在來說。
- 最后執(zhí)行onTouchEvent(ev)赐写,即前面說到的onTouchEvent()在dispatchTouchEvent()中執(zhí)行
結(jié)合前面的三個關(guān)注點:每個Activity包含一個Window鸟蜡,而Window其實是一個PhoneWindow,每個PhoneWindow則包含一個DecorView挺邀,而每個DecorVIew都是一個Framelayout(也就是一個ViewGroup)揉忘。
也就是說,在開發(fā)者層面端铛,頂層窗口是Window泣矛,頂層布局是DecorView(FrameLayout )。
在Activity中的onCreate()方法第一時間會去執(zhí)行了setContentView方法沦补,即設(shè)置頁面UI(布局)乳蓄,前面說到ViewGroup是Android中的布局的父類:
Activity的組成結(jié)構(gòu) | View類及其子類 |
---|---|
場景/疑問/好奇
講講 Android 的事件分發(fā)機(jī)制
查看過源碼中的事件攔截方法嗎咪橙?或者說在進(jìn)行事件分發(fā)的時候如何讓正常的分發(fā)方式進(jìn)行攔截夕膀?
在一個列表中,同時對父 View 和子 View 設(shè)置點擊方法美侦,優(yōu)先響應(yīng)哪個产舞?為什么會這樣?
上面的問題無論是面試還是平時開發(fā)都是很常見/常用的菠剩,各位看官心里是否或清晰或模糊或好奇呢易猫?
在Android中,通常以一個Activity作為一個頁面具壮,在Activity的組成情況中我們知道每個Activity包含一個Window准颓,而Window其實是一個PhoneWindow哈蝇,每個PhoneWindow則包含一個DecorView,而每個DecorVIew都是一個Framelayout攘已。
而事件炮赦,總是有一個觸發(fā)點的,對于手機(jī)的事件通常從用戶按下開始的样勃,事件分發(fā)對應(yīng)著三個重要方法:
- dispatchTouchEvent()
- onTouchEvent()
-
onInterceptTouchEvent()
(一下讓我想起了當(dāng)年在宿舍一遍遍背onInterceptTouchEvent的樣子吠勘,捂臉)
上面三個方法,從字面就非常好理解峡眶,分別是事件分發(fā)剧防、事件處理、事件攔截辫樱,在Android中無論AppCompatActivity峭拘、FragmentActivity都是Activity的子類,
舉個不恰當(dāng)?shù)牧凶邮ㄊ睿珹ctivity接到了一個“水幕電影”的項目棚唆,然后扭頭就轉(zhuǎn)包給了ViewGroup,這個ViewGroup是沒有能力搞這東西的心例,但是又想賺錢就接了宵凌,然后又轉(zhuǎn)包給了另外一個ViewGroup2,正所謂人以類聚止后,這ViewGroup2也是不會搞想賺錢的主瞎惫,最后這個ViewGroup2把這活給了一個專業(yè)對口的View,這個View漂亮的完成了項目译株,引起了一片好評瓜喇。
總結(jié)一下,Activity是第一時間接收到點擊事件的歉糜,然后會分發(fā)給頂層ViewGroup乘寒,頂層ViewGroup會去檢查是否攔截,不攔截則繼續(xù)往下傳遞匪补,若UI復(fù)雜則會重復(fù)該步驟伞辛,直到事件分發(fā)到具體View進(jìn)行事件處理。看起來似乎也是挺簡單明的夯缺,那么蚤氏,通過源碼來看看,Android具體在這如何進(jìn)行三大環(huán)節(jié)中進(jìn)行事件分發(fā)的踊兜。
DecorView@ebcfe75[ActivityG]
android.widget.LinearLayout{93999f3 V.E...... ........ 0,0-1440,2712}
android.widget.FrameLayout{17cada3 V.E...... ........ 0,84-1440,2712}
androidx.appcompat.widget.ActionBarOverlayLayout{ea7fb39 V.E...... ........ 0,0-1440,2628 #7f070056 app:id/decor_content_parent}
androidx.appcompat.widget.ContentFrameLayout{32e548 V.E...... ........ 0,196-1440,2628 #1020002 android:id/content}
androidx.constraintlayout.widget.ConstraintLayout{d004903 V.E...... ........ 0,0-1440,2432}
android.widget.LinearLayout{11a8c3f V.E...... ........ 0,0-1440,2432}
com.futurenavi.demo1.views.MyLinearLayout{9d95de9 V.E...... ........ 0,168-1440,336}