帶你從原始碼角度分析ViewGroup中事件分發流程
阿新 • • 發佈:2018-12-10
序言
這篇博文不是對事件分發機制全面的介紹,只是從原始碼的角度分析ACTION_DOWN、ACTION_MOVE、ACTION_UP事件在ViewGroup中的分發邏輯,瞭解各個事件在ViewGroup的分發邏輯對理解、解決滑動衝突問題很有幫助。ViewGroup中事件分發流程
這裡我是用的是2.3.3版本的原始碼,原因在於這個版本的原始碼易讀,當你理順了整個分發流程,再去看其他更加高階版本的原始碼,你會發現思想是一樣。如果一個事件傳遞給ViewGroup,那麼dispatchTouchEvent方法會被呼叫,以下是對dispatchTouchEvent的分析。public boolean dispatchTouchEvent(MotionEvent ev) { if (!onFilterTouchEventForSecurity(ev)) { return false; } final int action = ev.getAction(); //獲取事件的座標 final float xf = ev.getX(); final float yf = ev.getY(); final float scrolledXFloat = xf + mScrollX; final float scrolledYFloat = yf + mScrollY; final Rect frame = mTempRect; //disallowIntercept 預設是false, //可以通過requestDisallowItercepctTouchEvent來設定引數 //被設定成true後,ViewGroup無法攔截除ACTION_DOWN以外的事件(只能攔截Down) boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; //這裡是ACTION_DOWN的處理邏輯 if (action == MotionEvent.ACTION_DOWN) { if (mMotionTarget != null) { // this is weird, we got a pen down, but we thought it was // already down! //We should probably send an ACTION_UP to the current target mMotionTarget = null; } // If we're disallowing intercept or if we're allowing and we didn't // intercept //預設情況下disallowIntercept為false,表示允許攔截, //預設情況ViewGroup的onInterceptTouchEvent返回false if (disallowIntercept || !onInterceptTouchEvent(ev)) { // reset this event's action (just to protect ourselves) ev.setAction(MotionEvent.ACTION_DOWN); // We know we want to dispatch the event down, find a child // who can handle it, start with the front-most child. final int scrolledXInt = (int) scrolledXFloat; final int scrolledYInt = (int) scrolledYFloat; final View[] children = mChildren; final int count = mChildrenCount; for (int i = count - 1; i >= 0; i--) {//遍歷子View final View child = children[i]; //判斷子元素是否可以接收到事件,兩條件決定 //條件一:子View是VISIBLE或者在播動畫 //條件二:點選座標落在子View區域內(體現在內嵌的if) if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) { child.getHitRect(frame); //判斷是否點選座標落在子控制元件區域內 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; //將事件派發給子View,返回true表示子View處理該事件, if (child.dispatchTouchEvent(ev)) { // Event handled, we have a target now. mMotionTarget = child;//將處理事件的目標View儲存在變數 return true;//返回true,表示消耗事件 } // The event didn't get handled, try the next view. // Don't reset the event's location, it's not // necessary here. } } } } } boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) || (action == MotionEvent.ACTION_CANCEL); if (isUpOrCancel) { //重置mGroupFlags, //使得在下一個事件ACTION_DOWN來臨時disallowIntercept為false mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT; } // The event wasn't an ACTION_DOWN, dispatch it to our target if // we have one. final View target = mMotionTarget; if (target == null) {//沒有找到可以處理事件的子View // We don't have a target, this means we're handling the // event as a regular view. ev.setLocation(xf, yf); if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) { ev.setAction(MotionEvent.ACTION_CANCEL); mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; } //子控制元件不處理,所以此處判斷一下自己是否處理 //此時ViewGroup呼叫的是父類View的dispatchTouchEvent return super.dispatchTouchEvent(ev); } // if have a target, see if we're allowed to and want to intercept its // events //允許並且想要攔截事件 if (!disallowIntercept && onInterceptTouchEvent(ev)) { final float xc = scrolledXFloat - (float) target.mLeft; final float yc = scrolledYFloat - (float) target.mTop; mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; ev.setAction(MotionEvent.ACTION_CANCEL);//設定ACTION_CANCEL ev.setLocation(xc, yc); if (!target.dispatchTouchEvent(ev)) {//告知目標子控制元件事件被攔截 // target didn't handle ACTION_CANCEL. not much we can do // but they should have. } // clear the target mMotionTarget = null;//重置為null,使得下個事件來臨時target=null // Don't dispatch this event to our own view, because we already // saw it when intercepting; we just want to give the following // event to the normal onTouchEvent(). return true;//返回true,表示事件被消耗 } if (isUpOrCancel) { mMotionTarget = null; } // finally offset the event to the target's coordinate system and // dispatch the event. final float xc = scrolledXFloat - (float) target.mLeft; final float yc = scrolledYFloat - (float) target.mTop; ev.setLocation(xc, yc); if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) { ev.setAction(MotionEvent.ACTION_CANCEL); target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; mMotionTarget = null; } //將事件分發給目標子控制元件 return target.dispatchTouchEvent(ev); }
我們知道一些操作會產生事件,比方說在螢幕上滑動一下,這樣會產生一系列事件,但這些事件是屬於同一序列,它以ACTION_DOWN事件開始,中間有若干個ACTION_MOVE事件,以ACTION_UP事件結束。
ACTION_DOWN事件分發過程
ACTION_DOWN事件被分發到ViewGroup的時候是如何進行邏輯判斷的呢,在第31行程式碼可以看到,首先會通過條件:disallowIntercept||!onInterceptTouchEvent判斷是否攔截,disallowIntercept 預設是false,可以通過requestDisallowItercepctTouchEvent來設定引數,若這個條件不成立表示攔截則此時mMotionTarget = null,則來到第80行,那麼target = null,表示 沒有找到可以處理事件的子控制元件,接下來執行第91行程式碼,ACTION_DOWN事件用流程圖表示如下:
ACTION_MOVE事件分發過程
ACTION_MOVE事件被分發到ViewGroup的時候,從第81行可以看到首先會判斷target是否等於null,若等於null,表示子控制元件均不能消耗事件,則呼叫super.dispatchTouchEvent即View的dispatchTouchEvent來處理事件。若不等於空,此時會來到第97行,此時通過條件(!disallowIntercept&&onInterceptTouchEvent(ev))判斷是否攔截,若條件不成立表示不攔截則執行第131行程式碼,呼叫target.dispatchTouchEvent將事件分發給目標子控制元件處理,如果攔截則首先生成ACTION_CANCEL事件(見第101行)並分發給目標子控制元件target(見第103行),告知事件已被攔截,之後執行第108行將mMotionTarget重置為null,目的是讓接下來的ACTION_UP事件直接能給ViewGroup自己處理,最後在第112行返回true表示事件被消耗。
ACTION_MOVE事件用流程圖表示如下:
ACTION_UP事件分發過程
ACTION_UP分發到ViewGroup的時候,首先會通過執行mGrouFlags & = ~FALG_DISALLOW_INTERCEPT使得下一個ACTION_DOWN事件來臨時disallowIntecept重置為預設的false,之後的處理邏輯和ACTION_MOVE基本一致,這裡不再重複
ACTION_UP事件用流程圖表示如下: