1. 程式人生 > >android 事件分發機制(ViewGroup)

android 事件分發機制(ViewGroup)

   昨天寫了一篇文章 是關於view 的事件分發機制    那麼今天我們將繼續上次未完成的話題,從原始碼的角度分析ViewGruop的事件分發。

    首先確定下  ViewGroup  和Vew 有什麼區別     ViewGroup    顧名思義就是一個view 的集合 它包含很多的子View和子VewGroup,是Android中所有佈局的父類或間接父類,像LinearLayout、RelativeLayout等都是繼承自ViewGroup的。但ViewGroup實際上也是一個View,只不過比起View,它多了可以包含子View和定義佈局引數的功能。ViewGroup繼承結構示意圖如下所示:

先寫一個類繼承LinearLayout


然後寫下佈局



新增好點選事件

分別點選一下Button1、Button2和空白區域,打印出每個view

我第一個想法是 他應該先執行了buttond 觸控 再去執行 viewGroup的觸控

現在下結論還未免過早了,讓我們再來做一個實驗。

先來看一個 原始碼 

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

很簡單  返回 true 和false     重寫這個函式 返回true 試試看  程式碼如下

這個時候 你不管點選哪個 他觸發的都是 MyViewGroup  的觸控事件

按鈕的點選事件完全被遮蔽掉了! 之前我就說過 當用戶手指觸控的時候 肯定會觸發一個事件 就是 dispatchTouchEvent  函式 

實際情況是,當你點選了某個控制元件,首先會去呼叫該控制元件所在佈局的dispatchTouchEvent方法,然後在佈局的dispatchTouchEvent方法中找到被點選的相應控制元件,再去呼叫該控制元件的dispatchTouchEvent方法。如果我們點選了MyLayout中的按鈕,會先去呼叫MyLayout的dispatchTouchEvent方法,可是你會發現MyLayout中並沒有這個方法。那就再到它的父類LinearLayout中找一找,發現也沒有這個方法。那隻好繼續再找LinearLayout的父類ViewGroup,你終於在ViewGroup中看到了這個方法,按鈕的dispatchTouchEvent方法就是在這裡呼叫的。


  1. publicboolean dispatchTouchEvent(MotionEvent ev) {  
  2.     finalint action = ev.getAction();  
  3.     finalfloat xf = ev.getX();  
  4.     finalfloat yf = ev.getY();  
  5.     finalfloat scrolledXFloat = xf + mScrollX;  
  6.     finalfloat scrolledYFloat = yf + mScrollY;  
  7.     final Rect frame = mTempRect;  
  8.     boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;  
  9.     if (action == MotionEvent.ACTION_DOWN) {  
  10.         if (mMotionTarget != null) {  
  11.             mMotionTarget = null;  
  12.         }  
  13.         if (disallowIntercept || !onInterceptTouchEvent(ev)) {  
  14.             ev.setAction(MotionEvent.ACTION_DOWN);  
  15.             finalint scrolledXInt = (int) scrolledXFloat;  
  16.             finalint scrolledYInt = (int) scrolledYFloat;  
  17.             final View[] children = mChildren;  
  18.             finalint count = mChildrenCount;  
  19.             for (int i = count - 1; i >= 0; i--) {  
  20.                 final View child = children[i];  
  21.                 if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE  
  22.                         || child.getAnimation() != null) {  
  23.                     child.getHitRect(frame);  
  24.                     if (frame.contains(scrolledXInt, scrolledYInt)) {  
  25.                         finalfloat xc = scrolledXFloat - child.mLeft;  
  26.                         finalfloat yc = scrolledYFloat - child.mTop;  
  27.                         ev.setLocation(xc, yc);  
  28.                         child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
  29.                         if (child.dispatchTouchEvent(ev))  {  
  30.                             mMotionTarget = child;  
  31.                             returntrue;  
  32.                         }  
  33.                     }  
  34.                 }  
  35.             }  
  36.         }  
  37.     }  
  38.     boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||  
  39.             (action == MotionEvent.ACTION_CANCEL);  
  40.     if (isUpOrCancel) {  
  41.         mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;  
  42.     }  
  43.     final View target = mMotionTarget;  
  44.     if (target == null) {  
  45.         ev.setLocation(xf, yf);  
  46.         if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {  
  47.             ev.setAction(MotionEvent.ACTION_CANCEL);  
  48.             mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
  49.         }  
  50.         returnsuper.dispatchTouchEvent(ev);  
  51.     }  
  52.     if (!disallowIntercept && onInterceptTouchEvent(ev)) {  
  53.         finalfloat xc = scrolledXFloat - (float) target.mLeft;  
  54.         finalfloat yc = scrolledYFloat - (float) target.mTop;  
  55.         mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
  56.         ev.setAction(MotionEvent.ACTION_CANCEL);  
  57.         ev.setLocation(xc, yc);  
  58.         if (!target.dispatchTouchEvent(ev)) {  
  59.         }  
  60.         mMotionTarget = null;  
  61.         returntrue;  
  62.     }  
  63.     if (isUpOrCancel) {  
  64.         mMotionTarget = null;  
  65.     }  
  66.     finalfloat xc = scrolledXFloat - (float) target.mLeft;  
  67.     finalfloat yc = scrolledYFloat - (float) target.mTop;  
  68.     ev.setLocation(xc, yc);  
  69.     if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {  
  70.         ev.setAction(MotionEvent.ACTION_CANCEL);  
  71.         target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
  72.         mMotionTarget = null;  
  73.     }  
  74.     return target.dispatchTouchEvent(ev);  
  75. }  
首先在第13行可以看到一個條件判斷,如果disallowIntercept和!onInterceptTouchEvent(ev)兩者有一個為true,就會進入到這個條件判斷中。disallowIntercept是指是否禁用掉事件攔截的功能,預設是false,也可以通過呼叫requestDisallowInterceptTouchEvent方法對這個值進行修改。那麼當第一個值為false的時候就會完全依賴第二個值來決定是否可以進入到條件判斷的內部,第二個值是什麼呢?竟然就是對onInterceptTouchEvent方法的返回值取反!也就是說如果我們在onInterceptTouchEvent方法中返回false,就會讓第二個值為true,從而進入到條件判斷的內部,如果我們在onInterceptTouchEvent方法中返回true,就會讓第二個值為false,從而跳出了這個條件判斷。

這個時候你就可以思考一下了,由於我們剛剛在MyLayout中重寫了onInterceptTouchEvent方法,讓這個方法返回true,導致所有按鈕的點選事件都被遮蔽了,那我們就完全有理由相信,按鈕點選事件的處理就是在第13行條件判斷的內部進行的!

另外看這一段程式碼

  1.  for (int i = count - 1; i >= 0; i--) {  
  2.                 final View child = children[i];  
  3.                 if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE  
  4.                         || child.getAnimation() != null) {  
  5.                     child.getHitRect(frame);  
  6.                     if (frame.contains(scrolledXInt, scrolledYInt)) {  
  7.                         finalfloat xc = scrolledXFloat - child.mLeft;  
  8.                         finalfloat yc = scrolledYFloat - child.mTop;  
  9.                         ev.setLocation(xc, yc);  
  10.                         child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
  11.                         if (child.dispatchTouchEvent(ev))  {  
  12.                             mMotionTarget = child;  
  13.                             returntrue;  
  14.                         }  
  15.                     }  
  16.                 }  
  17.             }  
在這裡遍歷整個子view  看第11杭 找當前點選的自雷 執行了  子類發 dispatchTouchEvent 

最後再來簡單梳理一下吧。

1. Android事件分發是先傳遞到ViewGroup,再由ViewGroup傳遞到View的。

2. 在ViewGroup中可以通過onInterceptTouchEvent方法對事件傳遞進行攔截,onInterceptTouchEvent方法返回true代表不允許事件繼續向子View傳遞,返回false代表不對事件進行攔截,預設返回false。

3. 子View中如果將傳遞的事件消費掉,ViewGroup中將無法接收到任何事件。