国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 系統 > Android > 正文

Android事件分發機制 - CSDN博客

2019-11-08 00:18:17
字體:
來源:轉載
供稿:網友

回想一下,通常在Android開發中,我們最常接觸到的是什么東西?顯然除了Activity以外,就是各種形形色色的控件(即View)了。 與此同時,一個App誕生的起因,終究是根據不同需求完成與用戶的各種交互。而所謂的交互,本質就是友好的響應用戶的各種操作行為。 所以說,有很多時候,一個控件(View)出現在屏幕當中,通常不會是僅僅為了擺設,而是還要能夠負責響應用戶的操作。 以最基本的例子而言:現在某一個界面中有一個按鈕(Button),而每當用戶點擊了該按鈕,我們的程序將做出一定回應。 那么,如果我們還原“點擊按鈕”這一行為,可以視作其根本實際就是:用戶的手指 與屏幕上該按鈕所處的位置發生了某種接觸。 所以簡單來說,我們可以將用戶每次與屏幕發生接觸,視作是一次“觸摸事件”。而對于每次“觸摸事件”的回應處理,就構成了所謂的“交互”。 由此,也就引出了我們今天在此文里想要弄清楚的一個知識點,即“Android的事件分發機制”。接著,我們就一步一步的來走進它。

為什么需要分發?

在一切開始之前,我們先考慮一個問題。那就是為什么我們發現 關于這個知識點總是被描述為“事件分發”而不是“事件處理”?試想以下情況:

小明注冊了一家公司開始了自己的創業之旅。這天,一位客戶上門為小明的公司帶來了第一單業務。我們可以將“這單業務”視作一次“觸摸事件”。 小明樂壞了,趕緊開始著手這單業務。對應來說,我們可以理解為對“觸摸事件”的處理。是的,目前為止我們都沒發現任何和“分發”相關的東西。 其實,我們不難想象到之所以沒有出現任何“分發”相關的東西,是因為現在公司只有小明獨自一人,除了自己處理,他別無它選。 對于“分發”這個詞本身就是以一定數量作為基礎的。比如小明的公司經過發展擴大到了十個人的規模,劃分了部門。這天又有新的業務來了。 這個時候小明就有很多選擇了,他可以選擇自己來處理這單業務;當然也可以選擇將業務派發下去讓員工來處理。這就是我們所謂的“分發”。

這個案例對應到Android中來說是一樣的,如果我們能保證當前屏幕上永遠只有唯一的一個View,那么對于“觸摸事件”的處理就沒什么好說的了。 但顯然一個界面中通常都不會只有一個“獨苗”,它會涉及到不定量的View(ViewGroup)的組合。這個時候當“事件”發生,就不那么容易處理了。 這其實也是為什么,“Android的事件分發機制”是我們從菜鳥到進階的過程中繞不開必須掌握的一個知識點。 但實際上也沒關系,當我們明白了“小明創業”的這個例子,實際也就已經掌握了關于事件分發最基本的原理。

看事件如何分發?

我們說了所有與用戶發生交互的行為都是在手機屏幕,即某個界面中產生的。而Android的界面中的元素,無非就是View與ViewGroup。 與之同時,我們關注的另一個關鍵點是代表操作行為的“觸摸事件”,這對應到英文中來說似乎就是“TouchEvent”。 OK,在我們目前兩眼一抹瞎的情況下。不妨試著以“TouchEvent”為關鍵字,到View與ViewGroup類中去研究研究相關的方法。

最終,我們單獨提出以下幾個需要我們理解的方法,它們也是事件分發機制的原理所在:

dispatchTouchEventonTouchEventonInterceptTouchEvent (只存在于ViewGroup)

好的,截止現在我們還不太明白這幾個方法的具體作用。只知道如果單獨從命名上看,它們似乎分別代表分發、處理和攔截類似的作用。

起源在哪?

從現在開始,我們要正式的一步步研究一個觸摸事件的處理過程。我們先來看一個十分基礎但具有一定代表性的布局:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:tag="group_a" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <LinearLayout android:tag="group_b" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <Button android:tag="view" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout></LinearLayout>12345678910111213141516171819201234567891011121314151617181920

但如果是這樣,我們還是無法研究其事件的分發過程,所以我們自定義兩個類分別繼承LinearLayout以及Button類。 而我們要做的工作也很簡單,就是在我們之前提及的與“事件分發”相關的方法中,分別加上類似如下的日志打印:

Log.d(getTag().toString(),”onTouchEvent”);

然后我們將布局文件中的LinearLayout和Button分別替換成我們自己定義的類,就搞定了。此時,我們運行程序后,對按鈕進行點擊,得到如下日志:

group_a: dispatchTouchEvent group_a: onInterceptTouchEvent group_b: dispatchTouchEvent group_b: onInterceptTouchEvent view: dispatchTouchEvent view: onTouchEvent

一口氣吃不成一個胖子,我們這里首先只關注一點,那就是:當我們點擊按鈕后,最先被觸發的是“group_a“也就是最外層layout的dispatchTouchEvent方法。這個道理其實并不難理解,就像“小明創業”一樣,如果一單業務來臨,自然應該先由最高級別的“小明”知會。

也就是說,當一個“觸摸事件”產生,將最先傳送到最頂層View。這看上去合情合理,但我們又不難想到,如果談到“最頂層”的話: 那么,我們所有的View,即我們整個布局文件,它實際也有一個載體,那就是它的宿主Activity。因為你一定記得”setContenView(R.layout.xxx)”。 那么,我們想象一下,“觸摸事件”會不會最先被傳送到Activity呢?打開Activity的源碼我們驚喜的發現:它也包含dispatchTouchEvent和OnTouchEvent方法。OK,我們接著要做的,自然就是在我們的Activity類中覆寫這兩個方法,也加上日志打印,再次運行測試得到如下日志:

MainActivity: dispatchTouchEvent group_a: dispatchTouchEvent group_a: onInterceptTouchEvent group_b: dispatchTouchEvent group_b: onInterceptTouchEvent view: dispatchTouchEvent view: onTouchEvent

通過日志,我們不難發現,正如我們推測的一樣,當觸摸事件產生,的確是最先傳遞給Activity的。 這個時候,又要提到“小明”了。沒錯,當有業務來臨,肯定是先到達“公司”層面,然后才是最高負責人“小明”。

打開Activity類的dispatchTouchEvent源碼如下:

public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction(); } if (getWindow().superDispatchTouchEvent(ev)) { return true; } return onTouchEvent(ev); }123456789123456789

我們關注的重點放在下面一行代碼上。由此,我們發現Activity接收到觸摸事件后,是通過其所屬的Window來分發事件的。

if (getWindow().superDispatchTouchEvent(ev))11

Window自身是一個抽象類,而其superDispatchTouchEvent也是一個抽象方法。其唯一實現存在PhoneWindow類中。 而在PhoneWindow當中,superDispatchTouchEvent的實現是這樣的:

public boolean superDispatchTouchEvent(MotionEvent event){ return mDecor.superDispatchTouchEvent(event); }123123

也就是,這時候事件的分發會傳遞到mDecor,即Android視圖結構中所謂的“DecorView”當中,而DecorView自身本就是FrameLayout。 所以,這個時候事件分發的本質就很簡單了,它回歸到了FrameLayout,即ViewGroup的事件分發。

P.S: DecorView涉及到了Android的UI界面架構的知識。我們通常可以最簡單的理解為一個Activiy就是一個界面。

但其實隨著對Android越來越熟悉,我們也可以很容易猜測到,實際上肯定不僅僅如此。那么我們可以這樣簡單理解:

Activity會附屬在PhoneWindow上;這其中會首先包裹一個DecorView(翻譯就是裝飾視圖),它本身是一個FrameLayout。 (所以,我們其實可以理解為,DecorView才是我們界面中真正位于最頂層的父View)而同時,通常來說,DecorView中會有兩個子View,分別是:TitleView及ContentView。其中,TitleView簡單來說就是我們平時所說的ActionBar(TitleBar)的那一列。而ContentView我們就熟悉了:“setContenView(R.layout.xxx)”

好了,話到這里,我們先對我們目前的收獲進行總結,很簡單: 當一個“觸摸事件”產生,會首先傳遞到當前Activity。Activity會通過其所屬Window,找到其中DecorView,從而開始逐級向下分發事件。

繼續傳遞

回憶我們點擊按鈕時,所產生的輸出日志:

MainActivity: dispatchTouchEvent group_a: dispatchTouchEvent group_a: onInterceptTouchEvent group_b: dispatchTouchEvent group_b: onInterceptTouchEvent view: dispatchTouchEvent view: onTouchEvent

我們發現,經由MainActivity進行傳遞,最終會首先到達該Activity的ContentView,即我們定義的布局文件中的最外層LinearLayou上。 這之后發生的事,我們通過日志觀察的現象似乎是:事件在繼續向下傳遞,直到最后到達了最里的Button,才通過onTouchEvent進行了處理。 所以,我們大膽猜測:一個“觸摸事件”發生,會逐級dispatchTouchEvent,直至到達最底層的View,然后通過onTouchEvent進行處理。 這看上去是說得通的,但唯一的缺陷就在于,onInterceptTouchEvent 這個名字透露著攔截事件的方法,似乎并沒有得到什么發揮?

那么,我們干脆直接打開ViewGroup中onInterceptTouchEvent的源碼:

public boolean onInterceptTouchEvent(MotionEvent ev) { return false; }123123

我們發現源碼特別簡單,唯一值得注意的是,該方法有一個布爾型的返回值,并且默認的返回值是false。 秉承著一貫的“手賤主義”的思想,我們將我們自定義的LinearLayou類中的onInterceptTouchEvent返回值修改為true。然后再次測試:

MainActivity: dispatchTouchEvent group_a: dispatchTouchEvent group_a: onInterceptTouchEvent group_a: onTouchEvent

我們發現日志打印已經發生了改變,通過日志我們觀察到的是:事件傳遞到group_a后不再向下傳遞了,直接通過group_a的onTouchEvent進行處理。 這一現象其實是合理的,它告訴了我們:onInterceptTouchEvent的返回結果將決定事件是否被攔截在當前View層面。返回true則攔截;否則繼續傳遞。 而onInterceptTouchEvent的返回結果之所以能實現上述效果,可以在ViewGroup類中的dispatchTouchEvent方法里找到答案。 ViewGroup當中的dispatchTouchEvent源碼很多,也很復雜,我們很難去讀個透徹。但通過部分關鍵代碼,我們可以知道它的原理是如下: ViewGroup里,dispatchTouchEvent里會調用到onInterceptTouchEvent方法,并通過判斷其返回結果決定下一步操作:

如果onInterceptTouchEvent返回true,則會調用onTouchEvent方法處理觸摸事件。如果onInterceptTouchEvent返回false,則會通過child.dispatchTouchEvent的形式向下分發事件。

到現在,看上去我們基本已經對事件的整個分發流程都觸碰到了。但我們想象這樣一種情況: 小明的公司接收了一單新的業務,并分發給了底下的小王去做。但這時候的問題可能是: 小王收到通知后發現自己完成不了。或者說小王給出了一套方案,但他對該方案的信心并不足。 這樣的問題應該怎么解決,很顯然,小王應該“報告老板”。這單業務最后的處理方案可能還是得您來拿。

這對應到我們的程序中,應該如何來實現呢?我們發現,onTouchEvent這個方法也有一個布爾型的返回值。 現在,我們試著將我們之前自定義的Button類里的onTouchEvent的返回值修改為固定返回false。再次運行程序測試:

MainActivity: dispatchTouchEvent group_a: dispatchTouchEvent group_a: onInterceptTouchEvent group_b: dispatchTouchEvent group_b: onInterceptTouchEvent view: dispatchTouchEvent view: onTouchEvent group_b: onTouchEvent group_a: onTouchEvent

我們發現,事件在經過button類里onTouchEvent處理后, 又回傳到上層目錄繼續處理了。 由此我們可以知道:onTouchEvent的返回值也是另一種“攔截”,不同的是之前我們說的是攔截事件繼續向下傳遞。 而這里,是在表明,這個事件“我”是否能夠完全處理。如果能,則返回true,那么事件經過“我”處理后就結束了。否則返回false,再讓父View去處理。

好了,這里我們先總結一下到目前為止,我們掌握到的關于事件分發機制的流程:

當一個觸摸事件產生,會首先傳遞到其所屬Activity。Activity將負責將事件向下進行分發。該觸摸事件將逐級的依次向下傳遞,直到傳遞至最底層View,然后進行處理。ViewGroup在向下傳遞事件的時候,通過onInterceptTouchEvent的返回值來判斷是否攔截事件。如果確定攔截事件,那么此次一系列的觸摸事件都會通過該ViewGroup的onTouchEvent方法進行處理,并不再向下傳遞onTouchEvent的返回結果決定了事件在此次處理之后,是否需要回傳到上一級的View。

好了,我們繼續看。我們肯定也注意到了,說了這么多,但我們之前關于事件分發的重點,都是放在上一層View向下層View分發事件的過程。 也就是說,之前我們的重點是在分析ViewGroup的dispatchTouchEvent方法,我們說了該方法會判斷是否攔截事件,而決定是否繼續向下傳遞事件。 當就拿我們此文中的例子來說,當事件最終傳遞到button類當中。我們不難想象到,這時dispatchTouchEvent的工作肯定與之前有所不同。 因為Button自身只是一個View,它不會存在所謂的子視圖。也就說,這個時候事件你肯定要做出處理了,那么還分發個什么勁呢?

要得到這個問題的答案,最好的方式當然就是打開View類里的dispatchTouchEvent方法的源碼看看,這里我們只截取我們關心的代碼部分:

if (onFilterTouchEventForSecurity(event)) { //noinspection SimplifiableIfStatement ListenerInfo li = mListenerInfo; if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { result = true; } if (!result && onTouchEvent(event)) { result = true; } }1234567891011121312345678910111213

我們看到,首先會獲取一個ListenerInfo類型的變量。該類型實際就是封裝了各種類型的Listener。隨后會開啟第一次判斷: 如果此對象不為空,mOnTouchListener不為空,且(mViewFlags & ENABLED_MASK) ==ENABLED,則會執行mOnTouchListener。 這其中(mViewFlags & ENABLED_MASK) ==ENABLED是判斷控件狀態是否是ENABLE的,默認情況肯定是的。 mOnTouchListener看著有些眼熟,如果我們查看源碼,你就更眼熟了,因為我們發現它是通過如下代碼進行賦值的:

public void setOnTouchListener(OnTouchListener l) { getListenerInfo().mOnTouchListener = l; }123123

接著看代碼,我們發現如果li.mOnTouchListener.onTouch(this, event)執行的結果也為true,那么result也將被設置為true。 假設該方法的執行結果返回是false,那么以上代碼將繼續進行,從而開始第二輪判斷,于是onTouchEvent方法得以執行。

OK,對于這里的代碼,我覺得有兩點是值得一說的。第一是,我們首先可以明白如下的結論:

假如我們為某個View設置了OnTouchListener,那么listener會先于onTouchEvent執行。同時:如果listener的onTouch方法返回true,那么onTouchEvent將不再執行。

第二點是,我們看到對于View來說:假設onTouchEvent的返回值為false,那么dispatchTouchEvent也會返回false。 這實際上就構成了整個“onTouchEvent返回值可以決定事件是否回傳”的原因。這是因為: ViewGroup在通過child.dispatchTouchEvent向下分發事件時,會通過該返回值來判斷子視圖是否具備處理事件的能力。 如果返回為false,就會繼續遍歷子視圖,直至遍歷到有一個具備事件處理能力的子視圖為止。 如果沒有一個具備處理能力的子視圖,那么ViewGroup就會自己通過onTouchEvent來解決了。

到了這里,我們實際上已經對整個Android事件的分發機制有了不錯的了解了。但我們肯定會想到一個問題: 那就是,以Button來說,我們通常是通過setOnClickListener的方式來對其設置點擊事件監聽的。 但很顯然,我們目前為止研究過的代碼中,還沒有出現任何與之相關的東西。在View類中,我們剩下沒有看過代碼的,就是onTouchEvent了:

if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) { switch (action) { case MotionEvent.ACTION_UP: boolean PRepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0; if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) { // take focus if we don't have it already and we should in // touch mode. boolean focusTaken = false; if (isFocusable() && isFocusableInTouchMode() && !isFocused()) { focusTaken = requestFocus(); } if (prepressed) { // The button is being released before we actually // showed it as pressed. Make it show the pressed // state now (before scheduling the click) to ensure // the user sees it. setPressed(true, x, y); } if (!mHasperformedLongPress && !mIgnoreNextUpEvent) { // This is a tap, so remove the longpress check removeLongPressCallback(); // Only perform take click actions if we were in the pressed state if (!focusTaken) { // Use a Runnable and post this rather than calling // performClick directly. This lets other visual state // of the view update before click actions start. if (mPerformClick == null) { mPerformClick = new PerformClick(); } if (!post(mPerformClick)) { performClick(); } } }123456789101112131415161718192021222324252627282930313233343536373839123456789101112131415161718192021222324252627282930313233343536373839

以上代碼是我們關心的原因所在,我們看到經過一系列的判斷后,會進入到一個名為performClick的方法調用,打開該方法的源碼:

public boolean performClick() { final boolean result; final ListenerInfo li = mListenerInfo; if (li != null && li.mOnClickListener != null) { playSoundEffect(SoundEffectConstants.CLICK); li.mOnClickListener.onClick(this); result = true; } else { result = false; } sendaccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); return result; }12345678910111213141234567891011121314

綜合以上兩段代碼,我們可以得出以下結論:

onClickListener會在onTouchEvent方法中,ACTION為UP的時候得以執行。但注意前提條件是,控件是可點擊(長按)的;并且mOnClickListener不為空。

最后的總結與比喻

最后,我們仍舊以“小明創業“的例子來總結我們所學到的關于Android的事件分發的相關知識。

公司(Activity)接收了一項新的業務(觸摸事件)。該業務會最先到達公司的最高層小明手上(最頂層View)。小明研究了以下此次業務,此時可以分為兩種情況: 1、小明認為此業務難度不大,于是決定將業務分配給部門經理小王(dispatch-event)。 2、小明認為此次業務至關重要,決定自己處理(intercept-event & onTouchEvent)。假設業務分配給了經理小王,此時小王與小明一樣,同樣有兩種選擇:即自己攔截處理或者繼續向下布置。業務最終終將被分配到某個員工的手中,它會負責處理(onTouchEvent)。但該員工接收到任務后,也可以判斷自己是否能夠完成,如果覺得不能完成, 或者還需要上級審核,可以選擇回報給上級(onTouchEvent返回false)員工處理該任務的方式也許有多種,例如A(OnTouchListener)、B(onTouchEvent)、C(OnClickListener) 等等。 (它們的優先級順序是 A > B > C 。。。。 )
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 和田市| 临清市| 瑞安市| 台安县| 泽库县| 漳平市| 金昌市| 七台河市| 湘潭县| 揭西县| 汾阳市| 筠连县| 容城县| 博兴县| 无棣县| 津南区| 肥乡县| 横山县| 新余市| 敦煌市| 大埔县| 松阳县| 台北市| 稻城县| 甘德县| 繁昌县| 金阳县| 西吉县| 沈阳市| 平南县| 沙洋县| 金沙县| 柞水县| 仲巴县| 宝兴县| 鄂州市| 沙湾县| 象山县| 高青县| 麻江县| 个旧市|