Android事件分發(fā)機制詳解

前言

Android事件分發(fā)機制是Android開發(fā)者必須了解的基礎(chǔ)。

目錄

一. 基礎(chǔ)認知

1.1 事件分發(fā)的由來

安卓的View是樹形結(jié)構(gòu)的蒙袍,View可能會重疊在一起腕巡,當我們點擊的地方有多個View都可以響應(yīng)的時候玄坦,這個點擊事件應(yīng)該給誰呢?為了解決這一個問題绘沉,就有了事件分發(fā)機制煎楣。

1.2 事件分發(fā)的 "事件" 是指什么?

點擊事件 (Touch事件) 车伞。具體介紹如下:


特別說明:事件列择懂,即指從手指接觸屏幕至手指離開屏幕這個過程產(chǎn)生的一系列事件。一般情況下另玖,事件列都是以DOWN事件開始困曙、UP事件結(jié)束,中間有無數(shù)的MOVE事件谦去。

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

將點擊事件(MotionEvent)傳遞到某個具體的View & 處理的整個過程慷丽。’

即 事件傳遞的過程 = 分發(fā)過程鳄哭。

1.4 事件在哪些對象之間進行傳遞要糊?

答:Activity、ViewGroup窃诉、View杨耙。


1.5 事件分發(fā)的順序

即 事件傳遞的順序:Activity -> ViewGroup -> View

即:1個點擊事件發(fā)生后赤套,事件先傳到Activity、再傳到ViewGroup珊膜、最終再傳到View

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

答:dispatchTouchEvent()onInterceptTouchEvent()onTouchEvent()

二. 事件分發(fā)機制流程詳細分析

主要包括:Activity事件分發(fā)機制车柠、ViewGroup事件分發(fā)機制剔氏、View事件分發(fā)機制

2.1 Activity事件分發(fā)機制

事件分發(fā)機制,首先會將點擊事件傳遞到Activity中竹祷,具體是執(zhí)行dispatchTouchEvent()進行事件分發(fā)谈跛。

源碼分析

/**
  * 源碼分析:Activity.dispatchTouchEvent()
  */ 
  public boolean dispatchTouchEvent(MotionEvent ev) {

    // 僅貼出核心代碼

    // ->>分析1
    if (getWindow().superDispatchTouchEvent(ev)) {

        return true;
        // 若getWindow().superDispatchTouchEvent(ev)的返回true
        // 則Activity.dispatchTouchEvent()就返回true,則方法結(jié)束塑陵。即 :該點擊事件停止往下傳遞 & 事件傳遞過程結(jié)束
        // 否則:繼續(xù)往下調(diào)用Activity.onTouchEvent

    }
    // ->>分析3
    return onTouchEvent(ev);
  }

/**
  * 分析1:getWindow().superDispatchTouchEvent(ev)
  * 說明:
  *     a. getWindow() = 獲取Window類的對象
  *     b. Window類是抽象類感憾,其唯一實現(xiàn)類 = PhoneWindow類
  *     c. Window類的superDispatchTouchEvent() = 1個抽象方法,由子類PhoneWindow類實現(xiàn)
  */
  @Override
  public boolean superDispatchTouchEvent(MotionEvent event) {

      return mDecor.superDispatchTouchEvent(event);
      // mDecor = 頂層View(DecorView)的實例對象
      // ->> 分析2
  }

/**
  * 分析2:mDecor.superDispatchTouchEvent(event)
  * 定義:屬于頂層View(DecorView)
  * 說明:
  *     a. DecorView類是PhoneWindow類的一個內(nèi)部類
  *     b. DecorView繼承自FrameLayout令花,是所有界面的父類
  *     c. FrameLayout是ViewGroup的子類阻桅,故DecorView的間接父類 = ViewGroup
  */
  public boolean superDispatchTouchEvent(MotionEvent event) {

      return super.dispatchTouchEvent(event);
      // 調(diào)用父類的方法 = ViewGroup的dispatchTouchEvent()
      // 即將事件傳遞到ViewGroup去處理,詳細請看后續(xù)章節(jié)分析的ViewGroup的事件分發(fā)機制

  }
  // 回到最初的分析2入口處

/**
  * 分析3:Activity.onTouchEvent()
  * 調(diào)用場景:當一個點擊事件未被Activity下任何一個View接收/處理時兼都,就會調(diào)用該方法
  */
  public boolean onTouchEvent(MotionEvent event) {

        // ->> 分析5
        if (mWindow.shouldCloseOnTouch(this, event)) {
            finish();
            return true;
        }
        
        return false;
        // 即 只有在點擊事件在Window邊界外才會返回true嫂沉,一般情況都返回false,分析完畢
    }

/**
  * 分析4:mWindow.shouldCloseOnTouch(this, event)
  * 作用:主要是對于處理邊界外點擊事件的判斷:是否是DOWN事件扮碧,event的坐標是否在邊界內(nèi)等
  */
  public boolean shouldCloseOnTouch(Context context, MotionEvent event) {

  if (mCloseOnTouchOutside && event.getAction() == MotionEvent.ACTION_DOWN
          && isOutOfBounds(context, event) && peekDecorView() != null) {

        // 返回true:說明事件在邊界外趟章,即 消費事件
        return true;
    }

    // 返回false:在邊界內(nèi),即未消費(默認)
    return false;
  } 

源碼總結(jié)

當一個點擊事件發(fā)生時慎王,從Activity的事件分發(fā)開始(Activity.dispatchTouchEvent())蚓土,流程總結(jié)如下:

核心方法總結(jié)

主要包括:dispatchTouchEvent()、onTouchEvent() 總結(jié)如下:


那么ViewGroupdispatchTouchEvent() 什么時候返回true / false? 請繼續(xù)往下看ViewGroup事件的分發(fā)機制柬祠。

2.2 ViewGroup事件分發(fā)機制

從上面Activity的事件分發(fā)機制可知北戏,在Activity.dispatchTouchEvent()實現(xiàn)了將事件從Activity->ViewGroup的傳遞负芋,ViewGroup的事件分發(fā)機制從dispatchTouchEvent()開始漫蛔。

源碼分析

/**
  * 源碼分析:ViewGroup.dispatchTouchEvent()
  */ 
  public boolean dispatchTouchEvent(MotionEvent ev) { 

  // 僅貼出關(guān)鍵代碼
  ... 

  if (disallowIntercept || !onInterceptTouchEvent(ev)) {  
  // 分析1:ViewGroup每次事件分發(fā)時,都需調(diào)用onInterceptTouchEvent()詢問是否攔截事件
    // 判斷值1-disallowIntercept:是否禁用事件攔截的功能(默認是false)旧蛾,可通過調(diào)用requestDisallowInterceptTouchEvent()修改
    // 判斷值2-!onInterceptTouchEvent(ev) :對onInterceptTouchEvent()返回值取反
        // a. 若在onInterceptTouchEvent()中返回false莽龟,即不攔截事件,從而進入到條件判斷的內(nèi)部
        // b. 若在onInterceptTouchEvent()中返回true锨天,即攔截事件毯盈,從而跳出了該條件判斷
        // c. 關(guān)于onInterceptTouchEvent() ->>分析1

  // 分析2
    // 1. 通過for循環(huán),遍歷當前ViewGroup下的所有子View
    for (int i = count - 1; i >= 0; i--) {  
        final View child = children[i];  
        if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE  
                || child.getAnimation() != null) {  
            child.getHitRect(frame);  

            // 2. 判斷當前遍歷的View是不是正在點擊的View病袄,從而找到當前被點擊的View
            if (frame.contains(scrolledXInt, scrolledYInt)) {  
                final float xc = scrolledXFloat - child.mLeft;  
                final float yc = scrolledYFloat - child.mTop;  
                ev.setLocation(xc, yc);  
                child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  

                // 3. 條件判斷的內(nèi)部調(diào)用了該View的dispatchTouchEvent()
                // 即 實現(xiàn)了點擊事件從ViewGroup到子View的傳遞(具體請看下面章節(jié)介紹的View事件分發(fā)機制)
                if (child.dispatchTouchEvent(ev))  { 

                // 調(diào)用子View的dispatchTouchEvent后是有返回值的
                // 若該控件可點擊搂赋,那么點擊時dispatchTouchEvent的返回值必定是true赘阀,因此會導(dǎo)致條件判斷成立
                // 于是給ViewGroup的dispatchTouchEvent()直接返回了true,即直接跳出
                // 即該子View把ViewGroup的點擊事件消費掉了

                mMotionTarget = child;  
                return true; 
                      }  
                  }  
              }  
          }  
      }  
    }  

  ...

  return super.dispatchTouchEvent(ev);
  // 若無任何View接收事件(如點擊空白處)/ViewGroup本身攔截了事件(復(fù)寫了onInterceptTouchEvent()返回true)
  // 會調(diào)用ViewGroup父類的dispatchTouchEvent()脑奠,即View.dispatchTouchEvent()
  // 因此會執(zhí)行ViewGroup的onTouch() -> onTouchEvent() -> performClick() -> onClick()基公,即自己處理該事件,事件不會往下傳遞
  // 具體請參考View事件分發(fā)機制中的View.dispatchTouchEvent()

  ... 

}

/**
  * 分析1:ViewGroup.onInterceptTouchEvent()
  * 作用:是否攔截事件
  * 說明:
  *     a. 返回false:不攔截(默認)
  *     b. 返回true:攔截宋欺,即事件停止往下傳遞(需手動復(fù)寫onInterceptTouchEvent()其返回true)
  */
  public boolean onInterceptTouchEvent(MotionEvent ev) {  
    
    // 默認不攔截
    return false;

  } 
  // 回到調(diào)用原處

源碼總結(jié)

Android事件分發(fā)傳遞到Acitivity后轰豆,總是先傳遞到ViewGroup、再傳遞到View齿诞。流程總結(jié)如下:(假設(shè)已經(jīng)經(jīng)過了Acitivity事件分發(fā)傳遞并傳遞到ViewGroup)

核心方法總結(jié)

主要包括:dispatchTouchEvent()酸休、onTouchEvent()onInterceptTouchEvent()總結(jié)如下:

實例分析

1. 布局說明
2. 測試代碼

布局文件:activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/my_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:focusableInTouchMode="true"
    android:orientation="vertical">

    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="按鈕1" />

    <Button
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="按鈕2" />

</LinearLayout>

核心代碼:MainActivity.java

public class MainActivity extends AppCompatActivity {

  Button button1,button2;
  ViewGroup myLayout;

  @Override
  public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);

      button1 = (Button)findViewById(R.id.button1);
      button2 = (Button)findViewById(R.id.button2);
      myLayout = (LinearLayout)findViewById(R.id.my_layout);

      // 1.為ViewGroup布局設(shè)置監(jiān)聽事件
      myLayout.setOnClickListener(new View.OnClickListener() {
          @Override
          public void onClick(View v) {
              Log.d("TAG", "點擊了ViewGroup");
          }
      });

      // 2. 為按鈕1設(shè)置監(jiān)聽事件
      button1.setOnClickListener(new View.OnClickListener() {
          @Override
          public void onClick(View v) {
              Log.d("TAG", "點擊了button1");
          }
      });

      // 3. 為按鈕2設(shè)置監(jiān)聽事件
      button2.setOnClickListener(new View.OnClickListener() {
          @Override
          public void onClick(View v) {
              Log.d("TAG", "點擊了button2");
          }
      });
   }
}
3. 測試結(jié)果
// 點擊按鈕1祷杈,輸出如下
點擊了button1

// 點擊按鈕2斑司,輸出如下
點擊了button2

// 點擊空白處,輸出如下
點擊了ViewGroup
4. 結(jié)果分析
  • 點擊Button時但汞,因為ViewGroup默認不攔截陡厘,所以事件會傳遞到子View Button,于是執(zhí)行Button.onClick()特占。
  • 此時ViewGroup. dispatchTouchEvent()會直接返回true糙置,所以ViewGroup自身不會處理該事件,于是ViewGroupLayoutdispatchTouchEvent()不會執(zhí)行是目,所以注冊的onTouch()不會執(zhí)行谤饭,即onTouchEvent()-> performClick()-> onClick()整個鏈路都不會執(zhí)行,所以最后不會執(zhí)行ViewGroup設(shè)置的onClick()里懊纳。
  • 點擊空白區(qū)域時揉抵,ViewGroup. dispatchTouchEvent()里遍歷所有子View希望找到被點擊子View時找不到,所以ViewGroup自身會處理該事件嗤疯,于是執(zhí)行onTouchEvent() -> performClick() -> onClick()冤今,最終執(zhí)行ViewGroupLayout的設(shè)置的onClick()

2.3 View事件分發(fā)機制

從上面ViewGroup事件分發(fā)機制知道茂缚,View事件分發(fā)機制從dispatchTouchEvent()開始

源碼分析

/**
  * 源碼分析:View.dispatchTouchEvent()
  */
  public boolean dispatchTouchEvent(MotionEvent event) {  

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

        return onTouchEvent(event);  
  }
  // 說明:只有以下3個條件都為真戏罢,dispatchTouchEvent()才返回true;否則執(zhí)行onTouchEvent()
  //   1. (mViewFlags & ENABLED_MASK) == ENABLED
  //   2. mOnTouchListener != null
  //   3. mOnTouchListener.onTouch(this, event)
  // 下面對這3個條件逐個分析

/**
  * 條件1:(mViewFlags & ENABLED_MASK) == ENABLED
  * 說明:
  *    1. 該條件是判斷當前點擊的控件是否enable
  *    2. 由于很多View默認enable脚囊,故該條件恒定為true(除非手動設(shè)置為false)
  */

/**
  * 條件2:mOnTouchListener != null
  * 說明:
  *   1. mOnTouchListener變量在View.setOnTouchListener()里賦值
  *   2. 即只要給控件注冊了Touch事件龟糕,mOnTouchListener就一定被賦值(即不為空)
  */
  public void setOnTouchListener(OnTouchListener l) { 

    mOnTouchListener = l;  

} 

/**
  * 條件3:mOnTouchListener.onTouch(this, event)
  * 說明:
  *   1. 即回調(diào)控件注冊Touch事件時的onTouch();
  *   2. 需手動復(fù)寫設(shè)置悔耘,具體如下(以按鈕Button為例)
  */
  button.setOnTouchListener(new OnTouchListener() {  
      @Override  
      public boolean onTouch(View v, MotionEvent event) {  
   
        return false;  
        // 若在onTouch()返回true讲岁,就會讓上述三個條件全部成立,從而使得View.dispatchTouchEvent()直接返回true,事件分發(fā)結(jié)束
        // 若在onTouch()返回false缓艳,就會使得上述三個條件不全部成立校摩,從而使得View.dispatchTouchEvent()中跳出If,執(zhí)行onTouchEvent(event)
        // onTouchEvent()源碼分析 -> 分析1
      }  
  });

/**
  * 分析1:onTouchEvent()
  */
  public boolean onTouchEvent(MotionEvent event) {  

    ... // 僅展示關(guān)鍵代碼

    // 若該控件可點擊阶淘,則進入switch判斷中
    if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {  

        // 根據(jù)當前事件類型進行判斷處理
        switch (event.getAction()) { 

            // a. 事件類型=抬起View(主要分析)
            case MotionEvent.ACTION_UP:  
                    performClick(); 
                    // ->>分析2
                    break;  

            // b. 事件類型=按下View
            case MotionEvent.ACTION_DOWN:  
                postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());  
                break;  

            // c. 事件類型=結(jié)束事件
            case MotionEvent.ACTION_CANCEL:  
                refreshDrawableState();  
                removeTapCallback();  
                break;

            // d. 事件類型=滑動View
            case MotionEvent.ACTION_MOVE:  
                final int x = (int) event.getX();  
                final int y = (int) event.getY();  

                int slop = mTouchSlop;  
                if ((x < 0 - slop) || (x >= getWidth() + slop) ||  
                        (y < 0 - slop) || (y >= getHeight() + slop)) {  
                    removeTapCallback();  
                    if ((mPrivateFlags & PRESSED) != 0) {  
                        removeLongPressCallback();  
                        mPrivateFlags &= ~PRESSED;  
                        refreshDrawableState();  
                    }  
                }  
                break;  
        }  

        // 若該控件可點擊秧耗,就一定返回true
        return true;  
    }  
  // 若該控件不可點擊,就一定返回false
  return false;  
}

/**
  * 分析2:performClick()
  */  
  public boolean performClick() {  

      if (mOnClickListener != null) {
          // 只要通過setOnClickListener()為控件View注冊1個點擊事件
          // 那么就會給mOnClickListener變量賦值(即不為空)
          // 則會往下回調(diào)onClick() & performClick()返回true
          playSoundEffect(SoundEffectConstants.CLICK);  
          mOnClickListener.onClick(this);  
          return true;  
      }  
      return false;  
  }  

源碼總結(jié)


這里需要特別注意的是舶治,onTouch()的執(zhí)行 先于onClick()

核心方法總結(jié)

主要包括: dispatchTouchEvent()分井、onTouchEvent()

實例分析

在本示例中,將分析2種情況:

  1. 注冊Touch事件監(jiān)聽 且 在onTouch()返回false
  2. 注冊Touch事件監(jiān)聽 且 在onTouch()返回true
分析1:注冊Touch事件監(jiān)聽 且 在onTouch()返回false
代碼示例
// 1. 注冊Touch事件監(jiān)聽setOnTouchListener 且 在onTouch()返回false
button.setOnTouchListener(new View.OnTouchListener() {

      @Override
      public boolean onTouch(View v, MotionEvent event) {
          System.out.println("執(zhí)行了onTouch(), 動作是:" + event.getAction());
          return false;
      }
});

// 2. 注冊點擊事件OnClickListener()
button.setOnClickListener(new View.OnClickListener() {

      @Override
      public void onClick(View v) {
          System.out.println("執(zhí)行了onClick()");
      }
});


測試結(jié)果
執(zhí)行了onTouch(), 動作是:0
執(zhí)行了onTouch(), 動作是:1
執(zhí)行了onClick()
測試結(jié)果說明
  • 點擊按鈕會產(chǎn)生兩個類型的事件-按下View與抬起View霉猛,所以會回調(diào)兩次onTouch()尺锚;
  • 因為onTouch()返回了false,所以事件無被消費惜浅,會繼續(xù)往下傳遞瘫辩,即調(diào)用View.onTouchEvent()
  • 調(diào)用View.onTouchEvent()時坛悉,對于抬起View事件伐厌,在調(diào)用performClick()時,因為設(shè)置了點擊事件裸影,所以會回調(diào)onClick()挣轨。
分析2:注冊Touch事件監(jiān)聽 且 在onTouch()返回true
代碼示例
// 1. 注冊Touch事件監(jiān)聽setOnTouchListener 且 在onTouch()返回false
button.setOnTouchListener(new View.OnTouchListener() {

        @Override
        public boolean onTouch(View v, MotionEvent event) {
            System.out.println("執(zhí)行了onTouch(), 動作是:" + event.getAction());
            return true;
        }
    });

// 2. 注冊點擊事件OnClickListener()
button.setOnClickListener(new View.OnClickListener() {

        @Override
        public void onClick(View v) {
            System.out.println("執(zhí)行了onClick()");
        }
        
  });
測試結(jié)果
執(zhí)行了onTouch(), 動作是:0
執(zhí)行了onTouch(), 動作是:1
測試結(jié)果說明
  • 點擊按鈕會產(chǎn)生兩個類型的事件-按下View與抬起View,所以會回調(diào)兩次onTouch()轩猩;
  • 因為onTouch()返回true卷扮,所以事件被消費,不會繼續(xù)往下傳遞均践,View.dispatchTouchEvent()直接返回true晤锹;
  • 所以最終不會調(diào)用View.onTouchEvent(),也不會調(diào)用onClick()彤委。

三. 事件分發(fā)機制流程總結(jié)


這個三個方法均有一個 boolean(布爾) 類型的返回值鞭铆,通過返回 true 和 false 來控制事件傳遞的流程。
PS: 從上表可以看到 ActivityView都是沒有事件攔截的焦影,這是因為:

  • Activity 作為原始的事件分發(fā)者车遂,如果 Activity 攔截了事件會導(dǎo)致整個屏幕都無法響應(yīng)事件,這肯定不是我們想要的效果偷办。
  • View最為事件傳遞的最末端艰额,要么消費掉事件澄港,要么不處理進行回傳椒涯,根本沒必要進行事件攔截。

View相關(guān)

Question: 為什么 View 會有 dispatchTouchEvent ?

A:View 可以注冊很多事件監(jiān)聽器回梧,例如:單擊事件(onClick)废岂、長按事件(onLongClick)祖搓、觸摸事件(onTouch),并且View自身也有onTouchEvent方法湖苞,那么問題來了拯欧,這么多與事件相關(guān)的方法應(yīng)該由誰管理?毋庸置疑就是 dispatchTouchEvent财骨,所以 View 也會有事件分發(fā)镐作。

Question: 與 View 事件相關(guān)的各個方法調(diào)用順序是怎樣的?

A:如果不去看源碼隆箩,想一下讓自己設(shè)計會怎樣该贾?

  • 單擊事件(onClickListener) 需要兩個事件(ACTION_DOWNACTION_UP)才能觸發(fā),如果先分配給onClick判斷捌臊,等它判斷完杨蛋,用戶手指已經(jīng)離開屏幕,黃花菜都涼了理澎,定然造成 View 無法響應(yīng)其他事件逞力,應(yīng)該最后調(diào)用。(最后)
  • 長按事件(onLongClickListener) 同理糠爬,也是需要長時間等待才能出結(jié)果寇荧,肯定不能排到前面,但因為不需要ACTION_UP执隧,應(yīng)該排在onClick前面砚亭。(onLongClickListener> onClickListener)
  • 觸摸事件(onTouchListener) , 如果用戶注冊了觸摸事件殴玛,說明用戶要自己處理觸摸事件捅膘,這個應(yīng)該排在最前面。(最前)滚粟、
  • View自身處理(onTouchEvent) 提供了一種默認的處理方式寻仗,如果用戶已經(jīng)處理好了,也就不需要了凡壤,所以應(yīng)該排在onTouchListener后面署尤。(onTouchListener > onTouchEvent)

所以事件的調(diào)度順序應(yīng)該是 onTouchListener> onTouchEvent > onLongClickListener> onClickListener

ViewGroup相關(guān)

ViewGroup(通常是各種Layout) 的事件分發(fā)相對來說就要麻煩一些亚侠,因為 ViewGroup 不僅要考慮自身曹体,還要考慮各種ChildView,一旦處理不好就容易引起各種事件沖突硝烂,正所謂養(yǎng)兒方知父母難啊箕别。
VIewGroup的事件分發(fā)流程又是如何的呢?

我們了解到事件是通過ViewGroup一層一層傳遞的,最終傳遞給View串稀,ViewGroup要比它的 ChildView 先拿到事件除抛,并且有權(quán)決定是否告訴要告訴ChildView。在默認的情況下 ViewGroup事件分發(fā)流程是這樣的母截。

  1. 判斷自身是否需要(詢問 onInterceptTouchEvent 是否攔截)到忽,如果需要,調(diào)用自己的 onTouchEvent清寇。
  2. 自身不需要或者不確定喘漏,則詢問ChildView,一般來說是調(diào)用手指觸摸位置的 ChildView华烟。
  3. 如果子 ChildView不需要則調(diào)用自身的onTouchEvent陷遮。

用偽代碼應(yīng)該是這樣子的:

// 點擊事件產(chǎn)生后
// 步驟1:調(diào)用dispatchTouchEvent()
public boolean dispatchTouchEvent(MotionEvent ev) {

    boolean consume = false; //代表 是否會消費事件

    // 步驟2:判斷是否攔截事件
    if (onInterceptTouchEvent(ev)) {
      // a. 若攔截,則將該事件交給當前View進行處理
      // 即調(diào)用onTouchEvent()去處理點擊事件
      consume = onTouchEvent (ev) ;

    } else {

      // b. 若不攔截垦江,則將該事件傳遞到下層
      // 即 下層元素的dispatchTouchEvent()就會被調(diào)用帽馋,重復(fù)上述過程
      // 直到點擊事件被最終處理為止
      consume = child.dispatchTouchEvent (ev) ;
    }

    // 步驟3:最終返回通知 該事件是否被消費(接收 & 處理)
    return consume;
  }
}

安卓為了保證所有的事件都是被一個 View 消費的,對第一次的事件( ACTION_DOWN )進行了特殊判斷比吭,View 只有消費了 ACTION_DOWN 事件绽族,才能接收到后續(xù)的事件(可點擊控件會默認消費所有事件),并且會將后續(xù)所有事件傳遞過來衩藤,不會再傳遞給其他 View吧慢,除非上層 View 進行了攔截。如果上層 View 攔截了當前正在處理的事件赏表,會收到一個 ACTION_CANCEL检诗,表示當前事件已經(jīng)結(jié)束,后續(xù)事件不會再傳遞過來瓢剿。

核心要點

  1. 事件分發(fā)原理: 責(zé)任鏈模式逢慌,事件層層傳遞,直到被消費间狂。
  2. View 的 dispatchTouchEvent主要用于調(diào)度自身的監(jiān)聽器和 onTouchEvent攻泼。
  3. View的事件的調(diào)度順序是 onTouchListener > onTouchEvent > onLongClickListener > onClickListener
  4. 不論View自身是否注冊點擊事件鉴象,只要 View 是可點擊的就會消費事件忙菠。
  5. 事件是否被消費由返回值決定,true 表示消費纺弊,false 表示不消費牛欢,與是否使用了事件無關(guān)。
  6. ViewGroup 中可能有多個 ChildView 時淆游,將事件分配給包含點擊位置的 ChildView傍睹。
  7. ViewGroup 和 ChildView 同時注冊了事件監(jiān)聽器(onClick等)隔盛,由 ChildView 消費。
  8. 一次觸摸流程中產(chǎn)生事件應(yīng)被同一 View 消費焰望,全部接收或者全部拒絕骚亿。
  9. 只要接受 ACTION_DOWN 就意味著接受所有的事件已亥,拒絕 ACTION_DOWN 則不會收到后續(xù)內(nèi)容熊赖。
  10. 如果當前正在處理的事件被上層 View 攔截,會收到一個 ACTION_CANCEL虑椎,后續(xù)事件不會再傳遞過來震鹉。

參考與感謝

Android事件分發(fā)機制詳解:史上最全面、最易懂
安卓自定義View進階-事件分發(fā)機制詳解

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末捆姜,一起剝皮案震驚了整個濱河市传趾,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌泥技,老刑警劉巖浆兰,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異珊豹,居然都是意外死亡簸呈,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門店茶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蜕便,“玉大人,你說我怎么就攤上這事贩幻〗蜗伲” “怎么了?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵丛楚,是天一觀的道長族壳。 經(jīng)常有香客問我,道長趣些,這世上最難降的妖魔是什么决侈? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮喧务,結(jié)果婚禮上赖歌,老公的妹妹穿的比我還像新娘。我一直安慰自己功茴,他們只是感情好庐冯,可當我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著坎穿,像睡著了一般展父。 火紅的嫁衣襯著肌膚如雪返劲。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天栖茉,我揣著相機與錄音篮绿,去河邊找鬼。 笑死吕漂,一個胖子當著我的面吹牛亲配,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播惶凝,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼吼虎,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了苍鲜?” 一聲冷哼從身側(cè)響起思灰,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎混滔,沒想到半個月后洒疚,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體绕德,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡浊伙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了调榄。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片愿伴。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡肺魁,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出隔节,到底是詐尸還是另有隱情鹅经,我是刑警寧澤,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布怎诫,位于F島的核電站瘾晃,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏幻妓。R本人自食惡果不足惜蹦误,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望肉津。 院中可真熱鬧强胰,春花似錦、人聲如沸妹沙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽距糖。三九已至玄窝,卻和暖如春牵寺,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背恩脂。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工帽氓, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人俩块。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓黎休,卻偏偏與公主長得像,于是被迫代替她去往敵國和親典阵。 傳聞我的和親對象是個殘疾皇子奋渔,可洞房花燭夜當晚...
    茶點故事閱讀 44,592評論 2 353

推薦閱讀更多精彩內(nèi)容