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

android ViewGroup的事件分發機制

              android 事件分發機制的理解
              
Android 事件分發機制一直讓我很混亂,最近拼了,仔細研讀原始碼,有了一些自己的認識,今天記下.梳理下知識.
  
學習事件分發機制,就必須先了解一個物件. MontionEvent. 該物件分裝了使用者觸碰屏膜這一事件.
  
系統有一個執行緒在迴圈收集螢幕硬體資訊,當用戶觸控式螢幕幕時,該執行緒會把從硬體裝置收集到的資訊封裝成一個MotionEvent物件,然後把該物件存放到一個訊息佇列中。
系統的另一個執行緒迴圈的讀取訊息佇列中的MotionEvent,然後交給WMS去派發,WMS把該事件派發給當前處於活動的Activity,即處於活動棧最頂端的Activity。
這就是一個先進先出的消費者和生產者的模板,一個執行緒不停的建立MotionEvent物件放入佇列中,另一個執行緒不斷的從佇列中取出MotionEvent物件進行分發。
當用戶的手指從接觸螢幕到離開螢幕,是一個完整的觸控事件,在該事件中,系統會不斷收集事件資訊封裝成MotionEvent物件。收集的間隔時間取決於硬體裝置,
例如螢幕的靈敏度以及cpu的計算能力。目前的手機一般在20毫秒左右。

MontionEvent詳解:

MotionEvent物件包含了觸控事件的時間、位置、面積、壓力、以及本次事件的Dwon發生的時間。
MotionEvent常用的Action分為5種:Down 、Up、Move、Cancel、OutSide
MotionEvent中我們常用的方法就是獲取點選的座標,因為這是與我們操作息息相關的。獲取座標有兩種方式:
      getX和getY用於獲取以該View左上角為座標原點的座標
      getRowX和getRowY用於獲取以螢幕左上角為座標原點的座標
  
   事件分發機制的粗粒度流程分析
  
   我們知道一個activity的佈局檢視樹的根節點是DecorView(實際上就是個FrameLayout).MontionEvent實現了Parcelable介面.所以系統服務可以跨程序將該物件傳給我們應用中的處於最頂端的Activity的
   檢視樹的根節點DecorView.DecorView的dispatchTouchEvent()開始下發事件.
  
   這裡有一個和其他事件傳遞不同的地方,DecorView會優先傳遞給Activity,而不是它的子View。而Activity如果不處理又會回傳給DecorView,DecorView才會再將事件傳給子View。
dispatchTouchEvent就是觸控事件傳遞的對外介面,無論是DecorView傳給Activity,還是ViewGroup傳遞給子View,都是直接呼叫對方的dispatchTouchEvent方法,並傳遞MotionEvent引數。

粗粒度的流程講完了.讓我們具體到view與ViewGroup裡來看他們的事件分發機制吧.

View 的事件分發比較簡單,因不包含子View.看其原始碼
public boolean disPatchTouchEvent(MotionEvent ev){
   監聽是否註冊了touchListener  是否是可用的(可以獲取焦點被點選和觸碰的view) tounch監聽是否消費了該事件
  if(mTouchListener!=null&&(myViewFlags&ENABLE_FALAG)==ENABLED&&mTouchListener.onTouch(this,ev)){
   以上三個條件都成立則說明事件傳遞給了view的tounchListener來處理.並終結.
   return true
  }
  事件交由View的OnTouchEvent來處理.結果在上傳給其上層的ViewGroup
  return onTounchEvent(ev)
}

首先判斷是否設定了觸控監聽,並且可以響應事件,就交由監聽的onTouch處理。
如果上述條件不成立,或者監聽的onTouch事件沒有消費掉該事件,則交由onTouchEvent進行處理,並把返回結果交給上層。

Viewgroup 的事件分發
 Down事件的分發:
 我會一步步的解釋原始碼將其步驟列出.
 1.通過引數MontionEvent 獲取事件的佈局並將其轉換為檢視座標以便於在事件傳遞時傳遞座標給子view.
 2.將mMontionTarget(View)置為空.因為在down事件分發的時候會將事件傳遞給view,而在其他型別事件(move,cancle...)時的分發時,就不必在通過遍歷view樹了.直接將事件分發給
 將mMontionTarget.這裡將其置為空,以便遍後面記錄down事件接受事件的view.
 3.通過一個if判斷來決定是否分發事件.if(disallowIntcept||!onIntceptTouchEvent()) disallowIntcept是表示viewGroup預設是否攔截事件.其預設值是false.onIntceptTouchEvent()預設
 也是返回false,所以viewGroup預設是會向下分發事件.當然我們也可以複寫onIntceptTouchEvent(),來攔截事件.
 
 4.分發事件,開始遍歷viewGroup的子view.注意是倒著遍歷子view的.事件會先分發給後加入的view.
 5.遍歷中(for迴圈中)判斷當前的的view是否可見,是否有動畫(可見或有動畫的view是可以接受點選事件的)
 6.判斷事件的佈局座標是否在當前view的座標範圍內.
 7.在將檢視座標轉變為佈局座標.以便後面付給這個view.
 8.又一個判斷 if(child.disPatchTouchEvent(ev)){mMontionTarget=child,return true;}
 這個是最關鍵的一步.判斷裡,child如果是viewGroup,就遞迴走上面的程式碼,繼續分發事件,事件或者被viewgroup給攔截消費向上傳.或不攔截繼續往下傳或許是view分發處理在往上傳給父view的onTouch().
 上傳過程中或許事件中途被消費或傳到最頂端消失.
 
 其他事件:
 直接將事件傳遞給down事件傳遞時的的mMontionTargetdisPatchTouchEvent.以下是跟專業詳細的說法:
 
 
   判斷事件是否被取消或者事件是否要攔截住,是的話,給Down事件找到的target傳送一個取消事件。
  如果不取消,也不攔截,並且Down已經找到了target,則直接交給target處理,不再遍歷子View尋找合適的View了。
  這種處理事件是正確的,我們用手機經常可以體會到,當我手指按在一個拖動條上之後,在拖動的時候手指就算移出了拖動條,依然會把事件分發給拖動條控制它的拖動。

 在對viewGroup和View的事件分發機制有了詳細的瞭解後,我們現在重現一下當用戶觸碰了一下Android手機後整個事件分發的過程.
 系統有windowManger的服務一直在後臺執行.有兩種執行緒,一種不斷的手機螢幕被觸碰的事件,可以稱為取樣執行緒.一般手機每20mm就去收集一次放入訊息佇列中.touch事件有多種型別,up,move,cancle等.另一中則
 個執行緒則不斷從訊息佇列中取出事件進行分發.事件會被分發給任務棧最頂端,即與使用者互動的activity的DecorView(即FrameLayout).這裡注意事件不會直接從DecorView開始向下分發,而是先
 傳遞給activity.如果activity的disPatchTouchEvent()返回false則事件回傳給DecorView的disPatchTouchEvent()開始進行事件分發.而activity的onTouchevent()會是最後一個獲得事件.當然前提
 是事件沒有被某個view消費.
 DecorView 的disPatchTouchEvent()開始遵照viewGroup的事件分發機制開始分發事件詳見上面的類容.
 從view及viewGroup的事件分發機制我們可以看出我們可以複寫viewGroup的onIntceptTouchEvent()來攔截事件.複寫onTouchEvent()來處理事件.當然onTouch裡也可以.這裡有研究必要的是OnTouchEvent()的原始碼
 在OnTouchEvent(){onTouch是由程式設計師自己實現無原始碼研究}.OnTouchEvent主要處理了click longClick 和cancle等一般性事件.我們色置的clicklistener,也在這裡被呼叫.具體怎樣處理日後研究.
 
 注意:一:只要view的onIntceptTouchEvent()返回false,事件就不會傳遞給其子view 不管其onTouch()返回是否是true.如果onTouch返回true向上傳遞false事件消費.
  二:viewGroup也可以設定OnTouchListener 其onTouch()會在子View()的onTouchEvent()執行完後由內往外執行.如果子view是view則還要在其onTouch()執行完後執行.