Android事件分發機制理解

來源:互聯網
上載者:User

標籤:

預備知識 觸摸事件 :
  1. 安卓中把觸摸事件封裝成了一個類MotionEvent,使用者的一次點擊、觸摸或者滑動都會產生一系列的MotionEvent
  2. 這個類的內容很簡單,就兩個東西:事件類型+座標xy
  3. 事件類型有四種
    1. MotionEvent.ACTION_DOWN 表示使用者的手指剛接觸到螢幕
    2. MotionEvent.ACTION_MOVE 表示使用者的手指正在移動
    3. MotionEvent.ACTION_UP 表示使用者的手指從螢幕上抬起
    4. Cancel
  4. 所以一次使用者觸控螢幕幕可能會產生這些事件:
    1. 點擊螢幕然後鬆開,Down->Up
    2. 點擊螢幕,然後滑動一段距離,鬆開螢幕 ,Down->Move->…->Move->Up
  事件分發方法:在事件分發的過程中,主要涉及到三個方法
  1. dispatchTouchEvent(MotionEvent event)
  2. onInterceptTouchEvent(MotionEvent event)
  3. onTouchEvent(MotionEvent event)
   假設:
  1. 只考慮最重要的四個觸摸事件,即:DOWN,MOVE,UP和CANCEL
  2. 一個手勢(gesture)是一個事件列
    1. 以一個DOWN事件開始(當使用者觸控螢幕幕時產生)
    2. 後跟0個或多個MOVE事件(當使用者四處移動手指時產生)
    3. 最後跟一個單獨的UP或CANCEL事件(當使用者手指離開螢幕或者系統告訴你手勢(gesture)由於其他原因結束時產生)
  3. 當我們說到“手勢剩餘部分”時指的是手勢後續的MOVE事件和最後的UP或CANCEL事件
  4. 不考慮多點觸摸手勢(我們只假設用一個手指)
  5. 忽略多個MOVE事件可以被歸為一組這一實際情況
  6. 假設文中的view都沒有註冊onTouchListener
  7. 假設一種視圖層次:
    1. 最外層是一個ViewGroup A,包含一個或多個子view(children),其中一個子view是ViewGroup B,ViewGroupB中又包含一個或多個子view,其中一個子view是 View C,C不是一個ViewGroup
    2. 忽略同層級view之間可能的交叉疊加
  8. 假設情況:
    1. 使用者首先觸摸到的螢幕上的點是C上的某個點,該點被標記為觸摸點(touch point),DOWN事件就在該點產生
    2. 然後使用者移動手指並最後離開螢幕,此過程中手指是否離開C的地區無關緊要,關鍵是手勢(gesture)是從哪裡開始的
  9. 假設不考慮dispatchTouchEvent方法
  假設不考慮 onInterceptTouchEvent,同時 沒有重寫事件分發方法時:
  1. DOWN事件被傳到C的onTouchEvent方法中,該方法返回false,表示“我不關心這個手勢(gesture)”
  2. 因此,DOWN事件被傳到B的onTouchEvent方法中,該方法同樣返回false,表示B也不關心這個手勢
  3. 同樣,因為B不關心這個手勢,DOWN事件被傳到A的onTouchEvent方法中,該方法也返回false
  4. 由於沒有view關心這個手勢(gesture),它們將不再會從“手勢剩餘部分”中接收任何事件
(一個更好的情況描述是打log看哪些方法被調用)  假設不考慮 onInterceptTouchEvent,但 重寫事件分發(加上處理事件)假設C實際上是關心這個手勢(gesture)的,原因可能是C被設定成可點擊的(clickable)或者你覆寫了C的onTouchEvent方法
  1. DOWN事件被傳遞給C的onTouchEvent方法,該方法可以做任何它想做的事情,最後返回true。
  2. 因為C說它正在處理這個手勢(gesture),則DOWN事件將不再被傳遞給B和A的onTouchEvent方法。
  3. 因為C說它正在處理這個手勢(gesture),所以“手勢剩餘部分”的事件也將傳遞給C的onTouchEvent方法,此時該方法返回true或false都無關緊要了,但是為保持一致最好還是返回true。
 從這裡可以看出,各個View的onTouchEvent方法對DOWN事件的處理,代表了該View對以此DOWN開始的整個手勢(gesture)的處理意願,返回true代表願意處理該gesture,返回false代表不願意處理該gesture  加上onInterceptTouchEvent但不攔截
  1. onInterceptTouchEvent方法它只存在於ViewGroup中,普通的View中沒有這個方法
  2. 在任何一個view的onTouchEvent被調用之前,它的父輩們將先獲得攔截這個事件的一次機會
  3. 換句話說,它們可以竊取該事件。在剛才的“處理事件”部分中,我們遺漏了這一過程
 現在我們把它加上,情況是這樣:
  1. DOWN事件被傳給A的onInterceptTouchEvent,該方法返回false,表示它不想攔截。
  2. DOWN又被傳遞給B的onInterceptTouchEvent,它也不想攔截,因此該方法也返回false。
  3. 現在,DOWN事件被傳遞到C的onTouchEvent方法,該方法返回true,因為它想處理以該事件為首的手勢(gesture)。
  4. 現在,該手勢的下一個事件MOVE到來了。這個MOVE事件再一次被傳遞給A的onInterceptTouchEvent方法,該方法再一次返回false,B也同樣如此。
  5. 然後,MOVE事件被傳遞給C的onTouchEvent,就像在前一部分中一樣。
  6. “手勢剩餘部分”中其他事件的處理過程和上面一樣,假如A和B的onInterceptTouchEvent方法繼續返回false的話
 這裡有兩點需要注意:
  1. 雖然ViewGroup A和B的onInterceptTouchEvent方法對DOWN事件返回了false,後續的事件依然會傳遞給它們的onInterceptTouchEvent方法,這一點與onTouchEvent的行為是不一樣的
  2. 假如DOWN事件傳給C的onTouchEvent方法時,它返回了false,DOWN事件會繼續向上傳遞給B和A的onTouchEvent,即使它們在onInterceptTouchEvent方法中說它們不想攔截這個DOWN事件,兩者是獨立的
 由此可見,DOWN事件的處理實際上經曆了一下一上兩個過程,下是指A->B的onInterceptTouchEvent,上是指C->B->A的onTouchEvent,當然,任意一步的方法中返回true,都能阻止它繼續傳播。  onInterceptTouchEvent 攔截事件讓我們更進一步,假設B沒有攔截DOWN事件,但它攔截了接下來的MOVE事件。原因可能是B是一個scrolling view。當使用者僅僅在它的地區內點擊(tap)時,被點擊到的元素應當能處理該點擊事件。但是當使用者手指移動了一定的距離後,就不能再視該手勢(gesture)為點擊了——很明顯,使用者是想scroll。這就是為什麼B要接管該手勢(gesture)。 下面是事件被處理的順序:
  1. DOWN事件被依次傳到A和B的onInterceptTouchEvent方法中,它們都返回的false,因為它們目前還不想攔截。
  2. DOWN事件傳遞到C的onTouchEvent方法,返回了true。
  3. 在後續到來MOVE事件時,A的onInterceptTouchEvent方法仍然返回false。
  4. B的onInterceptTouchEvent方法收到了該MOVE事件,此時B注意到使用者手指移動距離已經超過了一定的threshold(或者稱為slop)。因此,B的onInterceptTouchEvent方法決定返回true,從而接管該手勢(gesture)後續的處理。
  5. 然後,這個MOVE事件將會被系統變成一個CANCEL事件,這個CANCEL事件將會傳遞給C的onTouchEvent方法。
  6. 現在,又來了一個MOVE事件,它被傳遞給A的onInterceptTouchEvent方法,A還是不關心該事件,因此onInterceptTouchEvent方法繼續返回false。
  7. 此時,該MOVE事件將不會再傳遞給B的onInterceptTouchEvent方法,該方法一旦返回一次true,就再也不會被調用了。事實上,該MOVE以及“手勢剩餘部分”都將傳遞給B的onTouchEvent方法(於是根本不會傳給子view)(除非A決定攔截“手勢剩餘部分”)。
  8. C再也不會收到該手勢(gesture)產生的任何事件了
 下面的一些小事情可能會令你感到吃驚:
  1. 如果一個ViewGroup攔截了最初的DOWN事件,該事件仍然會傳遞到該ViewGroup的onTouchEvent方法中。
  2. 另一方面,如果ViewGroup攔截了一個半路的事件(比如,MOVE),這個事件將會被系統變成一個CANCEL事件,並傳遞給之前處理該手勢(gesture)的子View,而且不會再傳遞(無論是被攔截的MOVE還是系統產生的CANCEL)給ViewGroup的onTouchEvent方法。只有再到來的事件才會傳遞到ViewGroup的onTouchEvent方法中。
 從此開始,你可以更進一步:
  1. 比如對覆寫 requestDisallowInterceptTouchEvent,C可以用該方法阻止B竊取事件
  2. 如果你想更加瘋狂一點,你可以在你自己的ViewGroup中直接覆寫dispatchTouchEvent方法,並對傳遞進來的事件做任何你想做的處理
但這樣的話你可能會破壞一些約定,所以應當小心  假設不考慮事件的類型同時考慮第一個dispatch方法,並且在三個方法中打上log,能看到事件傳遞過程是這樣的(Android群英傳裡寫得很好) ViewGroup能說三句話:我收到了,我轉寄了,我嘗試處理一下View只能說兩句話:我收到了,我嘗試處理一下一個事件傳遞進來
  1. ViewGroupA首先收到事件,說一聲,我收到了
  2. 但是他先不處理,它先轉寄給子View,然後說,我轉寄了
  3. ViewGroupB然後收到事件,也說一聲,我收到了
  4. 但是他也先不處理,它先轉寄給子View,然後說,我轉寄了
  5. ViewC最後收到事件,那也說一聲,我收到了
  6. (--------上面是事件傳遞過程,下面是事件處理過程--------)
  7. 他直接嘗試處理,說,我嘗試處理一下
  8. (結果發現自己無法處理,沒有消耗事件,於是事件原路返回)
  9. ViewGroupB收到返回的事件,那就處理一下唄,於是說,我嘗試處理一下
  10. ViewGroupB也收到返回的事件,也說,我嘗試處理一下
三句話對應的ViewGroup的方法是(感覺對應得有點怪,因為這個中文名只是為了好記,其實並不是本意)
  1. 我收到了:dispatchTouchEvent [派遣,發送]
  2. 我轉寄了:onInterceptTouchEvent [攔截]
  3. 我嘗試處理一下:onTouchEvent
 然後這三個方法都有傳回值
  1. 前兩句話是事件傳遞過程的
    1. 返回true,表示攔截,到此中斷,就不往下走了;
    2. 返回false,表示不攔截,繼續往下走
  2. 第三句話是事件處理過程的(完全一模一樣,只是意義不同)
    1. 返回true,表示攔截,到此中斷,就不往下走了;
    2. 返回false,表示不攔截,繼續往下走
  3. 預設情況下,三個方法都是返回false
    1. 而每個地方其實都可以改成true,這樣,迴路就直接中斷了
    2. 但是如果在onInterceptTouchEvent這個方法中返回true,那麼不是直接結束迴路,而是會跳到對應的onTouchEvent迴路也就是事件處理迴路中,並且所有外層的onTouchEvent都會被執行
    3. 在 onTouchEvent方法中返回true,就確實是直接中斷
 而在重寫方法時,雖然dispatchTouchEvent是第一步,但是我們一般不會重寫它,所以我們一般關注後兩個方法  論事件的流向的複雜性 事件的流向跟這幾個因素有關:
  1. 這個View是什麼;兩種情況:View、ViewGroup;會影響回調方法數
  2. 這個方法是什麼;三種情況:;兩類情況:事件傳遞過程、事件處理過程;會影響後續怎麼跳
  3. 這個方法返回什麼;兩種情況:true、false;會影響是否結束迴路
  4. 這個事件的類型;四種情況:DOWN,MOVE,UP和CANCEL
所以這塊的邏輯真是有點亂。。。  虛擬碼解釋法 很多人用這段虛擬碼來理清三個分發方法間的關係(我個人是覺得一下子拔高了理解門檻):
public boolean dispatchTouchEvent(MotionEvent event) {
    boolean consume = false;
    if (onInterceptTouchEvent(event)) {
        consume = onTouchEvent(event);
    } else {
        consume = child.dispatchTouchEvent(event);
    }
    return consume;}
這段解釋倒是不錯:
  1. 在dispatchTouchEvent中,先調用ViewGroup自身的onInterceptTouchEvent方法,判斷自己是否要攔截
    1. 如果這時候自己攔截,那就調用自己的onTouchEvent方法
      1. 如果onTouchEvent方法返回了True,那麼這次的事件就算消耗了,事件傳遞到此為止
      2. 如果返回了False,證明這次沒有消耗這次MotionEvent,那麼這次的事件就會往上返回,由上一級繼續處理;
    2. 如果當前ViewGroup的onInterceptTouchEvent返回了False,那就會調用它的子view的dispatchTouchEvent方法,這樣這個事件就傳遞下去了
  2. 如果它的子View處理不了,那麼還會回來調用ViewGroup的onTouchEvent方法,當然這一點是沒有在這一段虛擬碼裡體現的
 這就是ViewGroup層的事件分發,當然不是這麼簡單,這隻不過是通過簡單的方式去理解,其實在真實的事件分發中,有很多問題需要注意:
  1. 一個完成的事件序列以Down開始,中間可能包含若干個Move,然後以Up結束
  2. 一個view一旦攔截某個事件,當前事件所在的完整事件序列將都會由這個view去處理
    1. 反應在真實的代碼中,就是一旦view攔截了down事件,那麼此後的move和up事件都將不調用onInterceptTouchEvent,而直接由它處理
    2. 這就也意味著在onInterceptTouchEvent處理事件是不合適的,因為有可能來了事件,卻直接跳過onInterceptTouchEvent方法
    3. 這個也意味著,一旦一個ViewGroup沒有攔截ACTION_DOWN,那麼這個事件序列的其他Action,它都將收不到,所以在處理ACTION_DOWN的時候,尤其需要謹慎
    4. ( 哦,所以這就很清晰了,dispatch只是入口,它什麼都不幹,然後intercept是一個第一次攔截的標誌,只會被調用一次,最後ontouch才是真正處理事情,根據不同的event做不同的處理 )
  3. onTouchEvent中是要判斷MotionEvent的Action,因為一次點擊操作就會調用兩次onTouchEvent方法,一次是ACTION_DOWN,一次是ACTION_UP,如果手滑一下,還會有若干個ACTION_MOVE
  4. ViewGroup預設不攔截任何事件,源碼中ViewGroup的onInterceptTouchEvent方法預設返回的是false
  5. 整個事件分發,看起來都是由外向內傳遞的,父View將事件傳遞給子View,理論上來看,子View是沒有辦法影響到父View的事件處理的,但是有一個標示位,requestDisallowInterceptTouchEvent方法,通過這個方法 ,子View能夠影響父view的事件處理,這個可以用於解決父view和子view的滑動衝突
 參考資料:http://www.jianshu.com/p/2be492c1df96

Android事件分發機制理解

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.