Android高階進階之路【二】十分鐘徹底弄明白 View 事件分發機制
-
Android
事件分發機制是Android
開發者必須瞭解的基礎 -
網上有大量關於
Android
事件分發機制的文章,但存在一些問題: 內容不全、思路不清晰、無原始碼分析、簡單問題複雜化等等 -
今天,我將全面總結
Android
的事件分發機制,我能保證這是 市面上的最全面、最清晰、最易懂的
本文秉著“結論先行、詳細分析在後”的原則,即先讓大家感性認識,再透過理性分析從而理解問題;
所以,請各位讀者先記住結論,再往下繼續看分析;
-
文章較長,閱讀需要較長時間,建議收藏等充足時間再進行閱讀
目錄
1 . 基礎認知
1.1 事件分發的”事件“是指什麼?
答:點選事件(
Touch
事件)
。具體介紹如下:
image
此處需要特別說明:事件列,即指從手指接觸螢幕至手指離開螢幕這個過程產生的一系列事件。一般情況下,事件列都是以DOWN事件開始、UP事件結束,中間有無數的MOVE事件。
1.2 事件分發的本質
答:將點選事件(MotionEvent)傳遞到某個具體的
View
& 處理的整個過程
即 事件傳遞的過程 = 分發過程。
1.3 事件在哪些物件之間進行傳遞?
答:Activity、ViewGroup、View
。
Android
的
UI
介面由
Activity
、
ViewGroup
、
View
及其派生類組成
1.4 事件分發的順序
即 事件傳遞的順序:
Activity
->
ViewGroup
->
View
即:1個點選事件發生後,事件先傳到
Activity
、再傳到ViewGroup
、最終再傳到View
1.5 事件分發過程由哪些方法協作完成?
答:dispatchTouchEvent() 、onInterceptTouchEvent()和onTouchEvent()
下文會對這3個方法進行詳細介紹
1.6 總結
-
至此,相信大家已經對
Android
的事件分發有了感性的認知 -
下面,我將詳細介紹
Android
事件分發機制
2 . 事件分發機制流程概述
Android
事件分發流程 =
Activity -> ViewGroup -> View
即:1個點選事件發生後,事件先傳到
Activity
、再傳到ViewGroup
、最終再傳到View
即要想充分理解Android分發機制,本質上是要理解:
-
Activity
對點選事件的分發機制 -
ViewGroup
對點選事件的分發機制 -
View
對點選事件的分發機制
下面,我將透過原始碼,全面解析 事件分發機制
即按順序講解:
Activity
事件分發機制、ViewGroup
事件分發機制、View
事件分發機制
3 . 事件分發機制流程詳細分析
主要包括:
Activity
事件分發機制、
ViewGroup
事件分發機制、
View
事件分發機制
流程1:Activity的事件分發機制
Android事件分發機制首先會將點選事件傳遞到Activity中,具體是執行dispatchTouchEvent()進行事件分發。
原始碼分析
/**
* 原始碼分析:Activity.dispatchTouchEvent()
*/
public boolean dispatchTouchEvent(MotionEvent ev) {
// 僅貼出核心程式碼
// ->>分析1
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
// 若getWindow().superDispatchTouchEvent(ev)的返回true
// 則Activity.dispatchTouchEvent()就返回true,則方法結束。即 :該點選事件停止往下傳遞 & 事件傳遞過程結束
// 否則:繼續往下呼叫Activity.onTouchEvent
}
// ->>分析3
return onTouchEvent(ev);
}
/**
* 分析1:getWindow().superDispatchTouchEvent(ev)
* 說明:
* a. getWindow() = 獲取Window類的物件
* b. Window類是抽象類,其唯一實現類 = PhoneWindow類
* c. Window類的superDispatchTouchEvent() = 1個抽象方法,由子類PhoneWindow類實現
*/
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
// mDecor = 頂層View(DecorView)的例項物件
// ->> 分析2
}
/**
* 分析2:mDecor.superDispatchTouchEvent(event)
* 定義:屬於頂層View(DecorView)
* 說明:
* a. DecorView類是PhoneWindow類的一個內部類
* b. DecorView繼承自FrameLayout,是所有介面的父類
* c. FrameLayout是ViewGroup的子類,故DecorView的間接父類 = ViewGroup
*/
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
// 呼叫父類的方法 = ViewGroup的dispatchTouchEvent()
// 即將事件傳遞到ViewGroup去處理,詳細請看後續章節分析的ViewGroup的事件分發機制
}
// 回到最初的分析2入口處
/**
* 分析3:Activity.onTouchEvent()
* 呼叫場景:當一個點選事件未被Activity下任何一個View接收/處理時,就會呼叫該方法
*/
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的座標是否在邊界內等
*/
public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
if (mCloseOnTouchOutside && event.getAction() == MotionEvent.ACTION_DOWN
&& isOutOfBounds(context, event) && peekDecorView() != null) {
// 返回true:說明事件在邊界外,即 消費事件
return true;
}
// 返回false:在邊界內,即未消費(預設)
return false;
}
原始碼總結
當一個點選事件發生時,從
Activity
的事件分發開始(
Activity.dispatchTouchEvent()
),流程總結如下:
核心方法總結
主要包括:dispatchTouchEvent()、onTouchEvent() 總結如下
那麼,
ViewGroup
的
dispatchTouchEvent()
什麼時候返回
true
/
false
?請繼續往下看
ViewGroup事件的分發機制
流程2: ViewGroup的事件分發機制
從上面Activity的事件分發機制可知,在Activity.dispatchTouchEvent()實現了將事件從Activity->ViewGroup的傳遞,ViewGroup的事件分發機制從dispatchTouchEvent()開始。
原始碼分析
/**
* 原始碼分析:ViewGroup.dispatchTouchEvent()
*/
public boolean dispatchTouchEvent(MotionEvent ev) {
// 僅貼出關鍵程式碼
...
if (disallowIntercept || !onInterceptTouchEvent(ev)) {
// 分析1:ViewGroup每次事件分發時,都需呼叫onInterceptTouchEvent()詢問是否攔截事件
// 判斷值1-disallowIntercept:是否禁用事件攔截的功能(預設是false),可透過呼叫requestDisallowInterceptTouchEvent()修改
// 判斷值2-!onInterceptTouchEvent(ev) :對onInterceptTouchEvent()返回值取反
// a. 若在onInterceptTouchEvent()中返回false,即不攔截事件,從而進入到條件判斷的內部
// b. 若在onInterceptTouchEvent()中返回true,即攔截事件,從而跳出了該條件判斷
// c. 關於onInterceptTouchEvent() ->>分析1
// 分析2
// 1\. 透過for迴圈,遍歷當前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\. 條件判斷的內部呼叫了該View的dispatchTouchEvent()
// 即 實現了點選事件從ViewGroup到子View的傳遞(具體請看下面章節介紹的View事件分發機制)
if (child.dispatchTouchEvent(ev)) {
// 呼叫子View的dispatchTouchEvent後是有返回值的
// 若該控制元件可點選,那麼點選時dispatchTouchEvent的返回值必定是true,因此會導致條件判斷成立
// 於是給ViewGroup的dispatchTouchEvent()直接返回了true,即直接跳出
// 即該子View把ViewGroup的點選事件消費掉了
mMotionTarget = child;
return true;
}
}
}
}
}
}
...
return super.dispatchTouchEvent(ev);
// 若無任何View接收事件(如點選空白處)/ViewGroup本身攔截了事件(複寫了onInterceptTouchEvent()返回true)
// 會呼叫ViewGroup父類的dispatchTouchEvent(),即View.dispatchTouchEvent()
// 因此會執行ViewGroup的onTouch() -> onTouchEvent() -> performClick() -> onClick(),即自己處理該事件,事件不會往下傳遞
// 具體請參考View事件分發機制中的View.dispatchTouchEvent()
...
}
/**
* 分析1:ViewGroup.onInterceptTouchEvent()
* 作用:是否攔截事件
* 說明:
* a. 返回false:不攔截(預設)
* b. 返回true:攔截,即事件停止往下傳遞(需手動複寫onInterceptTouchEvent()其返回true)
*/
public boolean onInterceptTouchEvent(MotionEvent ev) {
// 預設不攔截
return false;
}
// 回到呼叫原處
原始碼總結
Android
事件分發傳遞到Acitivity後,總是先傳遞到
ViewGroup
、再傳遞到
View
。流程總結如下:(假設已經經過了Acitivity事件分發傳遞並傳遞到ViewGroup)
核心方法總結
主要包括:dispatchTouchEvent()、onTouchEvent() 、onInterceptTouchEvent()總結如下
例項分析
1 . 佈局說明
2 . 測試程式碼
佈局檔案: activity _ main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="
android:id="@+id/my_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="
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佈局設定監聽事件
myLayout.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d("TAG", "點選了ViewGroup");
}
});
// 2\. 為按鈕1設定監聽事件
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d("TAG", "點選了button1");
}
});
// 3\. 為按鈕2設定監聽事件
button2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d("TAG", "點選了button2");
}
});
}
}
3 . 測試結果
// 點選按鈕1,輸出如下
點選了button1
// 點選按鈕2,輸出如下
點選了button2
// 點選空白處,輸出如下
點選了ViewGroup
4 . 結果分析
-
點選Button時,因為ViewGroup預設不攔截,所以事件會傳遞到子View Button,於是執行Button.onClick()。
-
此時ViewGroup. dispatchTouchEvent()會直接返回true,所以ViewGroup自身不會處理該事件,於是ViewGroupLayout的dispatchTouchEvent()不會執行,所以註冊的onTouch()不會執行,即onTouchEvent() -> performClick() -> onClick()整個鏈路都不會執行,所以最後不會執行ViewGroup設定的onClick()裡。
-
點選空白區域時,ViewGroup. dispatchTouchEvent()裡遍歷所有子View希望找到被點選子View時找不到,所以ViewGroup自身會處理該事件,於是執行onTouchEvent() -> performClick() -> onClick(),最終執行ViewGroupLayout的設定的onClick()
-
-
*
-
流程3:View的事件分發機制
從上面
ViewGroup
事件分發機制知道,
View
事件分發機制從
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;否則執行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(除非手動設定為false)
*/
/**
* 條件2:mOnTouchListener != null
* 說明:
* 1\. mOnTouchListener變數在View.setOnTouchListener()裡賦值
* 2\. 即只要給控制元件註冊了Touch事件,mOnTouchListener就一定被賦值(即不為空)
*/
public void setOnTouchListener(OnTouchListener l) {
mOnTouchListener = l;
}
/**
* 條件3:mOnTouchListener.onTouch(this, event)
* 說明:
* 1\. 即回撥控制元件註冊Touch事件時的onTouch();
* 2\. 需手動複寫設定,具體如下(以按鈕Button為例)
*/
button.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
return false;
// 若在onTouch()返回true,就會讓上述三個條件全部成立,從而使得View.dispatchTouchEvent()直接返回true,事件分發結束
// 若在onTouch()返回false,就會使得上述三個條件不全部成立,從而使得View.dispatchTouchEvent()中跳出If,執行onTouchEvent(event)
// onTouchEvent()原始碼分析 -> 分析1
}
});
/**
* 分析1:onTouchEvent()
*/
public boolean onTouchEvent(MotionEvent event) {
... // 僅展示關鍵程式碼
// 若該控制元件可點選,則進入switch判斷中
if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
// 根據當前事件型別進行判斷處理
switch (event.getAction()) {
// a. 事件型別=抬起View(主要分析)
case MotionEvent.ACTION_UP:
performClick();
// ->>分析2
break;
// b. 事件型別=按下View
case MotionEvent.ACTION_DOWN:
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
break;
// c. 事件型別=結束事件
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變數賦值(即不為空)
// 則會往下回撥onClick() & performClick()返回true
playSoundEffect(SoundEffectConstants.CLICK);
mOnClickListener.onClick(this);
return true;
}
return false;
}
原始碼總結
image
這裡需要特別注意的是,
onTouch()
的執行 先於
onClick()
核心方法總結
主要包括:dispatchTouchEvent()、onTouchEvent()
例項分析
在本示例中,將分析兩種情況:
-
註冊Touch事件監聽 且 在onTouch()返回false
-
註冊Touch事件監聽 且 在onTouch()返回true
分析1:註冊Touch事件監聽 且 在onTouch()返回false
程式碼示例
// 1\. 註冊Touch事件監聽setOnTouchListener 且 在onTouch()返回false
button.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
System.out.println("執行了onTouch(), 動作是:" + event.getAction());
return false;
}
});
// 2\. 註冊點選事件OnClickListener()
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
System.out.println("執行了onClick()");
}
});
測試結果
執行了onTouch(), 動作是:0
執行了onTouch(), 動作是:1
執行了onClick()
測試結果說明
-
點選按鈕會產生兩個型別的事件-按下View與抬起View,所以會回撥兩次onTouch();
-
因為onTouch()返回了false,所以事件無被消費,會繼續往下傳遞,即呼叫View.onTouchEvent();
-
呼叫View.onTouchEvent()時,對於抬起View事件,在呼叫performClick()時,因為設定了點選事件,所以會回撥onClick()。
分析2:註冊Touch事件監聽 且 在onTouch()返回true
程式碼示例
// 1\. 註冊Touch事件監聽setOnTouchListener 且 在onTouch()返回false
button.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
System.out.println("執行了onTouch(), 動作是:" + event.getAction());
return true;
}
});
// 2\. 註冊點選事件OnClickListener()
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
System.out.println("執行了onClick()");
}
});
測試結果
執行了onTouch(), 動作是:0
執行了onTouch(), 動作是:1
測試結果說明
-
點選按鈕會產生兩個型別的事件-按下View與抬起View,所以會回撥兩次onTouch();
-
因為onTouch()返回了true,所以事件被消費,不會繼續往下傳遞,View.dispatchTouchEvent()直接返回true;
-
所以最終不會呼叫View.onTouchEvent(),也不會呼叫onClick()。
至此,關於Android事件分發機制的內容已經講解完畢,即Activity、ViewGroup、View的事件分發機制。 。
即:
Activity
、ViewGroup
、View
的事件分發機制
4 . 總結
在本章節中,將採用大量的圖表從各個角度對Android事件分發機制進行總結。主要包括:
-
工作流程總結
-
業務流程總結
-
以分發物件為核心的總結
-
以方法為核心的總結
4.1 工作流程-總結
Android事件分發流程 = Activity -> ViewGroup -> View,即:1個點選事件發生後,事件先傳到Activity、再傳到ViewGroup、最終再傳到View。
4.2 以分發物件為核心-總結
分發物件主要包括:Activity、ViewGroup、View。
4.3 以方法為核心-總結
事件分發的方法主要包括:dispatchTouchEvent()、onInterceptTouchEvent()和onTouchEvent()。
這裡需要特別注意的是:
-
注意點1:左側虛線代表具備相關性及逐層返回;
-
注意點2:各層dispatchTouchEvent() 返回true的情況保持一致(圖中虛線) 原因是:上層dispatchTouchEvent() 的返回true情況 取決於 下層dispatchTouchEvent() 是否返回ture,如Activity.dispatchTouchEvent() 返回true的情況 = ViewGroup.dispatchTouchEvent() 返回true
-
注意點3:各層dispatchTouchEvent() 與 onTouchEvent()的返回情況保持一致 原因:最下層View的dispatchTouchEvent()的返回值 取決於 View.onTouchEvent()的返回值;結合注意點1,逐層往上返回,從而保持一致。
下面,將針對該3個方法,分別針對預設執行邏輯、返回true、返回false的三種情況進行流程圖示意。
方法1:dispatchTouchEvent()
預設執行邏輯、返回true、返回false 這三種情況的返回邏輯分別如下所示。
方法2:onInterceptTouchEvent()
預設執行邏輯、返回true、返回false 這三種情況的返回邏輯分別如下所示。
這裡需要特別注意的是:
Activity
、
View
都無該方法,僅
ViewGroup
特有。
方法3:onTouchEvent()
預設執行邏輯、返回true、返回false 這三種情況的返回邏輯分別如下所示。
三者關係
下面,我用一段虛擬碼來闡述上述3個方法的關係 & 事件傳遞規則
// 點選事件產生後
// 步驟1:呼叫dispatchTouchEvent()
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean consume = false; //代表 是否會消費事件
// 步驟2:判斷是否攔截事件
if (onInterceptTouchEvent(ev)) {
// a. 若攔截,則將該事件交給當前View進行處理
// 即呼叫onTouchEvent()去處理點選事件
consume = onTouchEvent (ev) ;
} else {
// b. 若不攔截,則將該事件傳遞到下層
// 即 下層元素的dispatchTouchEvent()就會被呼叫,重複上述過程
// 直到點選事件被最終處理為止
consume = child.dispatchTouchEvent (ev) ;
}
// 步驟3:最終返回通知 該事件是否被消費(接收 & 處理)
return consume;
}
5 . 常見事件分發場景
下面,我將透過例項說明 常見的事件傳遞情況 & 流程
5.1 背景描述
-
討論的佈局如下:
-
情景
-
使用者先觸控到螢幕上
View C
上的某個點(圖中黃區)
Action_DOWN
事件在此處產生-
使用者移動手指
-
最後離開螢幕
-
5.2 一般的事件傳遞情況
一般的事件傳遞場景有:
-
預設情況
-
處理事件
-
攔截
DOWN
事件 -
攔截後續事件(
MOVE
、UP
)
場景1:預設
-
即不對控制元件裡的方法(
dispatchTouchEvent()
、onTouchEvent()
、onInterceptTouchEvent()
)進行重寫 或 更改返回值 -
那麼呼叫的是這3個方法的預設實現:呼叫下層的方法 & 逐層返回
-
事件傳遞情況:(呈
U
型)-
從上往下呼叫dispatchTouchEvent()
Activity A ->> ViewGroup B ->> View C
-
從下往上呼叫onTouchEvent()
View C ->> ViewGroup B ->> Activity A
-
注:雖然
ViewGroup B
的onInterceptTouchEvent
()對DOWN
事件返回了false
,但後續的事件(MOVE、UP)
依然會傳遞給它的onInterceptTouchEvent()
這一點與onTouchEvent()
的行為是不一樣的:不再傳遞 & 接收該事件列的其他事件
場景2:處理事件
設
View C
希望處理該點選事件,即:設定
View C
為可點選的
(Clickable)
或 複寫其
onTouchEvent()
返回
true
最常見的:設定
Button
按鈕來響應點選事件
事件傳遞情況:(如下圖)
-
DOWN
事件被傳遞給C的onTouchEvent
方法,該方法返回true
,表示處理該事件 -
因為
View C
正在處理該事件,那麼DOWN
事件將不再往上傳遞給ViewGroup B 和Activity A
的onTouchEvent()
; -
該事件列的其他事件
(Move、Up)
也將傳遞給View C
的onTouchEvent()
會逐層往
dispatchTouchEvent()
返回,最終事件分發結束
場景3:攔截DOWN事件
假設
ViewGroup B
希望處理該點選事件,即
ViewGroup B
複寫了
onInterceptTouchEvent()
返回
true
、
onTouchEvent()
返回
true
事件傳遞情況:(如下圖)
-
DOWN
事件被傳遞給ViewGroup B
的onInterceptTouchEvent()
,該方法返回true
,表示攔截該事件,即自己處理該事件(事件不再往下傳遞) -
呼叫自身的
onTouchEvent()
處理事件(DOWN
事件將不再往上傳遞給Activity A
的onTouchEvent()
) -
該事件列的其他事件
(Move、Up)
將直接傳遞給ViewGroup B
的onTouchEvent()
注:
該事件列的其他事件
(Move、Up)
將不會再傳遞給ViewGroup B
的onInterceptTouchEvent
();因:該方法一旦返回一次true
,就再也不會被呼叫逐層往
dispatchTouchEvent()
返回,最終事件分發結束
場景4:攔截DOWN的後續事件
結論
-
若
ViewGroup
攔截了一個半路的事件(如MOVE
),該事件將會被系統變成一個CANCEL
事件 & 傳遞給之前處理該事件的子View
; -
該事件不會再傳遞給
ViewGroup
的onTouchEvent()
-
只有再到來的事件才會傳遞到
ViewGroup
的onTouchEvent()
場景描述
ViewGroup B
無攔截
DOWN
事件(還是
View C
來處理
DOWN
事件),但它攔截了接下來的
MOVE
事件
即
DOWN
事件傳遞到View C
的onTouchEvent()
,返回了true
例項講解
-
在後續到來的MOVE事件,
ViewGroup B
的onInterceptTouchEvent()
返回true
攔截該MOVE
事件,但該事件並沒有傳遞給ViewGroup B
;這個MOVE
事件將會被系統變成一個CANCEL
事件傳遞給View C
的onTouchEvent()
-
後續又來了一個
MOVE
事件,該MOVE
事件才會直接傳遞給ViewGroup B
的onTouchEvent()
後續事件將直接傳遞給
ViewGroup B
的onTouchEvent()
處理,而不會再傳遞給ViewGroup B
的onInterceptTouchEvent()
,因該方法一旦返回一次true,就再也不會被呼叫了。
-
View C
再也不會收到該事件列產生的後續事件
至此,關於
Android
常見的事件傳遞情況 & 流程已經講解完畢。
6 . 特殊說明
6.1 Touch事件的後續事件(MOVE、UP)層級傳遞
-
若給控制元件註冊了
Touch
事件,每次點選都會觸發一系列action
事件(ACTION _ DOWN,ACTION _ MOVE,ACTION _ UP等) -
當
dispatchTouchEvent()
事件分發時,只有前一個事件(如ACTION _ DOWN)返回true,才會收到後一個事件(ACTION _ MOVE和ACTION _ UP)
即如果在執行ACTION _ DOWN時返回false,後面一系列的ACTION _ MOVE、ACTION _ UP事件都不會執行
從上面對事件分發機制分析知:
-
dispatchTouchEvent()、 onTouchEvent() 消費事件、終結事件傳遞(返回true)
-
而onInterceptTouchEvent 並不能消費事件,它相當於是一個分叉口起到分流導流的作用,對後續的ACTION _ MOVE和ACTION _ UP事件接收起到非常大的作用
請記住:接收了ACTION _ DOWN事件的函式不一定能收到後續事件(ACTION _ MOVE、ACTION _ UP)
這裡給出ACTION _ MOVE和ACTION _ UP事件的傳遞結論 :
-
結論1 若物件(Activity、ViewGroup、View)的dispatchTouchEvent()分發事件後消費了事件(返回true),那麼收到ACTION _ DOWN的函式也能收到ACTION _ MOVE和ACTION _ UP
黑線:ACTION _ DOWN事件傳遞方向 紅線:ACTION _ MOVE 、 ACTION _ UP事件傳遞方向
-
結論2 若物件(Activity、ViewGroup、View)的onTouchEvent()處理了事件(返回true),那麼ACTION _ MOVE、ACTION _ UP的事件從上往下傳到該
View
後就不再往下傳遞,而是直接傳給自己的onTouchEvent()
& 結束本次事件傳遞過程。
黑線:ACTION _ DOWN事件傳遞方向 紅線:ACTION _ MOVE、ACTION _ UP事件傳遞方向
6.2 onTouch()和onTouchEvent()的區別
-
該2個方法都是在
View.dispatchTouchEvent()
中呼叫 -
但
onTouch()
優先於onTouchEvent
執行;若手動複寫在onTouch()
中返回true
(即 將事件消費掉),將不會再執行onTouchEvent()
注:若1個控制元件不可點選(即非
enable
),那麼給它註冊onTouch
事件將永遠得不到執行,具體原因看如下程式碼
// &&為短路與,即如果前面條件為false,將不再往下執行
// 故:onTouch()能夠得到執行需2個前提條件:
// 1\. mOnTouchListener的值不能為空
// 2\. 當前點選的控制元件必須是enable的
mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener.onTouch(this, event)
// 對於該類控制元件,若需監聽它的touch事件,就必須透過在該控制元件中重寫onTouchEvent()來實現
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69983917/viewspace-2840820/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Android高階進階之路【一】Android中View繪製流程淺析AndroidView
- Android原始碼角度分析事件分發消費(徹底整明白Android事件)Android原始碼事件
- Android高階進階之路【四】一文讀懂 Handler 機制Android
- Android高階進階之路【五】深入剖析Android系統Binder通訊機制Android
- Android View 的事件體系 -- 事件分發機制AndroidView事件
- 高階 Android 工程師的進階之路Android工程師
- Android事件分發:從原始碼角度分析View事件分發機制Android事件原始碼View
- Android 事件分發機制原始碼解析-view層Android事件原始碼View
- View事件分發機制分析View事件
- Android從原始碼角度剖析View事件分發機制Android原始碼View事件
- 基於原始碼分析 Android View 事件分發機制原始碼AndroidView事件
- Android自定義View之事件分發機制總結AndroidView事件
- 淺談Android 事件分發機制(二)Android事件
- Android進階(六)Binder機制Android
- Android事件分發機制Android事件
- 自定義View事件之進階篇(一)-NestedScrolling(巢狀滑動)機制View事件巢狀
- React 進階之路(二)React
- 2018.03.08、View的事件分發機制筆記View事件筆記
- Android進階(五)View繪製流程AndroidView
- android事件分發機制詳解Android事件
- Android 事件分發機制的理解Android事件
- Android的MotionEvent事件分發機制Android事件
- Android事件分發機制三:事件分發工作流程Android事件
- 【朝花夕拾】Android自定義View篇之(五)Android事件分發及傳遞機制AndroidView事件
- Java初級~中級~高階進階之路Java
- Deno 正式釋出,徹底弄明白和 node 的區別
- 自定義View事件篇進階篇(二)-自定義NestedScrolling實戰View事件
- 【進階之路】併發程式設計(三)-非阻塞同步機制程式設計
- Android進階;Handler訊息機制詳解Android
- Android事件分發機制簡單理解Android事件
- Android View 事件分發原始碼分析AndroidView事件原始碼
- 事件分發機制(二):原始碼篇事件原始碼
- Android開發進階——自定義View的使用及其原理探索AndroidView
- Android高階進階之路【三】看完這篇再不會Android動畫框架,我跪搓衣板Android動畫框架
- 「前端進階」徹底弄懂函式柯里化前端函式
- 十分鐘明白什麼是容器技術
- 淺談Android中的事件分發機制Android事件
- 【Android基礎】講講Android的事件分發機制Android事件