前言
Android事件分發(fā)機制是Android開發(fā)者必須了解的基礎(chǔ)。
目錄
一. 基礎(chǔ)認知
1.1 事件分發(fā)的由來
安卓的View是樹形結(jié)構(gòu)的蒙袍,View可能會重疊在一起腕巡,當我們點擊的地方有多個View都可以響應(yīng)的時候玄坦,這個點擊事件應(yīng)該給誰呢?為了解決這一個問題绘沉,就有了事件分發(fā)機制煎楣。
1.2 事件分發(fā)的 "事件" 是指什么?
點擊事件 (Touch事件) 车伞。具體介紹如下:
特別說明:事件列择懂,即指從手指接觸屏幕至手指離開屏幕這個過程產(chǎn)生的一系列事件。一般情況下另玖,事件列都是以DOWN事件開始困曙、UP事件結(jié)束,中間有無數(shù)的MOVE事件谦去。
1.3 事件分發(fā)的本質(zhì)
將點擊事件(MotionEvent)傳遞到某個具體的View & 處理的整個過程慷丽。’
即 事件傳遞的過程 = 分發(fā)過程鳄哭。
1.4 事件在哪些對象之間進行傳遞要糊?
答:Activity、ViewGroup窃诉、View杨耙。
1.5 事件分發(fā)的順序
即 事件傳遞的順序:Activity
-> ViewGroup
-> View
即:1個點擊事件發(fā)生后赤套,事件先傳到
Activity
、再傳到ViewGroup
珊膜、最終再傳到View
1.6 事件分發(fā)過程由哪些方法協(xié)作完成容握?
答:dispatchTouchEvent()
、onInterceptTouchEvent()
和 onTouchEvent()
二. 事件分發(fā)機制流程詳細分析
主要包括:Activity
事件分發(fā)機制车柠、ViewGroup
事件分發(fā)機制剔氏、View
事件分發(fā)機制
2.1 Activity事件分發(fā)機制
事件分發(fā)機制,首先會將點擊事件傳遞到Activity
中竹祷,具體是執(zhí)行dispatchTouchEvent()
進行事件分發(fā)谈跛。
源碼分析
/**
* 源碼分析:Activity.dispatchTouchEvent()
*/
public boolean dispatchTouchEvent(MotionEvent ev) {
// 僅貼出核心代碼
// ->>分析1
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
// 若getWindow().superDispatchTouchEvent(ev)的返回true
// 則Activity.dispatchTouchEvent()就返回true,則方法結(jié)束塑陵。即 :該點擊事件停止往下傳遞 & 事件傳遞過程結(jié)束
// 否則:繼續(xù)往下調(diào)用Activity.onTouchEvent
}
// ->>分析3
return onTouchEvent(ev);
}
/**
* 分析1:getWindow().superDispatchTouchEvent(ev)
* 說明:
* a. getWindow() = 獲取Window類的對象
* b. Window類是抽象類感憾,其唯一實現(xiàn)類 = PhoneWindow類
* c. Window類的superDispatchTouchEvent() = 1個抽象方法,由子類PhoneWindow類實現(xiàn)
*/
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
// mDecor = 頂層View(DecorView)的實例對象
// ->> 分析2
}
/**
* 分析2:mDecor.superDispatchTouchEvent(event)
* 定義:屬于頂層View(DecorView)
* 說明:
* a. DecorView類是PhoneWindow類的一個內(nèi)部類
* b. DecorView繼承自FrameLayout令花,是所有界面的父類
* c. FrameLayout是ViewGroup的子類阻桅,故DecorView的間接父類 = ViewGroup
*/
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
// 調(diào)用父類的方法 = ViewGroup的dispatchTouchEvent()
// 即將事件傳遞到ViewGroup去處理,詳細請看后續(xù)章節(jié)分析的ViewGroup的事件分發(fā)機制
}
// 回到最初的分析2入口處
/**
* 分析3:Activity.onTouchEvent()
* 調(diào)用場景:當一個點擊事件未被Activity下任何一個View接收/處理時兼都,就會調(diào)用該方法
*/
public boolean onTouchEvent(MotionEvent event) {
// ->> 分析5
if (mWindow.shouldCloseOnTouch(this, event)) {
finish();
return true;
}
return false;
// 即 只有在點擊事件在Window邊界外才會返回true嫂沉,一般情況都返回false,分析完畢
}
/**
* 分析4:mWindow.shouldCloseOnTouch(this, event)
* 作用:主要是對于處理邊界外點擊事件的判斷:是否是DOWN事件扮碧,event的坐標是否在邊界內(nèi)等
*/
public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
if (mCloseOnTouchOutside && event.getAction() == MotionEvent.ACTION_DOWN
&& isOutOfBounds(context, event) && peekDecorView() != null) {
// 返回true:說明事件在邊界外趟章,即 消費事件
return true;
}
// 返回false:在邊界內(nèi),即未消費(默認)
return false;
}
源碼總結(jié)
當一個點擊事件發(fā)生時慎王,從Activity的事件分發(fā)開始(Activity.dispatchTouchEvent()
)蚓土,流程總結(jié)如下:
核心方法總結(jié)
主要包括:dispatchTouchEvent()、onTouchEvent() 總結(jié)如下:
那么ViewGroup
的dispatchTouchEvent()
什么時候返回true / false? 請繼續(xù)往下看ViewGroup
事件的分發(fā)機制柬祠。
2.2 ViewGroup事件分發(fā)機制
從上面Activity
的事件分發(fā)機制可知北戏,在Activity.dispatchTouchEvent()
實現(xiàn)了將事件從Activity
->ViewGroup
的傳遞负芋,ViewGroup
的事件分發(fā)機制從dispatchTouchEvent()
開始漫蛔。
源碼分析
/**
* 源碼分析:ViewGroup.dispatchTouchEvent()
*/
public boolean dispatchTouchEvent(MotionEvent ev) {
// 僅貼出關(guān)鍵代碼
...
if (disallowIntercept || !onInterceptTouchEvent(ev)) {
// 分析1:ViewGroup每次事件分發(fā)時,都需調(diào)用onInterceptTouchEvent()詢問是否攔截事件
// 判斷值1-disallowIntercept:是否禁用事件攔截的功能(默認是false)旧蛾,可通過調(diào)用requestDisallowInterceptTouchEvent()修改
// 判斷值2-!onInterceptTouchEvent(ev) :對onInterceptTouchEvent()返回值取反
// a. 若在onInterceptTouchEvent()中返回false莽龟,即不攔截事件,從而進入到條件判斷的內(nèi)部
// b. 若在onInterceptTouchEvent()中返回true锨天,即攔截事件毯盈,從而跳出了該條件判斷
// c. 關(guān)于onInterceptTouchEvent() ->>分析1
// 分析2
// 1. 通過for循環(huán),遍歷當前ViewGroup下的所有子View
for (int i = count - 1; i >= 0; i--) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
|| child.getAnimation() != null) {
child.getHitRect(frame);
// 2. 判斷當前遍歷的View是不是正在點擊的View病袄,從而找到當前被點擊的View
if (frame.contains(scrolledXInt, scrolledYInt)) {
final float xc = scrolledXFloat - child.mLeft;
final float yc = scrolledYFloat - child.mTop;
ev.setLocation(xc, yc);
child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
// 3. 條件判斷的內(nèi)部調(diào)用了該View的dispatchTouchEvent()
// 即 實現(xiàn)了點擊事件從ViewGroup到子View的傳遞(具體請看下面章節(jié)介紹的View事件分發(fā)機制)
if (child.dispatchTouchEvent(ev)) {
// 調(diào)用子View的dispatchTouchEvent后是有返回值的
// 若該控件可點擊搂赋,那么點擊時dispatchTouchEvent的返回值必定是true赘阀,因此會導(dǎo)致條件判斷成立
// 于是給ViewGroup的dispatchTouchEvent()直接返回了true,即直接跳出
// 即該子View把ViewGroup的點擊事件消費掉了
mMotionTarget = child;
return true;
}
}
}
}
}
}
...
return super.dispatchTouchEvent(ev);
// 若無任何View接收事件(如點擊空白處)/ViewGroup本身攔截了事件(復(fù)寫了onInterceptTouchEvent()返回true)
// 會調(diào)用ViewGroup父類的dispatchTouchEvent()脑奠,即View.dispatchTouchEvent()
// 因此會執(zhí)行ViewGroup的onTouch() -> onTouchEvent() -> performClick() -> onClick()基公,即自己處理該事件,事件不會往下傳遞
// 具體請參考View事件分發(fā)機制中的View.dispatchTouchEvent()
...
}
/**
* 分析1:ViewGroup.onInterceptTouchEvent()
* 作用:是否攔截事件
* 說明:
* a. 返回false:不攔截(默認)
* b. 返回true:攔截宋欺,即事件停止往下傳遞(需手動復(fù)寫onInterceptTouchEvent()其返回true)
*/
public boolean onInterceptTouchEvent(MotionEvent ev) {
// 默認不攔截
return false;
}
// 回到調(diào)用原處
源碼總結(jié)
Android事件分發(fā)傳遞到Acitivity
后轰豆,總是先傳遞到ViewGroup
、再傳遞到View
齿诞。流程總結(jié)如下:(假設(shè)已經(jīng)經(jīng)過了Acitivity事件分發(fā)傳遞并傳遞到ViewGroup
)
核心方法總結(jié)
主要包括:dispatchTouchEvent()
酸休、onTouchEvent()
、onInterceptTouchEvent()
總結(jié)如下:
實例分析
1. 布局說明
2. 測試代碼
布局文件:activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/my_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:focusableInTouchMode="true"
android:orientation="vertical">
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="按鈕1" />
<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="按鈕2" />
</LinearLayout>
核心代碼:MainActivity.java
public class MainActivity extends AppCompatActivity {
Button button1,button2;
ViewGroup myLayout;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button1 = (Button)findViewById(R.id.button1);
button2 = (Button)findViewById(R.id.button2);
myLayout = (LinearLayout)findViewById(R.id.my_layout);
// 1.為ViewGroup布局設(shè)置監(jiān)聽事件
myLayout.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d("TAG", "點擊了ViewGroup");
}
});
// 2. 為按鈕1設(shè)置監(jiān)聽事件
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d("TAG", "點擊了button1");
}
});
// 3. 為按鈕2設(shè)置監(jiān)聽事件
button2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d("TAG", "點擊了button2");
}
});
}
}
3. 測試結(jié)果
// 點擊按鈕1祷杈,輸出如下
點擊了button1
// 點擊按鈕2斑司,輸出如下
點擊了button2
// 點擊空白處,輸出如下
點擊了ViewGroup
4. 結(jié)果分析
- 點擊Button時但汞,因為
ViewGroup
默認不攔截陡厘,所以事件會傳遞到子ViewButton
,于是執(zhí)行Button.onClick()
特占。- 此時
ViewGroup. dispatchTouchEvent()
會直接返回true
糙置,所以ViewGroup
自身不會處理該事件,于是ViewGroupLayout
的dispatchTouchEvent()
不會執(zhí)行是目,所以注冊的onTouch()
不會執(zhí)行谤饭,即onTouchEvent()
->performClick()
->onClick()
整個鏈路都不會執(zhí)行,所以最后不會執(zhí)行ViewGroup
設(shè)置的onClick()
里懊纳。- 點擊空白區(qū)域時揉抵,
ViewGroup. dispatchTouchEvent()
里遍歷所有子View希望找到被點擊子View時找不到,所以ViewGroup
自身會處理該事件嗤疯,于是執(zhí)行onTouchEvent()
->performClick()
->onClick()
冤今,最終執(zhí)行ViewGroupLayout
的設(shè)置的onClick()
。
2.3 View事件分發(fā)機制
從上面ViewGroup
事件分發(fā)機制知道茂缚,View事件分發(fā)機制從dispatchTouchEvent()
開始
源碼分析
/**
* 源碼分析:View.dispatchTouchEvent()
*/
public boolean dispatchTouchEvent(MotionEvent event) {
if ( (mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener != null &&
mOnTouchListener.onTouch(this, event)) {
return true;
}
return onTouchEvent(event);
}
// 說明:只有以下3個條件都為真戏罢,dispatchTouchEvent()才返回true;否則執(zhí)行onTouchEvent()
// 1. (mViewFlags & ENABLED_MASK) == ENABLED
// 2. mOnTouchListener != null
// 3. mOnTouchListener.onTouch(this, event)
// 下面對這3個條件逐個分析
/**
* 條件1:(mViewFlags & ENABLED_MASK) == ENABLED
* 說明:
* 1. 該條件是判斷當前點擊的控件是否enable
* 2. 由于很多View默認enable脚囊,故該條件恒定為true(除非手動設(shè)置為false)
*/
/**
* 條件2:mOnTouchListener != null
* 說明:
* 1. mOnTouchListener變量在View.setOnTouchListener()里賦值
* 2. 即只要給控件注冊了Touch事件龟糕,mOnTouchListener就一定被賦值(即不為空)
*/
public void setOnTouchListener(OnTouchListener l) {
mOnTouchListener = l;
}
/**
* 條件3:mOnTouchListener.onTouch(this, event)
* 說明:
* 1. 即回調(diào)控件注冊Touch事件時的onTouch();
* 2. 需手動復(fù)寫設(shè)置悔耘,具體如下(以按鈕Button為例)
*/
button.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
return false;
// 若在onTouch()返回true讲岁,就會讓上述三個條件全部成立,從而使得View.dispatchTouchEvent()直接返回true,事件分發(fā)結(jié)束
// 若在onTouch()返回false缓艳,就會使得上述三個條件不全部成立校摩,從而使得View.dispatchTouchEvent()中跳出If,執(zhí)行onTouchEvent(event)
// onTouchEvent()源碼分析 -> 分析1
}
});
/**
* 分析1:onTouchEvent()
*/
public boolean onTouchEvent(MotionEvent event) {
... // 僅展示關(guān)鍵代碼
// 若該控件可點擊阶淘,則進入switch判斷中
if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
// 根據(jù)當前事件類型進行判斷處理
switch (event.getAction()) {
// a. 事件類型=抬起View(主要分析)
case MotionEvent.ACTION_UP:
performClick();
// ->>分析2
break;
// b. 事件類型=按下View
case MotionEvent.ACTION_DOWN:
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
break;
// c. 事件類型=結(jié)束事件
case MotionEvent.ACTION_CANCEL:
refreshDrawableState();
removeTapCallback();
break;
// d. 事件類型=滑動View
case MotionEvent.ACTION_MOVE:
final int x = (int) event.getX();
final int y = (int) event.getY();
int slop = mTouchSlop;
if ((x < 0 - slop) || (x >= getWidth() + slop) ||
(y < 0 - slop) || (y >= getHeight() + slop)) {
removeTapCallback();
if ((mPrivateFlags & PRESSED) != 0) {
removeLongPressCallback();
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
}
}
break;
}
// 若該控件可點擊秧耗,就一定返回true
return true;
}
// 若該控件不可點擊,就一定返回false
return false;
}
/**
* 分析2:performClick()
*/
public boolean performClick() {
if (mOnClickListener != null) {
// 只要通過setOnClickListener()為控件View注冊1個點擊事件
// 那么就會給mOnClickListener變量賦值(即不為空)
// 則會往下回調(diào)onClick() & performClick()返回true
playSoundEffect(SoundEffectConstants.CLICK);
mOnClickListener.onClick(this);
return true;
}
return false;
}
源碼總結(jié)
這里需要特別注意的是舶治,
onTouch()
的執(zhí)行 先于onClick()
核心方法總結(jié)
主要包括: dispatchTouchEvent()
分井、onTouchEvent()
實例分析
在本示例中,將分析2種情況:
- 注冊Touch事件監(jiān)聽 且 在
onTouch()
返回false
- 注冊Touch事件監(jiān)聽 且 在
onTouch()
返回true
分析1:注冊Touch事件監(jiān)聽 且 在onTouch()返回false
代碼示例
// 1. 注冊Touch事件監(jiān)聽setOnTouchListener 且 在onTouch()返回false
button.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
System.out.println("執(zhí)行了onTouch(), 動作是:" + event.getAction());
return false;
}
});
// 2. 注冊點擊事件OnClickListener()
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
System.out.println("執(zhí)行了onClick()");
}
});
測試結(jié)果
執(zhí)行了onTouch(), 動作是:0
執(zhí)行了onTouch(), 動作是:1
執(zhí)行了onClick()
測試結(jié)果說明
- 點擊按鈕會產(chǎn)生兩個類型的事件-按下View與抬起View霉猛,所以會回調(diào)兩次
onTouch()
尺锚; - 因為
onTouch()
返回了false
,所以事件無被消費惜浅,會繼續(xù)往下傳遞瘫辩,即調(diào)用View.onTouchEvent()
; - 調(diào)用
View.onTouchEvent()
時坛悉,對于抬起View事件伐厌,在調(diào)用performClick()
時,因為設(shè)置了點擊事件裸影,所以會回調(diào)onClick()
挣轨。
分析2:注冊Touch事件監(jiān)聽 且 在onTouch()返回true
代碼示例
// 1. 注冊Touch事件監(jiān)聽setOnTouchListener 且 在onTouch()返回false
button.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
System.out.println("執(zhí)行了onTouch(), 動作是:" + event.getAction());
return true;
}
});
// 2. 注冊點擊事件OnClickListener()
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
System.out.println("執(zhí)行了onClick()");
}
});
測試結(jié)果
執(zhí)行了onTouch(), 動作是:0
執(zhí)行了onTouch(), 動作是:1
測試結(jié)果說明
- 點擊按鈕會產(chǎn)生兩個類型的事件-按下
View
與抬起View
,所以會回調(diào)兩次onTouch()
轩猩; - 因為
onTouch()
返回true
卷扮,所以事件被消費,不會繼續(xù)往下傳遞均践,View.dispatchTouchEvent()
直接返回true
晤锹; - 所以最終不會調(diào)用
View.onTouchEvent()
,也不會調(diào)用onClick()
彤委。
三. 事件分發(fā)機制流程總結(jié)
這個三個方法均有一個 boolean(布爾) 類型的返回值鞭铆,通過返回 true 和 false 來控制事件傳遞的流程。
PS: 從上表可以看到
Activity
和 View
都是沒有事件攔截的焦影,這是因為:
- Activity 作為原始的事件分發(fā)者车遂,如果 Activity 攔截了事件會導(dǎo)致整個屏幕都無法響應(yīng)事件,這肯定不是我們想要的效果偷办。
- View最為事件傳遞的最末端艰额,要么消費掉事件澄港,要么不處理進行回傳椒涯,根本沒必要進行事件攔截。
View相關(guān)
Question: 為什么 View 會有 dispatchTouchEvent ?
A:View
可以注冊很多事件監(jiān)聽器回梧,例如:單擊事件(onClick
)废岂、長按事件(onLongClick
)祖搓、觸摸事件(onTouch
),并且View自身也有onTouchEvent
方法湖苞,那么問題來了拯欧,這么多與事件相關(guān)的方法應(yīng)該由誰管理?毋庸置疑就是 dispatchTouchEvent
财骨,所以 View 也會有事件分發(fā)镐作。
Question: 與 View 事件相關(guān)的各個方法調(diào)用順序是怎樣的?
A:如果不去看源碼隆箩,想一下讓自己設(shè)計會怎樣该贾?
- 單擊事件(
onClickListener
) 需要兩個事件(ACTION_DOWN
和ACTION_UP
)才能觸發(fā),如果先分配給onClick
判斷捌臊,等它判斷完杨蛋,用戶手指已經(jīng)離開屏幕,黃花菜都涼了理澎,定然造成 View 無法響應(yīng)其他事件逞力,應(yīng)該最后調(diào)用。(最后) - 長按事件(
onLongClickListener
) 同理糠爬,也是需要長時間等待才能出結(jié)果寇荧,肯定不能排到前面,但因為不需要ACTION_UP
执隧,應(yīng)該排在onClick
前面砚亭。(onLongClickListener
>onClickListener
) - 觸摸事件(
onTouchListener
) , 如果用戶注冊了觸摸事件殴玛,說明用戶要自己處理觸摸事件捅膘,這個應(yīng)該排在最前面。(最前)滚粟、 - View自身處理(
onTouchEvent
) 提供了一種默認的處理方式寻仗,如果用戶已經(jīng)處理好了,也就不需要了凡壤,所以應(yīng)該排在onTouchListener
后面署尤。(onTouchListener
>onTouchEvent
)
所以事件的調(diào)度順序應(yīng)該是 onTouchListener
> onTouchEvent
> onLongClickListener
> onClickListener
。
ViewGroup相關(guān)
ViewGroup
(通常是各種Layout
) 的事件分發(fā)相對來說就要麻煩一些亚侠,因為 ViewGroup
不僅要考慮自身曹体,還要考慮各種ChildView
,一旦處理不好就容易引起各種事件沖突硝烂,正所謂養(yǎng)兒方知父母難啊箕别。
VIewGroup
的事件分發(fā)流程又是如何的呢?
我們了解到事件是通過ViewGroup
一層一層傳遞的,最終傳遞給View
串稀,ViewGroup
要比它的 ChildView
先拿到事件除抛,并且有權(quán)決定是否告訴要告訴ChildView
。在默認的情況下 ViewGroup
事件分發(fā)流程是這樣的母截。
- 判斷自身是否需要(詢問
onInterceptTouchEvent
是否攔截)到忽,如果需要,調(diào)用自己的onTouchEvent
清寇。 - 自身不需要或者不確定喘漏,則詢問
ChildView
,一般來說是調(diào)用手指觸摸位置的ChildView
华烟。 - 如果子
ChildView
不需要則調(diào)用自身的onTouchEvent
陷遮。
用偽代碼應(yīng)該是這樣子的:
// 點擊事件產(chǎn)生后
// 步驟1:調(diào)用dispatchTouchEvent()
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean consume = false; //代表 是否會消費事件
// 步驟2:判斷是否攔截事件
if (onInterceptTouchEvent(ev)) {
// a. 若攔截,則將該事件交給當前View進行處理
// 即調(diào)用onTouchEvent()去處理點擊事件
consume = onTouchEvent (ev) ;
} else {
// b. 若不攔截垦江,則將該事件傳遞到下層
// 即 下層元素的dispatchTouchEvent()就會被調(diào)用帽馋,重復(fù)上述過程
// 直到點擊事件被最終處理為止
consume = child.dispatchTouchEvent (ev) ;
}
// 步驟3:最終返回通知 該事件是否被消費(接收 & 處理)
return consume;
}
}
安卓為了保證所有的事件都是被一個 View 消費的,對第一次的事件( ACTION_DOWN )進行了特殊判斷比吭,View 只有消費了 ACTION_DOWN 事件绽族,才能接收到后續(xù)的事件(可點擊控件會默認消費所有事件),并且會將后續(xù)所有事件傳遞過來衩藤,不會再傳遞給其他 View吧慢,除非上層 View 進行了攔截。如果上層 View 攔截了當前正在處理的事件赏表,會收到一個 ACTION_CANCEL
检诗,表示當前事件已經(jīng)結(jié)束,后續(xù)事件不會再傳遞過來瓢剿。
核心要點
- 事件分發(fā)原理: 責(zé)任鏈模式逢慌,事件層層傳遞,直到被消費间狂。
- View 的
dispatchTouchEvent
主要用于調(diào)度自身的監(jiān)聽器和onTouchEvent
攻泼。- View的事件的調(diào)度順序是
onTouchListener
>onTouchEvent
>onLongClickListener
>onClickListener
。- 不論
View
自身是否注冊點擊事件鉴象,只要View
是可點擊的就會消費事件忙菠。- 事件是否被消費由返回值決定,
true
表示消費纺弊,false
表示不消費牛欢,與是否使用了事件無關(guān)。- ViewGroup 中可能有多個
ChildView
時淆游,將事件分配給包含點擊位置的 ChildView傍睹。- ViewGroup 和 ChildView 同時注冊了事件監(jiān)聽器(
onClick
等)隔盛,由 ChildView 消費。- 一次觸摸流程中產(chǎn)生事件應(yīng)被同一
View
消費焰望,全部接收或者全部拒絕骚亿。- 只要接受
ACTION_DOWN
就意味著接受所有的事件已亥,拒絕ACTION_DOWN
則不會收到后續(xù)內(nèi)容熊赖。- 如果當前正在處理的事件被上層 View 攔截,會收到一個
ACTION_CANCEL
虑椎,后續(xù)事件不會再傳遞過來震鹉。