Android事件分發(fā)機制

一. Android分發(fā)機制概述:
Android如此受歡迎信卡,就在于其優(yōu)秀的交互性隔缀,這其中,Android優(yōu)秀的事件分發(fā)機制功不可沒傍菇。那么猾瘸,作為一個優(yōu)秀的程序員,要想做一個具有良好交互性的應用丢习,必須透徹理解Android的事件分發(fā)機制。
要想充分理解android的分發(fā)機制绰更,需要先對以下幾個知識點有所了解:
① View和ViewGroup什么显拜?
② 事件
③ View 事件的分發(fā)機制
④ ViewGroup事件的分發(fā)機制
下面,就讓我們沿著大致方針剂娄,開始事件分發(fā)的探究之旅吧……
二诗轻、View和ViewGroup:
Android的UI界面都是由View和ViewGroup及其派生類組合而成的劝术。其中秦忿,View是所有UI組件的基類寨躁,而ViewGroup是容納這些組件的容器,其本身也是從View派生出來的,也就是說ViewGroup的父類就是View翎冲。
通常來說衔沼,Button、ImageView、TextView等控件都是繼承父類View來實現(xiàn)的燎悍。RelativeLayout畴椰、LinearLayout、FrameLayout等布局都是繼承父類ViewGroup來實現(xiàn)的鸽粉。
事件:
當手指觸摸到View或ViewGroup派生的控件后斜脂,將會觸發(fā)一系列的觸發(fā)響應事件,如:
onTouchEvent触机、onClick秽褒、onLongClick等。每個View都有自己處理事件的回調方法威兜,開發(fā)人員只需要重寫這些回調方法,就可以實現(xiàn)需要的響應事件庐椒。
而事件通常重要的有如下三種:
MotionEvent.ACTION_DOWN 按下View椒舵,是所有事件的開始
MotionEvent.ACTION_MOVE 滑動事件
MotionEvent.ACTION_UP 與down對應,表示抬起
事件的響應原理:
在android開發(fā)設計模式中约谈,最廣泛應用的就是監(jiān)聽笔宿、回調,進而形成了事件響應的過程棱诱。
以Button的OnClick為例泼橘,因為Button也是一個View,所以它也擁有View父類的方法,在View中源碼如下:

/**定義接口成員變量*/
protected OnClickListener mOnClickListener;
    /**
     * Interface definition for a callback to be invoked when a view is clicked.
     */
    public interface OnClickListener {
        /**
         * Called when a view has been clicked.
         *
         * @param v The view that was clicked.
         */
        void onClick(View v);
    }
/**
     * Register a callback to be invoked when this view is clicked. If this view is not
     * clickable, it becomes clickable.
     *
     * @param l The callback that will run
     *
     * @see #setClickable(boolean)
     */
    public void setOnClickListener(OnClickListener l) {
        if (!isClickable()) {
            setClickable(true);
        }
        mOnClickListener = l;
}
 
/**
     * Call this view's OnClickListener, if it is defined.
     *
     * @return True there was an assigned OnClickListener that was called, false
     *         otherwise is returned.
     */
    public boolean performClick() {
        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
 
        if (mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            mOnClickListener.onClick(this);
            return true;
        }
 
        return false;
}
/**觸摸了屏幕后迈勋,實現(xiàn)并調用的方法*/
public boolean onTouchEvent(MotionEvent event) {
           …..
                   if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }
                                if (!post(mPerformClick)) {
                                    performClick();
                                }
           …..
}

以上是View源碼中關鍵代碼行炬灭,以Button為例,假設需要在一個布局上添加一個按鈕靡菇,并實現(xiàn)它的OnClick事件重归,需要如下步驟:

1、 OnClickListener類是一個當控件被點擊后進行回調的一個接口厦凤,它完成被點擊后的回調通知鼻吮。
2、 創(chuàng)建一個按鈕Button较鼓,并設置監(jiān)聽事件椎木,對這個Button進行setOnClickListener操作
3违柏、 當手指觸摸到Button按鈕,通過一系列方法(之后將會詳細講解香椎,這里暫時忽略)漱竖,觸發(fā)并執(zhí)行到onTouchEvent方法并執(zhí)行mPerformClick方法,在mPerformClick方法中士鸥,首先會判斷注冊的mOnClickListener是否為空闲孤,若不為空,它就會回調之前注冊的onClick方法烤礁,進而執(zhí)行用戶自定義代碼讼积。
事件響應機制,簡單來說上面的例子就已經(jīng)基本上詮釋了

注冊一個監(jiān)聽對象
實現(xiàn)監(jiān)聽對象的監(jiān)聽事件
當某一觸發(fā)事件到來脚仔,在觸發(fā)事件中通過注冊過的監(jiān)聽對象勤众,回調注冊對象的響應事件,來完成用戶自定義實現(xiàn)鲤脏。
但凡明白了這一個簡單的事件響應的過程们颜,就離事件驅動開發(fā)整個過程就不遠了,大道至簡猎醇,請完全理解了這個例子窥突,再繼續(xù)之后的學習,事半功倍硫嘶。
三阻问、View事件的分發(fā)機制:
通過上面的例子,我們初步的接觸了View的事件分發(fā)機制沦疾,再進一步了解称近。首先,我們要熟悉dispatchTouchEvent和onTouchEvent兩個函數(shù)哮塞,這兩個函數(shù)都是View的函數(shù)刨秆,要理解View事件的分發(fā)機制,只要清楚這兩個函數(shù)就基本上清楚了忆畅。
在這里先提醒一句衡未,這里的“分發(fā)”是指一個觸摸或點擊的事件發(fā)生,分發(fā)給當前觸摸控件所監(jiān)聽的事件(如OnClick家凯、onTouch等)眠屎,進而來決定是控件的哪個函數(shù)來響應此次事件。
dispatchTouchEvent:
此函數(shù)負責事件的分發(fā)肆饶,你只需要記住當觸摸一個View控件改衩,首先會調用這個函數(shù)就行,在這個函數(shù)體里決定將事件分發(fā)給誰來處理驯镊。
onTouchEvent:
此函數(shù)負責執(zhí)行事件的處理葫督,負責處理事件竭鞍,主要處理MotionEvent.ACTION_DOWN、
MotionEvent.ACTION_MOVE 橄镜、
MotionEvent.ACTION_UP這三個事件偎快。
public boolean onTouchEvent (MotionEvent event)
參數(shù)event為手機屏幕觸摸事件封裝類的對象,其中封裝了該事件的所有信息洽胶,例如觸摸的位置晒夹、觸摸的類型以及觸摸的時間等。該對象會在用戶觸摸手機屏幕時被創(chuàng)建姊氓。
那么它是如何執(zhí)行這個流程的呢丐怯?我們還以布局上的按鈕為例,看看它是如何實現(xiàn)的翔横。(看圖①)


image.png

我們知道读跷,View做為所有控件的父類,它本身定義了很多接口來監(jiān)聽觸摸在View上的事件禾唁,如OnClickListener(點擊)效览、OnLongClickListener(長按)、OnTouchListener(觸摸監(jiān)聽)等荡短,那么當手指觸摸到View時候丐枉,該響應“點擊”還是”觸摸”呢,就是根據(jù)dispatchTouchEvent和onTouchEvent這兩個函數(shù)組合實現(xiàn)的,我們之下的討論掘托,僅對常用的“點擊OnClick”和“觸摸onTouch”來討論矛洞,順藤摸瓜,找出主線烫映,進而搞清楚View的事件分發(fā)機制。
對于上面的按鈕噩峦,點擊它一下锭沟,我們期望2種結果,第一種:它響應一個點擊事件识补。第二種:不響應點擊事件族淮。
第一種源碼:

    public class MainActivity extends Activity implements OnClickListener ,OnTouchListener{
      private Button btnButton;
      @Override
      protected void onCreate(Bundle savedInstanceState) {
           super.onCreate(savedInstanceState);
           setContentView(R.layout.activity_main);
           btnButton=(Button) findViewById(R.id.btn);
           btnButton.setOnClickListener(this);
           btnButton.setOnTouchListener(this);
           }
     
      @Override
      public void onClick(View v) {
           // TODO Auto-generated method stub
           switch (v.getId()) {
           case R.id.btn:
                 Log.e("View", "onClick===========>");
                 break;
           default:
                 break;
           }
      }
     
      @Override
      public boolean onTouch(View v, MotionEvent event) {
           // TODO Auto-generated method stub
           Log.e("View", "onTouch..................................");
           return false;
      }
    }
image.png

第二種源碼:

    public class MainActivity extends Activity implements OnClickListener ,OnTouchListener{
      private Button btnButton;
      @Override
      protected void onCreate(Bundle savedInstanceState) {
           super.onCreate(savedInstanceState);
           setContentView(R.layout.activity_main);
           btnButton=(Button) findViewById(R.id.btn);
           btnButton.setOnClickListener(this);
           btnButton.setOnTouchListener(this);
           }
     
      @Override
      public void onClick(View v) {
           // TODO Auto-generated method stub
           switch (v.getId()) {
           case R.id.btn:
                 Log.e("View", "onClick===========>");
                 break;
           default:
                 break;
           }
      }
     
      @Override
      public boolean onTouch(View v, MotionEvent event) {
           // TODO Auto-generated method stub
           Log.e("View", "onTouch..................................");
           return true;
      }
    }
image.png

結果分析:
上面兩處代碼,第一種執(zhí)行了OnClick函數(shù)和OnTouch函數(shù)凭涂,第二種執(zhí)行了OnTouch函數(shù)祝辣,并沒有執(zhí)行OnClick函數(shù),而且對兩處代碼進行比較切油,發(fā)現(xiàn)只有在onTouch處返回值true和false不同蝙斜。當onTouch返回false,onClick被執(zhí)行了澎胡,返回true孕荠,onClick未被執(zhí)行娩鹉。
為什么會這樣呢?我們只有深入源碼才能分析出來稚伍。
前面提到弯予,觸摸一個View就會執(zhí)行dispatchTouchEvent方法去“分發(fā)”事件, 既然觸摸的是按鈕Button个曙,那么我們就查看Button的源碼锈嫩,尋找dispatchTouchEvent方法,Button源碼中沒有dispatchTouchEvent方法垦搬,但知道Button繼承自TextView呼寸,尋找TextView,發(fā)現(xiàn)它也沒有dispatchTouchEvent方法悼沿,繼續(xù)查找TextView的父類View等舔,發(fā)現(xiàn)View有dispatchTouchEvent方法,那我們就分析dispatchTouchEvent方法糟趾。
主要代碼如下:

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

分析:
先來看dispatchTouchEvent函數(shù)返回值慌植,如果返回true,表明事件被處理了义郑,反之蝶柿,表明事件未被處理。
if (onFilterTouchEventForSecurity(event))這個是事件安全過濾非驮,與主題無關交汤,繼續(xù)看。

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

這個判定很重要劫笙,mOnTouchListener != null芙扎,判斷該控件是否注冊了OnTouchListener對象的監(jiān)聽,(mViewFlags & ENABLED_MASK) == ENABLED填大,判斷當前的控件是否能被點擊(比如Button默認可以點擊戒洼,ImageView默認不許點擊,看到這里就了然了)允华,mOnTouchListener.onTouch(this, event)這個是關鍵圈浇,這個調用,就是回調你注冊在這個View上的mOnTouchListener對象的onTouch方法靴寂,如果你在onTouch方法里返回false磷蜀,那么這個判斷語句就跳出,去執(zhí)行下面的程序百炬,否則褐隆,當前2個都返回了true,自定義onTouch方法也返回true剖踊,條件成立妓灌,就直接返回了轨蛤,不再執(zhí)行下面的程序。接下來虫埂,if (onTouchEvent(event)) 這個判斷很重要祥山,能否回調OnClickListener接口的onClick函數(shù),關鍵在于此掉伏,可以肯定的是缝呕,如果上面
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener.onTouch(this, event))返回true,那么就不會執(zhí)行并回調OnClickListener接口的onClick函數(shù)斧散。
接下來供常,我們看onTouchEvent這個函數(shù),看它是如何響應點擊事件的鸡捐。
主要代碼如下:

  
  public boolean onTouchEvent(MotionEvent event) {
          final int viewFlags = mViewFlags;
   
          if ((viewFlags & ENABLED_MASK) == DISABLED) {
              if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PRESSED) != 0) {
                  mPrivateFlags &= ~PRESSED;
                  refreshDrawableState();
              }
              // A disabled view that is clickable still consumes the touch
              // events, it just doesn't respond to them.
              return (((viewFlags & CLICKABLE) == CLICKABLE ||
                      (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
          }
   
          if (mTouchDelegate != null) {
              if (mTouchDelegate.onTouchEvent(event)) {
                  return true;
              }
          }
   
          if (((viewFlags & CLICKABLE) == CLICKABLE ||
                  (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
              switch (event.getAction()) {
                  case MotionEvent.ACTION_UP:
                      boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
                      if ((mPrivateFlags & 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.
                              mPrivateFlags |= PRESSED;
                              refreshDrawableState();
                         }
   
                          if (!mHasPerformedLongPress) {
                              // 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();
                                  }
                              }
                          }
   
                          if (mUnsetPressedState == null) {
                              mUnsetPressedState = new UnsetPressedState();
                          }
   
                          if (prepressed) {
                              postDelayed(mUnsetPressedState,
                                      ViewConfiguration.getPressedStateDuration());
                          } else if (!post(mUnsetPressedState)) {
                              // If the post failed, unpress right now
                              mUnsetPressedState.run();
                          }
                          removeTapCallback();
                      }
                      break;
   
                  case MotionEvent.ACTION_DOWN:
                      mHasPerformedLongPress = false;
   
                      if (performButtonActionOnTouchDown(event)) {
                          break;
                      }
   
                      // Walk up the hierarchy to determine if we're inside a scrolling container.
                      boolean isInScrollingContainer = isInScrollingContainer();
   
                      // For views inside a scrolling container, delay the pressed feedback for
                      // a short period in case this is a scroll.
                      if (isInScrollingContainer) {
                          mPrivateFlags |= PREPRESSED;
                          if (mPendingCheckForTap == null) {
                              mPendingCheckForTap = new CheckForTap();
                          }
                          postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                      } else {
                          // Not inside a scrolling container, so show the feedback right away
                          mPrivateFlags |= PRESSED;
                          refreshDrawableState();
                          checkForLongClick(0);
                      }
                      break;
   
                  case MotionEvent.ACTION_CANCEL:
                      mPrivateFlags &= ~PRESSED;
                      refreshDrawableState();
                      removeTapCallback();
                      break;
   
                  case MotionEvent.ACTION_MOVE:
                      final int x = (int) event.getX();
                      final int y = (int) event.getY();
   
                      // Be lenient about moving outside of buttons
                      if (!pointInView(x, y, mTouchSlop)) {
                          // Outside button
                          removeTapCallback();
                          if ((mPrivateFlags & PRESSED) != 0) {
                              // Remove any future long press/tap checks
                              removeLongPressCallback();
   
                              // Need to switch from pressed to not pressed
                              mPrivateFlags &= ~PRESSED;
                              refreshDrawableState();
                          }
                      }
                      break;
              }
              return true;
          }
  
          return false;
  }
      public boolean performClick() {
          sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
   
          if (mOnClickListener != null) {
              playSoundEffect(SoundEffectConstants.CLICK);
              mOnClickListener.onClick(this);
              return true;
          }
   
          return false;
      }

代碼量太大了栈暇,不過不要緊,我們通過主要代碼分析一下箍镜。

     public boolean onTouchEvent(MotionEvent event) {
         
            //控件不能被點擊
            if ((viewFlags & ENABLED_MASK) == DISABLED) {
                 …
            }
    //委托代理別的View去實現(xiàn)
            if (mTouchDelegate != null) {
                if (mTouchDelegate.onTouchEvent(event)) {
                    return true;
                }
            }
            //控件能夠點擊或者長按
            if (((viewFlags & CLICKABLE) == CLICKABLE ||
                    (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
                switch (event.getAction()) {
                //抬起事件
                    case MotionEvent.ACTION_UP:
                              …...
                                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)) {
                            //這里就是去執(zhí)行回調注冊的onClick函數(shù)源祈,實現(xiàn)點擊
                                        performClick();
                                    }
                                }
                                ……
                        break;
               //按下事件
                    case MotionEvent.ACTION_DOWN:
                         
                        ……
                        break;
     
                   ……
               //移動事件
                    case MotionEvent.ACTION_MOVE:
                         ……
                        break;
                }
           
                return true;
            }
     
            return false;
    }

從上面主要代碼可以看出onTouchEvent傳參MotionEvent類型,它封裝了觸摸的活動事件色迂,其中就有MotionEvent.ACTION_DOWN香缺、MotionEvent.ACTION_MOVE、MotionEvent.ACTION_UP三個事件歇僧。我們在來看看onTouchEvent的返回值图张,因為onTouchEvent是在dispatchTouchEvent事件分發(fā)處理中調用的,

        public boolean dispatchTouchEvent(MotionEvent event) {
                 ……
                    if (onTouchEvent(event)) {
                        return true;
                    }
        return fasle;
                }
如果onTouchEvent返回true诈悍,dispatchTouchEvent就返回true祸轮,表明事件被處理了,反之侥钳,事件未被處理适袜。
 
程序的關鍵在  if (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE))的判斷里,我們發(fā)現(xiàn)無論switch的分支在什么地方跳出慕趴,返回都是true。這就表明鄙陡,無論是三個事件中的哪一個冕房,都會返回true。
參照下圖趁矾,結合上述耙册,不難理解View的分發(fā)機制了。

(圖④)


image.png

四毫捣、ViewGroup事件分發(fā)機制:
ViewGroup事件分發(fā)機制較View的稍微復雜一些详拙,不過對View的機制只要精確的理解后帝际,仔細看過這一節(jié),睡幾覺起來饶辙,估計也就悟出來了蹲诀,學習就是這么奇怪,當下理解不了或模糊的地方弃揽,只要腦子有印象脯爪,忽然一夜好像就懂了。
先來看下面的一個簡單布局矿微,我們將通過例子痕慢,了解ViewGroup+View的android事件處理機制。


image.png
上圖由:黑色為線性布局LinearLayout涌矢,紫色為相對布局RelativeLayout掖举,按鈕Button三部分組成。RelativeLayout為LinearLayout的子布局娜庇,Button為RelativeLayout的子布局塔次。以下RelativeLayout簡稱(R),LinearLayout簡稱(L)思灌,Button簡稱(B)俺叭。
經(jīng)過前面講解,我們首先知道這樣兩件事情泰偿。
1熄守、(R)和(L)的父類是ViewGroup,(B)的父類是View耗跛。
2裕照、dispatchTouchEvent這個函數(shù)很重要,不論是ViewGroup還是View调塌,都由它來處理事件的消費和傳遞晋南。
下面,我們通過橫向和縱向兩個維度羔砾,通過源碼和圖解的方式负间,充分理解事件的傳遞機制。
先來看整體的事件傳遞過程:
image.png

當手指點擊按鈕B時姜凄,事件傳遞的順序是從底向上傳遞的政溃,也就是按照L->R->B的順序由下往上逐層傳遞,響應正好相反态秧,是自上而下董虱。
L首先接收到點擊事件,L的父類是ViewGroup類,并將事件傳遞給dispatchTouchEvent方法愤诱,dispatchTouchEvent函數(shù)中判斷該控件L是否重載了onInterceptTouchEvent方法進行事件攔截云头,onInterceptTouchEvent默認返回false不攔截,那么dispatchTouchEvent方法將事件傳遞給R去處理(進入第2流程處理)淫半,如果返回true表示當前L控件攔截了事件向其它控件的傳遞溃槐,交給它自己父類View的dispatchTouchEvent去處理,在父方法的dispatchTouchEvent中撮慨,將會按照前面講的View的事件處理機制去判斷竿痰,比如判斷L是否重載了onTouch方法坐桩,是否可點擊瓦侮,是否做了監(jiān)聽等事件。
R也是ViewGroup的子類喘落,因此與第1流程基本相似规伐,如果onInterceptTouchEvent返回了false蟹倾,表示事件將不攔截繼續(xù)傳遞給B。
B是View的子類猖闪,它沒有onInterceptTouchEvent方法鲜棠,直接交給自己父類View的dispatchTouchEvent去處理,流程同不再敷述培慌。
總結:
onInterceptTouchEvent只有ViewGroup才有豁陆,當一個控件是繼承自ViewGroup而來的,那么它就可能會有子控件吵护,因此盒音,才有可能傳遞給子控件,而繼承自View的控件馅而,不會有子控件祥诽,也就沒有onInterceptTouchEvent函數(shù)了。
通過dispatchTouchEvent分發(fā)的控件返回值True和false瓮恭,表示當前控件是否消費了傳遞過來的事件雄坪,如果消費了,返回True屯蹦,反之false维哈。消費了,就不再繼續(xù)傳遞了登澜,沒有消費阔挠,如果有子控件將繼續(xù)傳遞。
啰嗦點帖渠,如果想再深層次了解一下谒亦,再次從源碼ViewGroup來分析一個L控件的事件傳遞過程竭宰,請看下圖:


image.png
結合上面的圖例空郊,下面列出ViewGroup源碼來分析一下份招,我們只需要分析ViewGroup的dispatchTouchEvent、onInterceptTouchEvent狞甚、dispatchTransformedTouchEvent三個方法即可锁摔。
    public boolean dispatchTouchEvent(MotionEvent ev) {
            if (mInputEventConsistencyVerifier != null) {
                mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
            }
     
            boolean handled = false;
            if (onFilterTouchEventForSecurity(ev)) {
                final int action = ev.getAction();
                final int actionMasked = action & MotionEvent.ACTION_MASK;
     
                // Handle an initial down.
                if (actionMasked == MotionEvent.ACTION_DOWN) {
                    // Throw away all previous state when starting a new touch gesture.
                    // The framework may have dropped the up or cancel event for the previous gesture
                    // due to an app switch, ANR, or some other state change.
                    cancelAndClearTouchTargets(ev);
                    resetTouchState();
                }
     
                // Check for interception.
                final boolean intercepted;
                if (actionMasked == MotionEvent.ACTION_DOWN
                        || mFirstTouchTarget != null) {
                    final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                    if (!disallowIntercept) {
                        intercepted = onInterceptTouchEvent(ev);
                        ev.setAction(action); // restore action in case it was changed
                    } else {
                        intercepted = false;
                    }
                } else {
                    // There are no touch targets and this action is not an initial down
                    // so this view group continues to intercept touches.
                    intercepted = true;
                }
     
                // Check for cancelation.
                final boolean canceled = resetCancelNextUpFlag(this)
                        || actionMasked == MotionEvent.ACTION_CANCEL;
     
                // Update list of touch targets for pointer down, if needed.
                final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
                TouchTarget newTouchTarget = null;
                boolean alreadyDispatchedToNewTouchTarget = false;
                if (!canceled && !intercepted) {
                    if (actionMasked == MotionEvent.ACTION_DOWN
                            || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                            || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                        final int actionIndex = ev.getActionIndex(); // always 0 for down
                        final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
                                : TouchTarget.ALL_POINTER_IDS;
     
                        // Clean up earlier touch targets for this pointer id in case they
                        // have become out of sync.
                        removePointersFromTouchTargets(idBitsToAssign);
     
                        final int childrenCount = mChildrenCount;
                        if (childrenCount != 0) {
                            // Find a child that can receive the event.
                            // Scan children from front to back.
                            final View[] children = mChildren;
                            final float x = ev.getX(actionIndex);
                            final float y = ev.getY(actionIndex);
     
                            for (int i = childrenCount - 1; i >= 0; i--) {
                                final View child = children[i];
                                if (!canViewReceivePointerEvents(child)
                                        || !isTransformedTouchPointInView(x, y, child, null)) {
                                    continue;
                                }
     
                                newTouchTarget = getTouchTarget(child);
                                if (newTouchTarget != null) {
                                    // Child is already receiving touch within its bounds.
                                    // Give it the new pointer in addition to the ones it is handling.
                                    newTouchTarget.pointerIdBits |= idBitsToAssign;
                                    break;
                                }
     
                                resetCancelNextUpFlag(child);
                                if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                    // Child wants to receive touch within its bounds.
                                    mLastTouchDownTime = ev.getDownTime();
                                    mLastTouchDownIndex = i;
                                    mLastTouchDownX = ev.getX();
                                    mLastTouchDownY = ev.getY();
                                    newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                    alreadyDispatchedToNewTouchTarget = true;
                                    break;
                                }
                            }
                        }
     
                        if (newTouchTarget == null && mFirstTouchTarget != null) {
                            // Did not find a child to receive the event.
                            // Assign the pointer to the least recently added target.
                            newTouchTarget = mFirstTouchTarget;
                            while (newTouchTarget.next != null) {
                                newTouchTarget = newTouchTarget.next;
                            }
                            newTouchTarget.pointerIdBits |= idBitsToAssign;
                        }
                    }
                }
     
                // Dispatch to touch targets.
                if (mFirstTouchTarget == null) {
                    // No touch targets so treat this as an ordinary view.
                    handled = dispatchTransformedTouchEvent(ev, canceled, null,
                            TouchTarget.ALL_POINTER_IDS);
                } else {
                    // Dispatch to touch targets, excluding the new touch target if we already
                    // dispatched to it.  Cancel touch targets if necessary.
                    TouchTarget predecessor = null;
                    TouchTarget target = mFirstTouchTarget;
                    while (target != null) {
                        final TouchTarget next = target.next;
                        if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                            handled = true;
                        } else {
                            final boolean cancelChild = resetCancelNextUpFlag(target.child)
                            || intercepted;
                            if (dispatchTransformedTouchEvent(ev, cancelChild,
                                    target.child, target.pointerIdBits)) {
                                handled = true;
                            }
                            if (cancelChild) {
                                if (predecessor == null) {
                                    mFirstTouchTarget = next;
                                } else {
                                    predecessor.next = next;
                                }
                                target.recycle();
                                target = next;
                                continue;
                            }
                        }
                        predecessor = target;
                        target = next;
                    }
                }
     
                // Update list of touch targets for pointer up or cancel, if needed.
                if (canceled
                        || actionMasked == MotionEvent.ACTION_UP
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                    resetTouchState();
                } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
                    final int actionIndex = ev.getActionIndex();
                    final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
                    removePointersFromTouchTargets(idBitsToRemove);
                }
            }
     
            if (!handled && mInputEventConsistencyVerifier != null) {
                mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
            }
            return handled;
    }

      public boolean onInterceptTouchEvent(MotionEvent ev) {
            return false;
        }
      private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
                View child, int desiredPointerIdBits) {
            final boolean handled;
     
            // Canceling motions is a special case.  We don't need to perform any transformations
            // or filtering.  The important part is the action, not the contents.
            final int oldAction = event.getAction();
            if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
                event.setAction(MotionEvent.ACTION_CANCEL);
                if (child == null) {
                    handled = super.dispatchTouchEvent(event);
                } else {
                    handled = child.dispatchTouchEvent(event);
                }
                event.setAction(oldAction);
                return handled;
            }
     
            // Calculate the number of pointers to deliver.
            final int oldPointerIdBits = event.getPointerIdBits();
            final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
     
            // If for some reason we ended up in an inconsistent state where it looks like we
            // might produce a motion event with no pointers in it, then drop the event.
            if (newPointerIdBits == 0) {
                return false;
            }
     
            // If the number of pointers is the same and we don't need to perform any fancy
            // irreversible transformations, then we can reuse the motion event for this
            // dispatch as long as we are careful to revert any changes we make.
            // Otherwise we need to make a copy.
            final MotionEvent transformedEvent;
            if (newPointerIdBits == oldPointerIdBits) {
                if (child == null || child.hasIdentityMatrix()) {
                    if (child == null) {
                        handled = super.dispatchTouchEvent(event);
                    } else {
                        final float offsetX = mScrollX - child.mLeft;
                        final float offsetY = mScrollY - child.mTop;
                        event.offsetLocation(offsetX, offsetY);
     
                        handled = child.dispatchTouchEvent(event);
     
                        event.offsetLocation(-offsetX, -offsetY);
                    }
                    return handled;
                }
                transformedEvent = MotionEvent.obtain(event);
            } else {
                transformedEvent = event.split(newPointerIdBits);
            }
     
            // Perform any necessary transformations and dispatch.
            if (child == null) {
                handled = super.dispatchTouchEvent(transformedEvent);
            } else {
                final float offsetX = mScrollX - child.mLeft;
                final float offsetY = mScrollY - child.mTop;
                transformedEvent.offsetLocation(offsetX, offsetY);
                if (! child.hasIdentityMatrix()) {
                    transformedEvent.transform(child.getInverseMatrix());
                }
     
                handled = child.dispatchTouchEvent(transformedEvent);
            }
     
            // Done.
            transformedEvent.recycle();
            return handled;
        }

代碼量比較大,我們先概述一下各個函數(shù)的主要作用哼审。
dispatchTouchEvent主要用來分發(fā)事件谐腰,函數(shù)主要作用是來決定當前的事件是交由自己消費處理,還是交由子控件處理涩盾。
onInterceptTouchEvent主要來決定當前控件是否需要攔截傳遞給子控件十气,如果返回True表示該控件攔截,并交由自己父類的dispatchTouchEvent處理消費春霍,如果返回false表示不攔截砸西,允許傳遞給子控件處理。
dispatchTransformedTouchEvent主要根據(jù)傳來的子控件址儒,決定是自身處理消費芹枷,還是交由子控件處理消費。
我們主要來分析一下dispatchTouchEvent函數(shù):

      if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // restore action in case it was changed
                } else {
                    intercepted = false;
                }
            } else {
                // There are no touch targets and this action is not an initial down
                // so this view group continues to intercept touches.
                intercepted = true;
            }

這段代碼莲趣,如果當前傳遞的事件是Down(按下)或者當前觸摸鏈表不為空鸳慈,那么它調用onInterceptTouchEvent函數(shù),判斷是否進行事件攔截處理喧伞,通過返回值來決定intercepted變量的值走芋。
接下來if (!canceled && !intercepted){} 這個括號內的代碼需要注意了,只有當intercepted返回值為false的時候絮识,才滿足這個條件進入代碼段绿聘。因此,我們結合onInterceptTouchEvent源碼次舌,發(fā)現(xiàn)它默認值返回的是false熄攘,也就說如果你不重載onInterceptTouchEvent方法并令其返回True,它一定是返回false彼念,并能夠執(zhí)行花括號內的代碼挪圾。
我們分析一下花括號中的代碼,

if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {}
                        判斷當前的事件是否是ACTION_DOWN逐沙、ACTION_POINTER_DOWN(多點觸摸)哲思、ACTION_HOVER_MOVE(懸停),如果是吩案,執(zhí)行花括號內代碼棚赔, 
final int childrenCount = mChildrenCount;
if (childrenCount != 0) {}

判斷當前控件是否有子控件,如果大于0,執(zhí)行花括號內代碼靠益,
for (int i = childrenCount - 1; i >= 0; i--)遍歷子控件丧肴,
if (!canViewReceivePointerEvents(child)
判斷當前的down、POINTER_DOWN胧后、HOVER_MOVE三個事件的坐標點是否落在了子控件上芋浮,如果落在子控件上,
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign))
通過dispatchTransformedTouchEvent傳遞事件壳快,交由子控件判斷是否傳遞或自己消費處理纸巷。如果dispatchTransformedTouchEvent返回true,表示子控件已消費處理眶痰,并添加此子控件View到觸摸鏈表瘤旨,并放置鏈表頭,并結束遍歷子控件竖伯。newTouchTarget = addTouchTarget(child, idBitsToAssign);false表示未處理裆站。

接著分析

  if (mFirstTouchTarget == null) {
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
   } else {
       ……
}

mFirstTouchTarget什么時候為空呢?從前面的代碼可以看到黔夭,如果onInterceptTouchEvent返回為false(也就是不攔截)宏胯,mFirstTouchTarget就為空,直接交給自己父View執(zhí)行dispatchTouchEvent去了本姥。如果mFirstTouchTarget不為空肩袍,它就取出觸摸鏈表,逐個遍歷判斷處理婚惫,如果前面比如Down事件處理過了氛赐,就不再處理了。

一個完整的事件 一個up 一個down 和多個move盡可能有一個控件來完成這個事件先舷。假如一個事件被兩個控件分別完成 就是事件沖突艰管。ScrollView和Viewpager 就是事件沖突 down 和up不是一個控件來執(zhí)行。 左右和上下滑動沖突蒋川。解決就是自定義這兩個牲芋。
打斷和分發(fā)盡量 不要重寫
繼承View么有打斷事件。
打斷事件分發(fā)捺球,不能打斷本身的事件缸浦,只能打斷事件向下分發(fā),一旦事件被打斷氮兵,就由誰來處理裂逐,誰打斷誰處理

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市泣栈,隨后出現(xiàn)的幾起案子卜高,更是在濱河造成了極大的恐慌弥姻,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件掺涛,死亡現(xiàn)場離奇詭異蚁阳,居然都是意外死亡,警方通過查閱死者的電腦和手機鸽照,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來颠悬,“玉大人矮燎,你說我怎么就攤上這事∨獍” “怎么了诞外?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長灾票。 經(jīng)常有香客問我峡谊,道長,這世上最難降的妖魔是什么刊苍? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任既们,我火速辦了婚禮,結果婚禮上正什,老公的妹妹穿的比我還像新娘啥纸。我一直安慰自己,他們只是感情好婴氮,可當我...
    茶點故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布斯棒。 她就那樣靜靜地躺著,像睡著了一般主经。 火紅的嫁衣襯著肌膚如雪荣暮。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天罩驻,我揣著相機與錄音穗酥,去河邊找鬼。 笑死惠遏,一個胖子當著我的面吹牛迷扇,可吹牛的內容都是我干的。 我是一名探鬼主播爽哎,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼蜓席,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了课锌?” 一聲冷哼從身側響起厨内,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤祈秕,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后雏胃,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體请毛,經(jīng)...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年瞭亮,在試婚紗的時候發(fā)現(xiàn)自己被綠了方仿。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,102評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡统翩,死狀恐怖仙蚜,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情厂汗,我是刑警寧澤委粉,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站娶桦,受9級特大地震影響贾节,放射性物質發(fā)生泄漏。R本人自食惡果不足惜衷畦,卻給世界環(huán)境...
    茶點故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一栗涂、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧祈争,春花似錦戴差、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至墨吓,卻和暖如春球匕,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背帖烘。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工亮曹, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人秘症。 一個月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓照卦,卻偏偏與公主長得像,于是被迫代替她去往敵國和親乡摹。 傳聞我的和親對象是個殘疾皇子役耕,可洞房花燭夜當晚...
    茶點故事閱讀 45,044評論 2 355

推薦閱讀更多精彩內容