《Android核心剖析》讀書筆記 第13章 View工作原理【觸摸訊息派發】

來源:互聯網
上載者:User

相比按鍵訊息,觸摸訊息也是由ViewRootImpl.WindowInputEventReceiver執行個體負責接收,然後判斷訊息類型之後執行不同的方法,對於觸摸訊息就是執行
deliverPointerEvent(.)方法;不同點主要以下幾點:

  1. 觸摸訊息由訊息擷取模組InputManagerService直接派發給應用程式,而無需經過Wms內部的預先處理,最新的版本中僅僅會對當螢幕關閉時執行
    interceptMotionBeforeQueueingWhenScreenOff(.);
  2. 觸摸訊息在處理時,需要根據觸摸點座標計算該訊息應該派發給哪個具體的View/ViewGroup,而在按鍵訊息處理中卻不存在該計算過程;
  3. 沒有類似於“系統按鍵”之類的“系統觸摸”,應用程式可以完全控制觸摸行為;
  4. 子視圖優先於父視圖進行訊息處理,這與按鍵訊息的處理機制完全相反;

螢幕座標/視圖座標/布局座標三者間的關係

  1. 螢幕座標:以螢幕左上方為(0,0)的座標體系,X/Y軸的最大值即為物理螢幕解析度的寬和高;
    觸摸訊息中MotionEvent.getX/getY取到的就是螢幕座標值;
  2. 視圖座標:視圖座標是完全由視圖內容的寬高決定的座標體系,理論上他是沒有邊界的,不受物理螢幕大小限制;
    比如1000行的文本限定寬度為100px,每行高度為5px,那對應的X/Y最大座標值為(100,5000);
  3. 布局座標:子視圖相對於父視圖而言的相對螢幕座標,以父視圖的左上方為(0,0),而不關心父視圖到底位於螢幕何處,X/Y最大座標值為父視圖的width/height;對於子視圖而言,若內容過多,超過父視圖分配的地區大小將由部分內容不能顯示,此時就會出現捲軸,視圖座標=布局座標+mScrollX/mScrollY;
    View體系下的getX/getY/getTop/getBottom/getLeft/getRight都是指的布局座標;
    子視圖的螢幕座標=子視圖的布局座標+父視圖的螢幕座標;對於視圖座標和布局座標兩者之間可以進行轉換,具體可以參見的案例,注意:空白地區不計尺寸,只是為了更明確的示範而已;

觸摸訊息總體派發過程

下面就介紹下ViewRootImpl接收到具體訊息執行deliverPointerEvent(.)方法的具體過程:

  1. 進行物理像素到邏輯像素的轉換;只有當物理螢幕像素與作業系統中識別的螢幕像素不一致時才需要,一般不需要這步;
  2. 如果是DOWN事件,調用ensureTouchMode(true)方法設定為觸摸模式,這會引起相關檢視狀態的變化,詳見後續說明;
  3. 將螢幕座標轉換成視圖座標,因為後續需要根據觸摸點的座標來確定到底由哪個視圖來處理訊息;
  4. 調用mView.dispatchTouchEvent()將訊息派發給根視圖,這裡的mView執行個體為PhoneWindow.DecorView;
    1. 判斷是否存在Window.Callback回調對象,其實就是Activity執行個體,若不存在,則直接調用DecorView.super.dispatchTouchEvent(ev),實際執行到的是ViewGroup.dispatchTouchEvent(.),注意:ViewGroup完全重載了該方法,未調用super方法;

      1. 執行 onInterceptTouchEvent(.);對當前事件進行攔截返回是否當前ViewGroup需要自己處理該訊息,用來控制事件的傳遞方向,開發人員可以通過調用 requestDisallowInterceptTouchEvent(.)設定是否允許做訊息攔截;
      2. 若不需要,則遍曆所有子視圖,通過isTransformedTouchPointInView(…)判斷當前觸摸點是否在子視圖以內,找到可以處理該訊息的子視圖,然後執行 dispatchTransformedTouchEvent(…),對座標進行相應轉換之後調用
        child.dispatchTouchEvent(.);對於子視圖child而言,若是ViewGroup執行個體,則迴圈之前的邏輯;若不是,則執行View.dispatchTouchEvent(.);

        1. 首先判斷當前View是否有設定過View.OnTouchListener介面Listener,若有,則執行Listener執行個體的
          onTouch(.)方法;
          注意:該Listener只對非ViewGroup子類有效,因為ViewGroup完全重載了dispatchTouchEvent方法,沒有機會調用到該Listener的方法;
        2. 若沒有,則直接執行onTouchEvent(.)方法;在基類View.onTouchEvent(.)方法中主要是完成了對tap、click、longClick三種點擊事件的處理,將其轉換為對
          OnClickListener/ OnLongClickListener介面的回調,開發人員只需要實現對應的Listener介面即可;其對應的事件處理模型如:

          注意:tap其實是沒有單獨的監聽Listener介面的,他也並不會改變視圖的狀態,但會引起視圖獲得焦點,所以我們的一些selector中對pressed狀態設定的UI效果對於tap而言是沒有作用的;因為tap的時間間隔只有180ms,所以要想體驗具體的UI效果,需要很快的輕觸才行;
      3. 若需要自己處理,則直接執行dispatchTransformedTouchEvent(…);
    2. 若存在Activity回調執行個體,則執行Activity.dispatchTouchEvent()方法;
      1. 如果是DOWN訊息,調用onUserInteraction();可以在訊息被處理之前做點什麼,該方法為按鍵和觸摸訊息通用,若重載了對兩者都起效;
      2. 執行PhoneWindow.superDispatchTouchEvent();其實就是轉交給DecorView.super.dispatchTouchEvent(.)處理,這點與沒有Activity回調執行個體是一致的,即無論如何都是先由View自身先處理訊息,然後才是Activity處理;
      3. 若View系統仍未處理訊息,則調用Activity.onTouchEvent(.),開發人員可以重載該方法實現自己的處理邏輯;

View/ViewGroup中dispatchTouchEvent/onInterceptTouchEvent/onTouchEvent的具體邏輯關係

  1. 在多層嵌套的View系統中,觸摸事件由頂層向底層傳遞,在ViewGroup中對dispatchTouchEvent(.)進行遞迴調用,子視圖優先父視圖進行訊息處理,整體一般呈現U型遞迴,參考所示,其中LayoutView1中包括子視圖LayoutView2,LayoutView2中包括子視圖TextView;
  2. dispatchTouchEvent為事件處理的源頭,View/ViewGroup兩者中的具體處理邏輯完全不一樣,詳見上面的具體內容;
    onTouchEvent是為開發人員開放的回調方法,用來處理具體的訊息,當然在預設實現中系統進一步抽取了很多的Listener回調介面供開發人員使用;
    onInterceptTouchEvent是ViewGroup特有,在dispatchTouchEvent中被調用,讓ViewGroup有機會決定是自己處理觸摸訊息,還是傳遞給子視圖進行處理;
  3. 觸摸事件都以DOWN事件開始,中間會經過多次的MOVE,最後以UP結束;
  4. 若ViewGroup.onInterceptTouchEvent()對DOWN事件處理返回false,則表示該ViewGroup不攔截該事件,交由子視圖處理;那麼後續的move, up等事件將繼續會先傳遞給該ViewGroup,之後再傳遞給子視圖處理。
    注意:若子視圖未處理該DOWN事件,然後當前ViewGroup.onTouchEvent處理了該事件,那之後的MOVE/UP事件將會跳過ViewGroup.onInterceptTouchEvent()而直接執行ViewGroup.onTouchEvent;
  5. 若ViewGroup.onInterceptTouchEvent()對DOWN事件處理返回true,那麼後續的MOVE/UP事件將不再傳遞給onInterceptTouchEvent(),而是直接傳遞給該ViewGroup的onTouchEvent()處理;
    注意,這種情況下子視圖View將接收不到任何事件,因為被父視圖攔截了。
  6. 若子視圖的onTouchEvent()返回了false,那麼當前事件將被傳遞至父視圖的onTouchEvent()處理,其後續事件將不會再傳遞至該子視圖。
    注意:這裡指的是對DOWN事件處理的情況,若是其他事件,即便子視圖的onTouchEvent()返回了false,該事件也不會傳遞至父視圖;
  7. 若子視圖的onTouchEvent()返回了true,那麼對於當前事件父視圖的onTouchEvent()將不會被觸發,其後續事件將繼續由該子視圖的onTouchEvent()處理,並且若當前子視圖仍是ViewGroup,那onInterceptTouchEvent()也將被跳過不會執行。
  8. 若對於DOWN事件最後沒有任何View.onTouchEvent()返回true,那後續的move、up事件將會被自動丟棄,不會進行任何傳遞;
  9. 若ViewGroup.onInterceptTouchEvent()對DOWN事件返回false,但卻對接下來的MOVE事件返回true,那此時系統將對子視圖發送一個CANCEL事件,當前MOVE事件的ViewGroup.onTouchEvent()不會被執行,即當前的MOVE事件被丟棄了,但接下去新的MOVE/UP訊息將直接進入ViewGroup.onTouchEvent()執行;

接下來針對以上描述的情況給出一些案例說明,簡單的情況參見http://blog.csdn.net/ddna/article/details/5473293;

但要注意:目前網路上大部分對觸摸事件傳遞的描述其實都是不夠準確的,大家可以結合以下的案例自己多琢磨琢磨;

因為觸摸訊息是一個事件系列,包括一次DOWN事件、多次MOVE事件、一次UP事件;對於每次事件都會涉及到onInterceptTouchEvent/onTouchEvent的執行,而每次執行你都可以根據需要返回true/false,這導致的組合情況就非常多了,但原則還是基於以上提到的這幾條,下面來看看以下案例:

  1. 執行條件如下:
    LayoutView1.onIntercept始終返回false;
    LayoutView2.onIntercept始終返回true;
    LayoutView1.onTouchEvent始終返回true;
    LayoutView2.onTouchEvent始終返回false;
    MyTextView.onTouchEvent始終返回true;
    執行結果如下:
    06-25 16:32:41.779: D/LayoutView1(25253): onInterceptTouchEvent action:ACTION_DOWN06-25 16:32:41.779: D/LayoutView2(25253): onInterceptTouchEvent action:ACTION_DOWN
    06-25 16:32:41.784: D/LayoutView2(25253): onTouchEvent action:ACTION_DOWN
    06-25 16:32:41.784: D/LayoutView1(25253): onTouchEvent action:ACTION_DOWN
    06-25 16:32:41.789: D/LayoutView1(25253): onTouchEvent action:ACTION_MOVE
    06-25 16:32:41.804: D/LayoutView1(25253): onTouchEvent action:ACTION_MOVE
    06-25 16:32:41.974: D/LayoutView1(25253): onTouchEvent action:ACTION_MOVE
    06-25 16:32:41.979: D/LayoutView1(25253): onTouchEvent action:ACTION_UP
    解讀如下:
    對於DOWN事件,LayoutView1.onIntercept執行但未攔截,LayoutView2.onIntercept執行並攔截,即可轉交給LayoutView2.onTouchEvent處理,但由於返回false,所以事件交由父視圖LayoutView1.onTouchEvent處理,並成功消耗返回true;
    對於後續MOVE/UP事件,直接由LayoutView1.onTouchEvent處理,LayoutView1/2的onIntercept均被略過了;
  2. 執行條件如下:
    LayoutView1.onIntercept始終返回false;
    LayoutView2.onIntercept對DOWN事件返回false,但對MOVE事件返回true;
    LayoutView1.onTouchEvent始終返回true;
    LayoutView2.onTouchEvent始終返回false;
    MyTextView.onTouchEvent始終返回false;
    執行結果如下:
    06-25 17:04:26.764: D/LayoutView1(21298): onInterceptTouchEvent action:ACTION_DOWN06-25 17:04:26.764: D/LayoutView2(21298): onInterceptTouchEvent action:ACTION_DOWN
    06-25 17:04:26.764: D/MyTextView(21298): onTouchEvent action:ACTION_DOWN
    06-25 17:04:26.764: D/LayoutView2(21298): onTouchEvent action:ACTION_DOWN
    06-25 17:04:26.764: D/LayoutView1(21298): onTouchEvent action:ACTION_DOWN
    06-25 17:04:26.804: D/LayoutView1(21298): onTouchEvent action:ACTION_MOVE
    06-25 17:04:26.819: D/LayoutView1(21298): onTouchEvent action:ACTION_MOVE
    06-25 17:04:27.059: D/LayoutView1(21298): onTouchEvent action:ACTION_MOVE
    06-25 17:04:27.064: D/LayoutView1(21298): onTouchEvent action:ACTION_UP
    解讀如下:
    對於DOWN事件,LayoutView1.onIntercept執行但未攔截,LayoutView2.onIntercept也未攔截,交由MyTextView.onTouchEvent處理,但由於返回false,所以事件交由父視圖LayoutView2.onTouchEvent執行,但可惜也返回false,繼續交由LayoutView1.onTouchEvent執行,成功消耗訊息返回true;
    對於後續MOVE/UP事件,直接由LayoutView1.onTouchEvent處理,LayoutView1/2的onIntercept均被略過了;
  3. 執行條件如下:
    LayoutView1.onIntercept始終返回false;
    LayoutView2.onIntercept對DOWN事件返回false,但對MOVE事件返回true;
    LayoutView1.onTouchEvent始終返回true;
    LayoutView2.onTouchEvent始終返回true;
    MyTextView.onTouchEvent始終返回true;
    執行結果如下:
    06-25 15:56:09.219: D/LayoutView1(28368): onInterceptTouchEvent action:ACTION_DOWN06-25 15:56:09.219: D/LayoutView2(28368): onInterceptTouchEvent action:ACTION_DOWN
    06-25 15:56:09.219: D/MyTextView(28368): onTouchEvent action:ACTION_DOWN
    06-25 15:56:09.254: D/LayoutView1(28368): onInterceptTouchEvent action:ACTION_MOVE
    06-25 15:56:09.254: D/LayoutView2(28368): onInterceptTouchEvent action:ACTION_MOVE
    06-25 15:56:09.254: D/MyTextView(28368): onTouchEvent action:ACTION_CANCEL
    06-25 15:56:09.274: D/LayoutView1(28368): onInterceptTouchEvent action:ACTION_MOVE
    06-25 15:56:09.274: D/LayoutView2(28368): onTouchEvent action:ACTION_MOVE
    06-25 15:56:09.389: D/LayoutView1(28368): onInterceptTouchEvent action:ACTION_MOVE
    06-25 15:56:09.389: D/LayoutView2(28368): onTouchEvent action:ACTION_MOVE
    06-25 15:56:09.389: D/LayoutView1(28368): onInterceptTouchEvent action:ACTION_UP
    06-25 15:56:09.389: D/LayoutView2(28368): onTouchEvent action:ACTION_UP
    解讀如下:
    對於DOWN事件,LayoutView1/2均沒有進行攔截,直接交由MyTextView處理;
    對於首次MOVE事件,LayoutView1執行了onInterceptTouchEvent方法但未攔截,LayoutView2執行onInterceptTouchEvent方法並進行了攔截,此次MOVE事件並未直接交由LayoutView2.onTouchEvent處理,而是向MyTextView發送了一次CANCEL事件,並執行MyTextView.onTouchEvent方法;
    對於後續MOVE/UP事件,LayoutView1執行了onInterceptTouchEvent方法仍未攔截,之後便直接進入LayoutView2.onTouchEvent處理了,一直到UP事件結束都不會執行LayoutView2.onInterceptTouchEvent方法了;
  4. 執行條件如下:
    LayoutView1.onIntercept始終返回false;
    LayoutView2.onIntercept對DOWN事件返回false,但對MOVE事件返回true;
    LayoutView1.onTouchEvent始終返回true;
    LayoutView2.onTouchEvent始終返回false;上個實驗這裡是返回true;
    MyTextView.onTouchEvent始終返回true;
    執行結果如下:
    06-25 17:18:48.914: D/LayoutView1(1819): onInterceptTouchEvent action:ACTION_DOWN06-25 17:18:48.914: D/LayoutView2(1819): onInterceptTouchEvent action:ACTION_DOWN
    06-25 17:18:48.914: D/MyTextView(1819): onTouchEvent action:ACTION_DOWN
    06-25 17:18:48.954: D/LayoutView1(1819): onInterceptTouchEvent action:ACTION_MOVE
    06-25 17:18:48.954: D/LayoutView2(1819): onInterceptTouchEvent action:ACTION_MOVE
    06-25 17:18:48.954: D/MyTextView(1819): onTouchEvent action:ACTION_CANCEL
    06-25 17:18:48.964: D/LayoutView1(1819): onInterceptTouchEvent action:ACTION_MOVE
    06-25 17:18:48.964: D/LayoutView2(1819): onTouchEvent action:ACTION_MOVE
    06-25 17:18:49.139: D/LayoutView1(1819): onInterceptTouchEvent action:ACTION_MOVE
    06-25 17:18:49.139: D/LayoutView2(1819): onTouchEvent action:ACTION_MOVE
    06-25 17:18:49.139: D/LayoutView1(1819): onInterceptTouchEvent action:ACTION_UP
    06-25 17:18:49.139: D/LayoutView2(1819): onTouchEvent action:ACTION_UP
    解讀如下:
    對於DOWN事件,LayoutView1/2均沒有進行攔截,直接交由MyTextView處理;
    對於首次MOVE事件,LayoutView1執行了onInterceptTouchEvent方法但未攔截,LayoutView2執行onInterceptTouchEvent方法並進行了攔截,此次MOVE事件並未直接交由LayoutView2.onTouchEvent處理,而是向MyTextView發送了一次CANCEL事件,並執行MyTextView.onTouchEvent方法;
    對於後續MOVE/UP事件,LayoutView1執行了onInterceptTouchEvent方法仍未攔截,之後便直接進入LayoutView2.onTouchEvent處理了,一直到UP事件結束都不會執行LayoutView2.onInterceptTouchEvent方法了;
    從執行結果來看和上個案例是完全一樣,為什麼LayoutView2.onTouchEvent返回false,該訊息卻未被交由父視圖執行LayoutView1.onTouchEvent呢?原因是此時事件已經不是DOWN事件了,只有在觸摸的初始DOWN事件中onTouchEvent才會向父視圖傳遞;
  5. 執行條件如下:
    LayoutView1.onIntercept始終返回false;
    LayoutView2.onIntercept始終返回false;
    LayoutView1.onTouchEvent始終返回true;
    LayoutView2.onTouchEvent始終返回false;
    MyTextView.onTouchEvent對DOWN/UP事件返回true,但對MOVE事件返回false;
    執行結果如下:
    06-25 18:05:21.029: D/LayoutView1(9095): onInterceptTouchEvent action:ACTION_DOWN06-25 18:05:21.029: D/LayoutView2(9095): onInterceptTouchEvent action:ACTION_DOWN
    06-25 18:05:21.029: D/MyTextView(9095): onTouchEvent action:ACTION_DOWN
    06-25 18:05:21.039: D/LayoutView1(9095): onInterceptTouchEvent action:ACTION_MOVE
    06-25 18:05:21.039: D/LayoutView2(9095): onInterceptTouchEvent action:ACTION_MOVE
    06-25 18:05:21.039: D/MyTextView(9095): onTouchEvent action:ACTION_MOVE
    06-25 18:05:21.224: D/LayoutView1(9095): onInterceptTouchEvent action:ACTION_MOVE
    06-25 18:05:21.224: D/LayoutView2(9095): onInterceptTouchEvent action:ACTION_MOVE
    06-25 18:05:21.224: D/MyTextView(9095): onTouchEvent action:ACTION_MOVE
    06-25 18:05:21.239: D/LayoutView1(9095): onInterceptTouchEvent action:ACTION_UP
    06-25 18:05:21.239: D/LayoutView2(9095): onInterceptTouchEvent action:ACTION_UP
    06-25 18:05:21.239: D/MyTextView(9095): onTouchEvent action:ACTION_UP
    解讀如下:
    對於DOWN/MOVE/UP事件,LayoutView1/2均執行了onInterceptTouchEvent方法但未攔截,都交由MyTextView.onTouchEvent處理;即便對DOWN事件處理返回false,該MOVE事件仍未交由父視圖LayoutView2.onTouchEvent處理;
    該執行個體和上面的執行個體一樣,只有為了說明:只有在觸摸的初始DOWN事件中onTouchEvent才會向父視圖傳遞;

以上內容若有轉載,請註明出處,歡迎訪問老唐的專欄http://blog.csdn.net/sfdev

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.