下面以點擊某個view之後的事件傳遞為例子。
首先分析view裡的dispatchTouchEvent()方法,它是點擊view執行的第一個方法。
public boolean dispatchTouchEvent(MotionEvent event) { if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && mOnTouchListener.onTouch(this, event)) { return true; } return onTouchEvent(event);}注意:裡麵包含兩個回調函數onTouch(),onTouchEvent();如果控件綁定了OnTouchListener,且該控件是enabled,那麼就執行onTouch()方法,如果該方法返回true,則說明該觸摸事件已經被OnTouchListener監聽器消費掉了,不會再往下分發了;但是如果返回false,則說明未被消費,繼續往下分發到該控件的onTouchEvent()去處理。
然後分析onTouchEvent()方法,進行進一步的觸摸事件處理。
if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) { switch (event.getAction()) { case MotionEvent.ACTION_UP: ..... performClick(); //響應點擊事件break; case MotionEvent.ACTION_DOWN: ..... break; case MotionEvent.ACTION_CANCEL: ..... break; case MotionEvent.ACTION_MOVE: ..... break; } return true; } return false;如果該控件是clickable 、long_clickable的,那麼就可以響應對應事件,響應完後返回true繼續響應。比如點擊事件,先響應ACTION_DOWN,然後break並返回true,然後手抬起,又從dispatchTouchEvent()分發下來,再響應ACTION_UP,裡面會去performClick()響應點擊事件。
響應點擊事件
public boolean performClick() { sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); if (mOnClickListener != null) { playSoundEffect(SoundEffectConstants.CLICK); mOnClickListener.onClick(this); return true; } return false;}裡面執行mOnClickListener.onClick(this);即回調綁定監聽器的onClick()函數。
關鍵點:
onTouch和onTouchEvent的區別,又該如何使用?
答:
當view控件接受到觸摸事件,如果控件綁定了onTouchListener監聽器,而且該控件是enable,那麼就去執行onTouch()方法,如果返回true,則已經把觸摸事件消費掉,不再向下傳遞;如果返回false,那麼繼續調用onTouchEvent()事件。
Android的Touch事件傳遞到Activity頂層的DecorView(一個FrameLayout)之後,會通過ViewGroup一層層往視圖樹的上面傳遞,最終將事件傳遞給實際接收的View。下面給出一些重要的方法。
dispatchTouchEvent
事件傳遞到一個ViewGroup上面時,會調用dispatchTouchEvent。代碼有刪減
public boolean dispatchTouchEvent(MotionEvent ev) { boolean handled = false; if (onFilterTouchEventForSecurity(ev)) { final int action = ev.getAction(); final int actionMasked = action & MotionEvent.ACTION_MASK; // Attention 1 :在按下時候清除一些狀態if (actionMasked == MotionEvent.ACTION_DOWN) { cancelAndClearTouchTargets(ev); //注意這個方法resetTouchState(); } // Attention 2:檢查是否需要攔截final boolean intercepted; //如果剛剛按下或者已經有子View來處理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 { // 不是一個動作序列的開始同時也沒有子View來處理,直接攔截intercepted = true; } //事件沒有取消同時沒有被當前ViewGroup攔截,去找是否有子View接盤if (!canceled && !intercepted) { //如果這是一系列動作的開始或者有一個新的Pointer按下我們需要去找能夠處理這個Pointer的子View 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 //上面說的觸碰點32的限制就是這裡導致final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex) : TouchTarget.ALL_POINTER_IDS; final int childrenCount = mChildrenCount; if (newTouchTarget == null && childrenCount != 0) { final float x = ev.getX(actionIndex); final float y = ev.getY(actionIndex); //對當前ViewGroup的所有子View進行排序,在上層的放在開始final ArrayList<View> preorderedList = buildOrderedChildList(); final boolean customOrder = preorderedList == null && isChildrenDrawingOrderEnabled(); final View[] children = mChildren; for (int i = childrenCount - 1; i >= 0; i--) { final int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i; final View child = (preorderedList == null) ? children[childIndex] : preorderedList.get(childIndex); // canViewReceivePointerEvents visible的View都可以接受事件// isTransformedTouchPointInView 計算是否落在點擊區域上if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) { ev.setTargetAccessibilityFocus(false); continue; } //能夠處理這個Pointer的View是否已經處理之前的Pointer,那麼把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; } } //Attention 3 : 直接發給子View if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { // Child wants to receive touch within its bounds. mLastTouchDownTime = ev.getDownTime(); if (preorderedList != null) { // childIndex points into presorted list, find original index for (int j = 0; j < childrenCount; j++) { if (children[childIndex] == mChildren[j]) { mLastTouchDownIndex = j; break; } } } else { mLastTouchDownIndex = childIndex; } mLastTouchDownX = ev.getX(); mLastTouchDownY = ev.getY(); newTouchTarget = addTouchTarget(child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true; break; } } } } } // 前面已經找到了接收事件的子View,如果為NULL,表示沒有子View來接手,當前ViewGroup需要來處理if (mFirstTouchTarget == null) { // ViewGroup處理handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); } else { if(alreadyDispatchedToNewTouchTarget) { //ignore some code if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) { handled = true; } } } return handled;}上面代碼中的Attention在後面部分將會涉及,重點注意。
這裡需要指出一點的是,一系列動作中的不同Pointer可以分配給不同的View去響應。 ViewGroup會維護一個PointerId和處理View的列表TouchTarget,一個TouchTarget代表一個可以處理Pointer的子View,當然一個View可以處理多個Pointer,比如兩根手指都在某一個子View區域。 TouchTarget內部使用一個int來存儲它能處理的PointerId,一個int32位,這也就是上層為啥最多只能允許同時最多32點觸碰。
看一下Attention 3 處的代碼,我們經常說view的dispatchTouchEvent如果返回false,那麼它就不能係列動作後面的動作,這是為啥呢?因為Attention 3處如果返回false,那麼它不會被記錄到TouchTarget中,ViewGroup認為你沒有能力處理這個事件。
這裡可以看到,ViewGroup真正處理事件是在dispatchTransformedTouchEvent裡面,跟進去看看:
dispatchTransformedTouchEventprivate boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) { //沒有子類處理,那麼交給viewgroup處理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); } return handled;}可以看到這裡不管怎麼樣,都會調用View的dispatchTouchEvent,這是真正處理這一次點擊事件的地方。
dispatchTouchEvent public boolean dispatchTouchEvent(MotionEvent event) { if (onFilterTouchEventForSecurity(event)) { //先走View的onTouch事件,如果onTouch返回True ListenerInfo li = mListenerInfo; if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { result = true; } if (!result && onTouchEvent(event)) { result = true; } } return result; }我們給View設置的onTouch事件處在一個較高的優先級,如果onTouch執行返回true,那麼就不會去走view的onTouchEvent,而我們一些點擊事件都是在onTouchEvent中處理的,這也是為什麼onTouch中返回true,view的點擊相關事件不會被處理。
小小總結一下這個流程
ViewGroup在接受到上級傳下來的事件時,如果是一系列Touch事件的開始(ACTION_DOWN),ViewGroup會先看看自己需不需要攔截這個事件(onInterceptTouchEvent,ViewGroup的默認實現直接返回false表示不攔截),接著ViewGroup遍歷自己所有的View。找到當前點擊的那個View,馬上調用目標View的dispatchTouchEvent。如果目標View的dispatchTouchEvent返回false,那麼認為目標View只是在那個位置而已,它並不想接受這個事件,只想安安靜靜的做一個View(我靜靜地看著你們裝*)。此時,ViewGroup還會去走一下自己dispatchTouchEvent,Done!