Android-你必須要懂得事件分發(fā)全解析

一、 事件分發(fā)的對象是誰?

答:事件

當用戶觸摸屏幕時(View或ViewGroup派生的控件),將產(chǎn)生點擊事件(Touch事件)。
Touch事件相關(guān)細節(jié)(發(fā)生觸摸的位置、時間、歷史記錄、手勢動作等)被封裝成MotionEvent對象
主要發(fā)生的Touch事件有如下四種:

  • MotionEvent.ACTION_DOWN:按下View(所有事件的開始)(只會產(chǎn)生一次)
  • MotionEvent.ACTION_MOVE:滑動View
  • MotionEvent.ACTION_CANCEL:非人為原因結(jié)束本次事件
  • MotionEvent.ACTION_UP:抬起View(與DOWN對應)(只會產(chǎn)生一次)

事件列:從手指接觸屏幕至手指離開屏幕,這個過程產(chǎn)生的一系列事件 任何事件列都是以DOWN事件開始,UP事件結(jié)束,中間有無數(shù)的MOVE事件,如下圖:

事件執(zhí)行順序.png

二、事件分發(fā)的本質(zhì)

答:將點擊事件(MotionEvent)向某個View進行傳遞并最終得到處理

即當一個點擊事件發(fā)生后,系統(tǒng)需要將這個事件傳遞給一個具體的View去處理。這個事件傳遞的過程就是分發(fā)過程。

三、事件在哪些對象之間進行傳遞?

答:Activity、ViewGroup、View

一個點擊事件產(chǎn)生后,傳遞順序是:Activity(Window) -> ViewGroup -> View
Android的UI界面是由Activity、ViewGroup、View及其派生類組合而成的

image.png

View是所有UI組件的基類

一般Button、ImageView、TextView等控件都是繼承父類View

ViewGroup是容納UI組件的容器,即一組View的集合(包含很多子View和子VewGroup),

其本身也是從View派生的,即ViewGroup是View的子類
是Android所有布局的父類或間接父類:項目用到的布局(LinearLayout、RelativeLayout等),都繼承自ViewGroup,即屬于ViewGroup子類。

與普通View的區(qū)別:ViewGroup實際上也是一個View,只不過比起View,它多了可以包含子View和定義布局參數(shù)的功能。

四、 事件分發(fā)過程由哪些方法協(xié)作完成?

答:dispatchTouchEvent() 、onInterceptTouchEvent()和onTouchEvent()

image.png

五、 總結(jié)

Android事件分發(fā)機制的本質(zhì)是要解決:

點擊事件由哪個對象發(fā)出,經(jīng)過哪些對象,最終達到哪個對象并最終得到處理。

這里的對象是指Activity、ViewGroup、View
Android中事件分發(fā)順序:Activity(Window) -> ViewGroup -> View

事件分發(fā)過程由dispatchTouchEvent() 、onInterceptTouchEvent()和onTouchEvent()三個方法協(xié)助完成

經(jīng)過上述3個問題,相信大家已經(jīng)對Android的事件分發(fā)有了感性的認知,接下來,我將詳細介紹Android事件分發(fā)機制。

六、事件分發(fā)機制方法介紹

Android事件分發(fā)流程如下:(必須熟記)
Android事件分發(fā)順序:Activity(Window) -> ViewGroup -> View

事件分發(fā)順序.png

super:調(diào)用父類方法(在這里Activity的父類是ViewGroup,ViewGroup的父類是View)
true:消費事件,即事件不繼續(xù)往下傳遞
false:不消費事件,事件也不繼續(xù)往下傳遞 / 交由給父控件onTouchEvent()處理

看圖總結(jié):首先當你觸摸的屏幕的時候會一口氣產(chǎn)生至少2個事件,一個down,一個up,其余就是你手指移動過程中產(chǎn)生的move事件。首先down事件會先執(zhí)行activity的dispatchTouchEvent方法,不管它返回true或者false都會直接在這里被消費,不會繼續(xù)往下傳遞。只有調(diào)用父類方法的時候會執(zhí)行ViewGroup的dispatchTouchEvent方法,此時假如dispatchTouchEvent返回true也代表在此消費,不會繼續(xù)傳遞,假如返回false,表示不分發(fā),則交還給Activity,說大家都處理不了,你自己處理吧。假如dispatchTouchEvent返回父類,則會調(diào)用ViewGroup的onInterceptTouchEvent,返回true表示攔截這個事件,交給自己的onTouchEvent方法消費事件,假如返回false,表示不攔截,則交給View的dispatchTouchEvent方法,同樣的做處理,之后交給View的onTouchEvent方法,返回true代表消費,返回false,表示無法處理,交還給上級。

其實,這些不需要硬記,實在開發(fā)中遇到不知道哪里返回true,哪里返回false時,回來看看這個順序。最主要的是多去用用就記住了。下面我們看一般的事件傳遞。

七、一般事件傳遞

image.png

7.1 默認情況

即不對控件里的方法(dispatchTouchEvent()、onTouchEvent()、onInterceptTouchEvent())進行重寫或更改返回值
那么調(diào)用的是這3個方法的默認實現(xiàn):調(diào)用父類的方法
事件傳遞情況:

從Activity A---->ViewGroup B--->View C,從上往下調(diào)用dispatchTouchEvent()
再由View C--->ViewGroup B --->Activity A,從下往上調(diào)用onTouchEvent()

7.2處理事件

假設(shè)View C希望處理這個點擊事件,即C被設(shè)置成可點擊的(Clickable)或者覆寫了C的onTouchEvent方法返回true。

事件傳遞情況:

DOWN事件被傳遞給C的onTouchEvent方法,該方法返回true,表示處理這個事件
因為C正在處理這個事件,那么DOWN事件將不再往上傳遞給B和A的onTouchEvent();
該事件列的其他事件(Move、Up)也將傳遞給C的onTouchEvent()

7.3 攔截DOWN事件

假設(shè)ViewGroup B希望處理這個點擊事件,即B覆寫了onInterceptTouchEvent()返回true、onTouchEvent()返回true。 事件傳遞情況:

DOWN事件被傳遞給B的onInterceptTouchEvent()方法,該方法返回true,表示攔截這個事件,即自己處理這個事件(不再往下傳遞)
調(diào)用onTouchEvent()處理事件(DOWN事件將不再往上傳遞給A的onTouchEvent())
該事件列的其他事件(Move、Up)將直接傳遞給B的onTouchEvent()
該事件列的其他事件(Move、Up)將不會再傳遞給B的onInterceptTouchEvent方法,該方法一旦返回一次true,就再也不會被調(diào)用了。

7.4 攔截DOWN的后續(xù)事件

假設(shè)ViewGroup B沒有攔截DOWN事件(還是View C來處理DOWN事件),但它攔截了接下來的MOVE事件。

DOWN事件傳遞到C的onTouchEvent方法,返回了true。
在后續(xù)到來的MOVE事件,B的onInterceptTouchEvent方法返回true攔截該MOVE事件,但該事件并沒有傳遞給B;這個MOVE事件將會被系統(tǒng)變成一個CANCEL事件傳遞給C的onTouchEvent方法
后續(xù)又來了一個MOVE事件,該MOVE事件才會直接傳遞給B的onTouchEvent()
后續(xù)事件將直接傳遞給B的onTouchEvent()處理
后續(xù)事件將不會再傳遞給B的onInterceptTouchEvent方法,該方法一旦返回一次true,就再也不會被調(diào)用了。
C再也不會收到該事件列產(chǎn)生的后續(xù)事件。

八、源碼分析

View中dispatchTouchEvent()的源碼分析

public boolean dispatchTouchEvent(MotionEvent event) {  
    if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&  
            mOnTouchListener.onTouch(this, event)) {  
        return true;  
    }  
    return onTouchEvent(event);  
}

從上面可以看出:

只有以下三個條件都為真,dispatchTouchEvent()才返回true;否則執(zhí)行onTouchEvent(event)方法

  • 第一個條件:mOnTouchListener != null;
  • 第二個條件:(mViewFlags & ENABLED_MASK) == ENABLED;
  • 第三個條件:mOnTouchListener.onTouch(this, event);

下面,我們來看看下這三個判斷條件:

第一個條件:mOnTouchListener!= null

//mOnTouchListener是在View類下setOnTouchListener方法里賦值的
public void setOnTouchListener(OnTouchListener l) { 

//即只要我們給控件注冊了Touch事件,mOnTouchListener就一定被賦值(不為空)
    mOnTouchListener = l;  
}

第二個條件:(mViewFlags & ENABLED_MASK) == ENABLED

該條件是判斷當前點擊的控件是否enable
由于很多View默認是enable的,因此該條件恒定為true

第三個條件:mOnTouchListener.onTouch(this, event)

回調(diào)控件注冊Touch事件時的onTouch方法

//手動調(diào)用設(shè)置
button.setOnTouchListener(new OnTouchListener() {  
  @Override  
  public boolean onTouch(View v, MotionEvent event) {  
      return false;  
  }  
});

如果在onTouch方法返回true,就會讓上述三個條件全部成立,從而整個方法直接返回true。

如果在onTouch方法里返回false,就會去執(zhí)行onTouchEvent(event)方法。

下面看onTouchEvent(event)**的源碼分析

主要這里面調(diào)用到了performClick()方法,關(guān)注這個方法即可

public boolean performClick() {  
    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);  
    if (mOnClickListener != null) {  
        playSoundEffect(SoundEffectConstants.CLICK);  
        mOnClickListener.onClick(this);  
        return true;  
    }  
    return false;  
}

只要mOnClickListener不為null,就會去調(diào)用onClick方法;
那么,mOnClickListener又是在哪里賦值的呢?請繼續(xù)看:

public void setOnClickListener(OnClickListener l) {  
    if (!isClickable()) {  
        setClickable(true);  
    }  
    mOnClickListener = l;  
}

當我們通過調(diào)用setOnClickListener方法來給控件注冊一個點擊事件時,就會給mOnClickListener賦值(不為空),即會回調(diào)onClick()。

結(jié)論

onTouch()的執(zhí)行高于onClick()

每當控件被點擊時:如果在回調(diào)onTouch()里返回false,就會讓dispatchTouchEvent方法返回false,那么就會執(zhí)行onTouchEvent();如果回調(diào)了setOnClickListener()來給控件注冊點擊事件的話,最后會在performClick()方法里回調(diào)onClick()。

onTouch()返回false(該事件沒被onTouch()消費掉) = 執(zhí)行onTouchEvent() = 執(zhí)行OnClick()
如果在回調(diào)onTouch()里返回true,就會讓dispatchTouchEvent方法返回true,那么將不會執(zhí)行onTouchEvent(),即onClick()也不會執(zhí)行;

onTouch()返回true(該事件被onTouch()消費掉) = dispatchTouchEvent()返回true(不會再繼續(xù)向下傳遞) = 不會執(zhí)行onTouchEvent() = 不會執(zhí)行OnClick()

onTouch()和onTouchEvent()的區(qū)別

這兩個方法都是在View的dispatchTouchEvent中調(diào)用,但onTouch優(yōu)先于onTouchEvent執(zhí)行。
如果在onTouch方法中返回true將事件消費掉,onTouchEvent()將不會再執(zhí)行。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容