Android事件分發機制
事件分發機制
Android事件分發是指在Android系統中,當用戶觸摸屏幕或執行其他操作時,系統如何將這些事件傳遞給正確的視圖或組件進行處理的過程。
Android事件分發遵循一種稱為"事件分發機制"的規則,該機制由三個主要的階段組成:觸摸事件的捕獲階段、目標視圖的處理階段和冒泡階段。
在觸摸事件的捕獲階段,事件從頂層視圖(如Activity)開始,逐級向下傳遞,直到找到最底層的子視圖。在這個過程中,每個視圖都有機會攔截事件,如果某個視圖攔截了事件,則后續的視圖將無法接收到該事件。
在目標視圖的處理階段,事件被傳遞給最底層的子視圖,并由該視圖進行處理。如果該視圖沒有處理事件,則事件將被傳遞給其父視圖,直到找到能夠處理事件的視圖為止。
在冒泡階段,事件從底層視圖向上冒泡,直到達到頂層視圖。在這個過程中,每個視圖都有機會處理事件,如果某個視圖處理了事件,則后續的視圖將無法接收到該事件。
通過這種事件分發機制,Android系統能夠準確地將用戶的操作傳遞給正確的視圖或組件進行處理,從而實現了用戶與應用程序的交互。在實際開發中,我們可以通過重寫視圖的相關方法(如onTouchEvent())來自定義事件的處理邏輯,以滿足特定的需求。
涉及的對象
- View:View是Android中的基本UI組件,它負責接收用戶的輸入事件(如點擊、觸摸等),并將事件傳遞給相應的處理方法。
- ViewGroup:ViewGroup是View的子類,它可以包含其他的View或ViewGroup。當一個事件發生在ViewGroup上時,它會遍歷其子View,并將事件傳遞給合適的子View。
- MotionEvent:MotionEvent是Android中的事件對象,它封裝了用戶的觸摸事件信息,包括觸摸點的坐標、觸摸的動作等。
- MotionEventCompat:MotionEventCompat是一個兼容性類,用于處理不同Android版本之間的觸摸事件兼容性問題。
- GestureDetector:GestureDetector是Android提供的手勢檢測器,它可以識別用戶的手勢操作,如滑動、長按、雙擊等。
- OnTouchListener:OnTouchListener是一個接口,用于監聽View的觸摸事件。通過實現該接口,可以自定義觸摸事件的處理邏輯。
以上是事件分發中的一些關鍵對象,它們共同協作,實現了Android中的事件分發機制。
MotionEvent
MotionEvent是Android中的一個類,用于處理與用戶交互相關的事件,例如觸摸屏幕、按下按鈕等。它包含了一系列的常量和方法,用于獲取事件的類型、坐標、時間等信息。通過監聽MotionEvent,開發者可以實現對用戶的觸摸操作進行響應和處理。
以下是一些常用的MotionEvent方法:
- getAction():獲取觸摸事件的動作類型,返回一個整數值??梢酝ㄟ^使用MotionEvent.ACTION_DOWN、MotionEvent.ACTION_MOVE、MotionEvent.ACTION_UP等常量來判斷觸摸事件的具體類型。
- getDownTime():獲取當屏幕剛被按下時的時間(毫秒),按下后移動此時間不變
- getEventTime():獲取MotionEvent所在的事件被激發的時間(毫秒)
- getX()和getY():獲取觸摸事件的發生位置的橫坐標和縱坐標。返回的是相對于觸摸事件發生位置的坐標值。
- getRawX()和getRawY():獲取觸摸事件的發生位置的原始橫坐標和縱坐標。返回的是相對于屏幕的坐標值。
- getPointerCount():獲取觸摸事件中手指的數量。
- getPointerId(int pointerIndex):獲取指定索引的手指的ID。
- getPressure(int pointerIndex):獲取指定索引的手指的壓力值。
- getHistorySize():獲取觸摸事件的歷史記錄的數量。
- getHistoricalX(int pointerIndex, int pos)和getHistoricalY(int pointerIndex, int pos):獲取指定索引的手指在指定歷史記錄位置的橫坐標和縱坐標。
這些方法可以幫助開發者獲取觸摸事件的相關信息,并進行相應的處理。
MotionEvent#getAction()類型
MotionEvent#getAction()方法返回一個整數,表示當前觸摸事件的類型。具體的類型有以下幾種:
- ACTION_DOWN:手指按下屏幕時觸發的事件
- ACTION_MOVE:手指在屏幕上滑動時觸發的事件
- ACTION_UP:手指離開屏幕時觸發的事件
- ACTION_CANCEL:觸摸事件被取消時觸發的事件
- ACTION_OUTSIDE:超出區域
這些類型可以通過與MotionEvent類中定義的常量進行比較來判斷當前觸摸事件的類型。例如,可以使用以下代碼來判斷當前事件是否為按下事件:
if (event.getAction() == MotionEvent.ACTION_DOWN) {
// 處理按下事件的邏輯
}
示例:手指觸摸屏幕到離開屏幕事件走向
圖片
事件分發對象間傳遞
在Android中,事件分發是通過View的事件分發機制來實現的。當用戶觸摸屏幕或者進行其他操作時,事件會從頂層的ViewGroup開始向下傳遞,直到找到合適的View來處理該事件。
Android中的事件分發涉及到以下幾個對象:
- Activity:Activity是Android應用程序的一個組件,它負責管理用戶界面和處理用戶交互。當用戶觸摸屏幕或者進行其他操作時,事件首先會傳遞給當前顯示的Activity。
- Window:Window是Activity的一個屬性,它表示一個窗口,用于顯示Activity的用戶界面。事件會首先傳遞給Window,然后由Window負責將事件傳遞給對應的View。
- View:View是Android中的基本UI組件,用于構建用戶界面。每個View都有一個事件分發的責任,它可以處理自己感興趣的事件,也可以將事件傳遞給其他View。
- ViewGroup:ViewGroup是View的子類,用于管理其他View的布局和顯示。當事件傳遞到ViewGroup時,它會遍歷自己的子View,并將事件傳遞給合適的子View。
在事件分發過程中,每個對象都有機會處理事件。如果一個對象處理了事件,那么事件就會停止傳遞。如果一個對象沒有處理事件,那么事件會繼續向下傳遞,直到找到合適的處理者或者事件傳遞到最底層的View。
可以通過重寫View的dispatchTouchEvent()方法來實現事件的分發和傳遞。在該方法中,可以根據需要調用super.dispatchTouchEvent()方法將事件傳遞給父View或者調用View的onTouchEvent()方法來處理事件。
Android中的事件分發是通過Activity、Window、View和ViewGroup等對象之間的協作來實現的。每個對象都有機會處理事件,通過合理地重寫相關方法,可以實現事件的傳遞和處理。
事件分發機制解析
從上面的文章中我們得知Android事件分發機制的傳遞過程可以分為三個階段:分發、攔截和處理。
- 分發階段:事件首先由Activity或ViewGroup的dispatchTouchEvent()方法開始分發。在這個方法中,事件會被傳遞給當前ViewGroup的onInterceptTouchEvent()方法進行攔截判斷。如果onInterceptTouchEvent()返回true,則表示當前ViewGroup攔截了事件,不再向下傳遞;如果返回false,則表示當前ViewGroup不攔截事件,會繼續向下傳遞。
- 攔截階段:如果當前ViewGroup不攔截事件,事件會繼續向下傳遞給子View。子View的dispatchTouchEvent()方法會被調用,同樣會經過onInterceptTouchEvent()方法的判斷。如果子View攔截了事件,那么事件將不再向下傳遞給其他子View,而是交給子View的onTouchEvent()方法進行處理;如果子View不攔截事件,事件會繼續向下傳遞。
- 處理階段:如果事件沒有被任何ViewGroup或View攔截,最終會傳遞給最底層的View的onTouchEvent()方法進行處理。在這個方法中,可以根據事件的類型(如觸摸、滑動、點擊等)進行相應的處理操作。
Android事件分發機制的傳遞過程是從上到下的遞歸過程,事件會依次經過父ViewGroup和子View的攔截判斷,最終到達最底層的View進行處理。這個過程中,可以通過重寫相關方法來實現事件的攔截和處理,從而實現自定義的交互邏輯。
Activity事件分發機制
Activity的事件分發機制是通過ViewGroup和View的層級關系來實現的。當用戶觸摸屏幕或者按下按鍵時,系統會將事件傳遞給當前顯示的Activity的根布局ViewGroup,然后由ViewGroup負責將事件分發給各個子View進行處理。
具體的事件分發流程如下:
- 用戶觸摸屏幕或按下按鍵,事件首先傳遞給Activity的根布局ViewGroup。
- ViewGroup會調用自己的dispatchTouchEvent()方法來處理事件。在該方法中,ViewGroup會根據事件的類型(如觸摸事件、按鍵事件等)進行相應的處理,例如判斷是否需要攔截事件、是否需要消費事件等。
- 如果ViewGroup需要攔截事件,即認為自己應該處理該事件,那么事件就會停止向下傳遞,直接由ViewGroup來處理。
- 如果ViewGroup不需要攔截事件,那么事件會繼續向下傳遞給子View。
- 子View會依次接收事件,并調用自己的dispatchTouchEvent()方法來處理事件。子View也會根據事件的類型進行相應的處理,例如判斷是否需要攔截事件、是否需要消費事件等。
- 如果子View需要攔截事件,那么事件就會停止向下傳遞,直接由該子View來處理。
- 如果子View不需要攔截事件,那么事件會繼續向下傳遞給下一個子View,直到事件被消費或者傳遞到最后一個子View。
- 如果事件最終沒有被任何子View消費,那么事件會回到ViewGroup,ViewGroup會根據自身的情況來決定是否消費事件。
- 如果事件最終還是沒有被消費,那么事件會繼續向上層傳遞,直到被消費或者傳遞到最頂層的Activity。
通過這樣的事件分發機制,Android系統可以實現對用戶的觸摸和按鍵事件進行靈活的處理,從而實現各種交互效果。
Activity源碼:
public boolean dispatchTouchEvent(MotionEvent ev) {
// 開始事件都是Dwon,一般第一次都會進入到onUserInteraction
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
// 若Window返回true,則會告訴Activity也返回true。true在所有touch代表著終止,不再繼續往下一個事件傳遞了
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
下面看getWindow().superDispatchTouchEvent(ev); Window是個抽象類,唯一實現類為PhoneWindow,定位到PhoneWindow的superDispatchTouchEvent()。
PhoneWindow的源碼:
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
mDecor是DecorView類,DecorView是PhoneWindow類的一個內部類。同時DecorView也是整個Window中的最頂層View。
DecorView
DecorView是Android中的一個類,它是Android系統中的頂級視圖,用于承載應用程序的用戶界面。它是Android窗口系統的一部分,負責管理應用程序的窗口和布局。
DecorView是一個特殊的ViewGroup,它包含了應用程序的整個用戶界面,包括狀態欄、標題欄、內容區域等。它是Android應用程序的根視圖,所有其他視圖都是DecorView的子視圖。
DecorView的主要作用是提供一個容器,用于放置應用程序的布局和控件。它還負責處理用戶輸入事件,如觸摸、滑動等,并將其傳遞給相應的子視圖進行處理。
在Android開發中,我們通常不直接操作DecorView,而是通過Activity或Fragment來管理和操作應用程序的用戶界面。DecorView在內部被Activity或Fragment自動創建和管理,開發者只需要關注布局和控件的設計和交互邏輯即可。
DecorView是Android應用程序的根視圖,負責承載應用程序的用戶界面,并提供容器和事件處理功能。
DecorView是一個特殊的ViewGroup,分發處理同ViewGroup,下面看ViewGroup的superDispatchTouchEvent()
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
整個Activity事件分發過程如下圖:
圖片
ViewGroup事件分發機制
ViewGroup是一種特殊的View,它可以包含其他的View或者ViewGroup。當用戶進行觸摸操作時,事件會被傳遞給ViewGroup,并由ViewGroup負責將事件分發給其子View或者子ViewGroup。
ViewGroup的事件分發機制主要包括以下幾過程:
- 事件的傳遞:當用戶觸摸屏幕時,事件會首先傳遞給最頂層的ViewGroup,即Activity的根布局。然后,ViewGroup會根據觸摸事件的坐標位置,確定哪個子View或者子ViewGroup應該接收該事件。
- 事件的攔截:在確定了接收事件的子View或者子ViewGroup后,ViewGroup會調用該子View或者子ViewGroup的dispatchTouchEvent()方法,將事件傳遞給它們。在這個過程中,如果父ViewGroup需要攔截事件,可以通過重寫onInterceptTouchEvent()方法來實現。如果父ViewGroup攔截了事件,那么該事件將不會傳遞給子View或者子ViewGroup,而是由父ViewGroup來處理。
- 事件的處理:當事件傳遞到子View或者子ViewGroup時,它們會調用自己的dispatchTouchEvent()方法來處理事件。在這個方法中,子View或者子ViewGroup可以根據自己的需求來處理事件,例如響應點擊、滑動等操作。如果子View或者子ViewGroup不消費事件,那么事件會繼續傳遞給父ViewGroup,直到事件被消費或者傳遞到最頂層的ViewGroup。
ViewGroup的事件分發機制是通過事件的傳遞、攔截和處理來實現的。通過重寫dispatchTouchEvent()和onInterceptTouchEvent()方法,可以對事件進行自定義的處理。這種機制可以保證事件在ViewGroup及其子View或者子ViewGroup之間的正確傳遞和處理。
整個ViewGroup分發過程如下圖:
圖片
View事件分發機制
View事件分發機制主要包括三個關鍵方法:dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent。
- dispatchTouchEvent:該方法是ViewGroup類中的一個關鍵方法,用于分發觸摸事件。當一個觸摸事件發生時,系統會首先將該事件傳遞給頂層的ViewGroup,然后由ViewGroup根據一定的規則將事件傳遞給子View進行處理。在dispatchTouchEvent方法中,系統會根據事件類型和觸摸位置等信息,決定將事件傳遞給哪個子View進行處理。
- onInterceptTouchEvent:該方法也是ViewGroup類中的一個關鍵方法,用于攔截觸摸事件。當一個觸摸事件傳遞給ViewGroup后,ViewGroup會先調用onInterceptTouchEvent方法來判斷是否需要攔截該事件。如果onInterceptTouchEvent方法返回true,則表示ViewGroup會攔截該事件,并將事件傳遞給自己的onTouchEvent方法進行處理;如果返回false,則表示ViewGroup不會攔截該事件,會將事件繼續傳遞給子View進行處理。
- onTouchEvent:該方法是View類中的一個關鍵方法,用于處理觸摸事件。當一個觸摸事件傳遞到View時,View會調用自己的onTouchEvent方法來處理該事件。在onTouchEvent方法中,可以根據事件類型進行相應的處理,例如處理點擊事件、滑動事件等。
View事件分發機制的流程如下:
- 當用戶觸摸屏幕時,系統會將觸摸事件傳遞給頂層的ViewGroup。
- ViewGroup會調用dispatchTouchEvent方法來分發觸摸事件。
- 在dispatchTouchEvent方法中,ViewGroup會根據一定的規則將事件傳遞給子View進行處理。
- 子View會先調用onInterceptTouchEvent方法來判斷是否需要攔截該事件。
- 如果onInterceptTouchEvent方法返回true,則表示ViewGroup會攔截該事件,并將事件傳遞給自己的onTouchEvent方法進行處理。
- 如果onInterceptTouchEvent方法返回false,則表示ViewGroup不會攔截該事件,會將事件繼續傳遞給子View進行處理。
- 子View會調用自己的onTouchEvent方法來處理觸摸事件。
View#dispatchTouchEvent源碼:
public boolean dispatchTouchEvent(MotionEvent event) {
...
if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
...
}
只有當4個條件都為真才返回true,否則執行onTouchEvent(),下面對這4個條件逐個分析:
- li != null:li即是ListenerInfo,ListenerInfo是封裝了所有事件,所以只要賦值任一事件,這個都不可能會為null。
- mOnTouchListener != null:mOnTouchListener變量在View.setOnTouchListener()方法里賦值,即只要我們給控件注冊了Touch事件,mOnTouchListener就不為空。
- (mViewFlags & ENABLED_MASK) == ENABLED:判斷當前點擊的控件是否enable,由于View默認enable,故該條件為true。
- mOnTouchListener.onTouch(this, event):控件注冊touch事件時重寫的onTouch()方法。
若在setOnTouchListener返回true,就會滿足以上4個條件,并且返回了true,從而使得View.dispatchTouchEvent()直接返回true,事件分發結束,不會執行onTouchEvent(event)。
View#onTouchEvent(event)源碼:
public boolean onTouchEvent(MotionEvent event) {
...
// clickable代表該控件是否可點擊,可點擊就進入下面條件判斷
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
// 1. 當前的事件 = 抬起View
case MotionEvent.ACTION_UP:
// 經過種種判斷,此處省略
........
if (!focusTaken) {
// 執行performClick()
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClickInternal();
}
}
break;
// 2. 當前的事件 = 按下View
case MotionEvent.ACTION_DOWN:
// 經過種種判斷,此處省略
break;
// 3. 當前的事件 = 結束事件(非人為原因)
case MotionEvent.ACTION_CANCEL:
// 經過種種判斷,此處省略
break;
// 4. 當前的事件 = 滑動View
case MotionEvent.ACTION_MOVE:
// 經過種種判斷,此處省略
break;
}
// 若該控件可點擊,就一定返回true
return true;
}
// 若該控件不可點擊,就一定返回false
return false;
...
}
整個View分發過程如下圖:
圖片
總結
圖片
dispatchTouchEvent
dispatchTouchEvent用于分發觸摸事件。它是ViewGroup類中的一個方法,用于將觸摸事件傳遞給子View或處理自身的觸摸事件。
觸摸事件的傳遞是通過觸摸事件分發機制來實現的。當用戶觸摸屏幕時,系統會將觸摸事件傳遞給頂層的ViewGroup,然后由ViewGroup負責將觸摸事件傳遞給子View或處理自身的觸摸事件。
dispatchTouchEvent方法的作用是將觸摸事件分發給子View或處理自身的觸摸事件。它會根據觸摸事件的類型和位置來確定是將觸摸事件傳遞給子View,還是處理自身的觸摸事件。
在dispatchTouchEvent方法中,會依次調用onInterceptTouchEvent方法和onTouchEvent方法來判斷是否攔截觸摸事件和處理觸摸事件。如果onInterceptTouchEvent方法返回true,則表示攔截觸摸事件,不再向子View傳遞觸摸事件;如果onTouchEvent方法返回true,則表示處理了觸摸事件,不再向子View傳遞觸摸事件。
onTouchEvent
onTouchEvent用于處理觸摸事件。它是View類的一個成員方法,可以被重寫以實現自定義的觸摸事件處理邏輯。
觸摸事件包括按下(ACTION_DOWN)、移動(ACTION_MOVE)、抬起(ACTION_UP)等多個動作。當用戶觸摸屏幕時,系統會將觸摸事件傳遞給相應的View,并調用該View的onTouchEvent方法來處理事件。
在onTouchEvent方法中,可以根據不同的觸摸動作進行相應的處理,例如根據觸摸位置進行繪制、處理滑動事件、處理點擊事件等??梢酝ㄟ^重寫onTouchEvent方法來實現自定義的觸摸交互效果。
重寫onTouchEvent方法來處理觸摸事件:
@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
// 處理按下事件
break;
case MotionEvent.ACTION_MOVE:
// 處理移動事件
break;
case MotionEvent.ACTION_UP:
// 處理抬起事件
break;
}
return true;
}
需要注意的是,onTouchEvent方法的返回值為boolean類型。如果返回true,表示已經處理了該觸摸事件,不再向其他View傳遞;如果返回false,則會將該觸摸事件傳遞給父View或其他相關的View進行處理。
onInterceptTouchEvent
onInterceptTouchEvent用于攔截觸摸事件。它通常用于父容器對子View的觸摸事件進行攔截和處理。
觸摸事件是由屏幕上的觸摸點產生的,包括按下、移動和抬起等動作。當一個觸摸事件發生時,系統會將該事件傳遞給最上層的View,并通過dispatchTouchEvent方法進行分發。在分發過程中,如果父容器的onInterceptTouchEvent方法返回true,則表示父容器要攔截該事件,不再將事件傳遞給子View;如果返回false,則表示父容器不攔截該事件,繼續將事件傳遞給子View。
onInterceptTouchEvent方法的返回值決定了是否攔截觸摸事件,它有三種可能的返回值:
- 返回true:表示父容器要攔截觸摸事件,不再傳遞給子View。
- 返回false:表示父容器不攔截觸摸事件,繼續傳遞給子View。
- 返回super.onInterceptTouchEvent(event):表示父容器不對觸摸事件進行攔截,繼續按照默認的方式處理。
通過在onInterceptTouchEvent方法中對觸摸事件進行處理,我們可以實現一些特定的觸摸事件邏輯,例如滑動沖突處理、多指觸摸事件的處理等。
setOnTouchListener
setOnTouchListener是一個用于設置觸摸事件監聽器的方法,用于對觸摸事件進行處理。
使用setOnTouchListener方法,可以為一個控件(如Button、ImageView等)設置一個觸摸事件監聽器。當用戶觸摸該控件時,觸摸事件監聽器會被觸發,并執行相應的操作。
示例代碼:
button.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
// 處理觸摸事件的邏輯代碼
return true; // 返回true表示已經處理了觸摸事件,false表示未處理
}
});
button是要設置觸摸事件監聽器的視圖對象。setOnTouchListener方法接受一個View.OnTouchListener對象作為參數,該對象實現了onTouch方法,用于處理觸摸事件。
在onTouch方法中,可以編寫自定義的觸摸事件處理邏輯。根據MotionEvent對象的不同動作(如按下、移動、抬起等),可以執行相應的操作。最后,需要返回一個布爾值,表示是否已經處理了觸摸事件。
使用setOnTouchListener方法可以實現各種觸摸事件的處理,例如拖動、縮放、滑動等。根據具體需求,可以在onTouch方法中編寫相應的代碼邏輯。