編輯:關於Android編程
向下分發
向下分發:Android接收到觸屏事件,由Activity開始逐層向下傳遞,直到傳遞給一個View,或者ViewGroup攔截這個事件為止。
分發和傳遞的方法分別為dispatchTouchEvent,onInterceptTouchEvent
其中dispatchTouchEvent,View、Activity、ViewGroup都有
onInterceptTouchEvent 只有ViewGroup有
事件傳遞給ViewGroup時候,ViewGroup會調用dispatchTouchEvent,
dispatchTouchEvent又會調用onInterceptTouchEvent方法
如果onInterceptTouchEvent返回true(攔截)那麼,這個事件就交給這個ViewGroup來消耗,如果不攔截,向下找子View看哪個消耗(調用onTouchEvent)
注意:onInterceptTouchEvent 不是一直調用的
觸屏事件可以是一些列事件 dowm->move->move…move->up
如果這個事件是由這個ViewGroup處理的那麼在之後不會調用onInterceptTouchEvent
如果有個標志位FLAG_DISALLOW_INTERCEPT 設置為true 也不會再次調用
onInterceptTouchEvent 而是直接把其賦值為false
注意:
onTouchListener的onTouch方法優先級比onTouchEvent高,會先觸發。
假如onTouch方法返回false會接著觸發onTouchEvent,反之onTouchEvent方法不會被調用。
內置諸如click事件的實現等等都基於onTouchEvent,假如onTouch返回true,這些事件將不會被觸發。
public boolean dispatchTouchEvent(MotionEvent event){ ... ... if(onFilterTouchEventForSecurity(event)){ ListenerInfo li = mListenerInfo; if(li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { return true; } if(onTouchEvent(event)){ return true; } } ... ... return false; }
import android.view.MotionEvent; import android.view.View; public class ZeroDispatchTouchEvent { /** * dispatchTouchEvent()源碼學習及其注釋 * 常說事件傳遞中的流程是:dispatchTouchEvent->onInterceptTouchEvent->onTouchEvent * 在這個鏈條中dispatchTouchEvent()是處在鏈首的位置當然也是最重要的. * 在dispatchTouchEvent()決定了Touch事件是由自己的onTouchEvent()處理 * 還是分發給子View處理讓子View調用其自身的dispatchTouchEvent()處理. * * * 其實dispatchTouchEvent()和onInterceptTouchEvent()以及onTouchEvent()的關系 * 在dispatchTouchEvent()方法的源碼中體現得很明顯. * 比如dispatchTouchEvent()會調用onInterceptTouchEvent()來判斷是否要攔截. * 比如dispatchTouchEvent()會調用dispatchTransformedTouchEvent()方法且在該方法中遞歸調用 * dispatchTouchEvent();從而會在dispatchTouchEvent()裡最終調用到onTouchEvent() * * * * 重點關注: * 1 子View對於ACTION_DOWN的處理十分重要!!!!! * ACTION_DOWN是一系列Touch事件的開端,如果子View對於該ACTION_DOWN事件在onTouchEvent()中返回了false即未消費. * 那麼ViewGroup就不會把後續的ACTION_MOVE和ACTION_UP派發給該子View.在這種情況下ViewGroup就和普通的View一樣了, * 調用該ViewGroup自己的dispatchTouchEvent()從而調用自己的onTouchEvent();即不會將事件分發給子View. * 詳細代碼請參見如下代碼分析. * * 2 為什麼子view對於Touch事件處理返回true那麼其上層的ViewGroup就無法處理Touch事件了????? * 這個想必大家都知道了,因為該Touch事件被子View消費了其上層的ViewGroup就無法處理該Touch事件了. * 那麼在源碼中的依據是什麼呢??請看下面的源碼分析 */ @Override 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; /** * 第一步:對於ACTION_DOWN進行處理(Handle an initial down) * 因為ACTION_DOWN是一系列事件的開端,當是ACTION_DOWN時進行一些初始化操作. * 從源碼的注釋也可以看出來:清除以往的Touch狀態(state)開始新的手勢(gesture) * cancelAndClearTouchTargets(ev)中有一個非常重要的操作: * 將mFirstTouchTarget設置為null!!!! * 隨後在resetTouchState()中重置Touch狀態標識 */ 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) * 在dispatchTouchEvent(MotionEventev)這段代碼中 * 使用變量intercepted來標記ViewGroup是否攔截Touch事件的傳遞. * 該變量在後續代碼中起著很重要的作用. */ final boolean intercepted; // 事件為ACTION_DOWN或者mFirstTouchTarget不為null(即已經找到能夠接收touch事件的目標組件)時if成立 if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { //判斷disallowIntercept(禁止攔截)標志位 //因為在其他地方可能調用了requestDisallowInterceptTouchEvent(boolean disallowIntercept) //從而禁止執行是否需要攔截的判斷(有點拗口~其實看requestDisallowInterceptTouchEvent()方法名就可明白) final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; //當沒有禁止攔截判斷時(即disallowIntercept為false)調用onInterceptTouchEvent(ev)方法 if (!disallowIntercept) { //既然disallowIntercept為false那麼就調用onInterceptTouchEvent()方法將結果賦值給intercepted //常說事件傳遞中的流程是:dispatchTouchEvent->onInterceptTouchEvent->onTouchEvent //其實在這就是一個體現,在dispatchTouchEvent()中調用了onInterceptTouchEvent() intercepted = onInterceptTouchEvent(ev); ev.setAction(action); // restore action in case it was changed } else { //當禁止攔截判斷時(即disallowIntercept為true)設置intercepted = false intercepted = false; } } else { //當事件不是ACTION_DOWN並且mFirstTouchTarget為null(即沒有Touch的目標組件)時 //設置 intercepted = true表示ViewGroup執行Touch事件攔截的操作。 //There are no touch targets and this action is not an initial down //so this view group continues to intercept touches. intercepted = true; } /** * 第三步:檢查cancel(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; //不是ACTION_CANCEL並且ViewGroup的攔截標志位intercepted為false(不攔截) if (!canceled && !intercepted) { //處理ACTION_DOWN事件.這個環節比較繁瑣. 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) { // 依據Touch坐標尋找子View來接收Touch事件 // 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); final boolean customOrder = isChildrenDrawingOrderEnabled(); // 遍歷子View判斷哪個子View接受Touch事件 for (int i = childrenCount - 1; i >= 0; i--) { final int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i; final View child = children[childIndex]; if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) { continue; } newTouchTarget = getTouchTarget(child); if (newTouchTarget != null) { // 找到接收Touch事件的子View!!!!!!!即為newTouchTarget. // 既然已經找到了,所以執行break跳出for循環 // 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不滿足,當然也不會執行break語句. * 於是代碼會執行到這裡來. * * 調用方法dispatchTransformedTouchEvent()將Touch事件傳遞給子View做 * 遞歸處理(也就是遍歷該子View的View樹) * 該方法很重要,看一下源碼中關於該方法的描述: * Transforms a motion event into the coordinate space of a particular child view, * filters out irrelevant pointer ids, and overrides its action if necessary. * If child is null, assumes the MotionEvent will be sent to this ViewGroup instead. * 將Touch事件傳遞給特定的子View. * 該方法十分重要!!!!在該方法中為一個遞歸調用,會遞歸調用dispatchTouchEvent()方法!!!!!!!!!!!!!! * 在dispatchTouchEvent()中: * 如果子View為ViewGroup並且Touch沒有被攔截那麼遞歸調用dispatchTouchEvent() * 如果子View為View那麼就會調用其onTouchEvent(),這個就不再贅述了. * * * 該方法返回true則表示子View消費掉該事件,同時進入該if判斷. * 滿足if語句後重要的操作有: * 1 給newTouchTarget賦值 * 2 給alreadyDispatchedToNewTouchTarget賦值為true. * 看這個比較長的英語名字也可知其含義:已經將Touch派發給新的TouchTarget * 3 執行break. * 因為該for循環遍歷子View判斷哪個子View接受Touch事件,既然已經找到了 * 那麼就跳出該for循環. * 4 注意: * 如果dispatchTransformedTouchEvent()返回false即子View * 的onTouchEvent返回false(即Touch事件未被消費)那麼就不滿足該if條件,也就無法執行addTouchTarget() * 從而導致mFirstTouchTarget為null.那麼該子View就無法繼續處理ACTION_MOVE事件 * 和ACTION_UP事件!!!!!!!!!!!!!!!!!!!!!! * 5 注意: * 如果dispatchTransformedTouchEvent()返回true即子View * 的onTouchEvent返回true(即Touch事件被消費)那麼就滿足該if條件. * 從而mFirstTouchTarget不為null!!!!!!!!!!!!!!!!!!! * 6 小結: * 對於此處ACTION_DOWN的處理具體體現在dispatchTransformedTouchEvent() * 該方法返回boolean,如下: * true---->事件被消費----->mFirstTouchTarget!=null * false--->事件未被消費---->mFirstTouchTarget==null * 因為在dispatchTransformedTouchEvent()會調用遞歸調用dispatchTouchEvent()和onTouchEvent() * 所以dispatchTransformedTouchEvent()的返回值實際上是由onTouchEvent()決定的. * 簡單地說onTouchEvent()是否消費了Touch事件(true or false)的返回值決定了dispatchTransformedTouchEvent() * 的返回值!!!!!!!!!!!!!從而決定了mFirstTouchTarget是否為null!!!!!!!!!!!!!!!!從而進一步決定了ViewGroup是否 * 處理Touch事件.這一點在下面的代碼中很有體現. * * */ if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { // Child wants to receive touch within its bounds. mLastTouchDownTime = ev.getDownTime(); mLastTouchDownIndex = childIndex; mLastTouchDownX = ev.getX(); mLastTouchDownY = ev.getY(); newTouchTarget = addTouchTarget(child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true; break; } } } /** * 該if條件表示: * 經過前面的for循環沒有找到子View接收Touch事件並且之前的mFirstTouchTarget不為空 */ 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指向了最初的TouchTarget newTouchTarget.pointerIdBits |= idBitsToAssign; } } } /** * 分發Touch事件至target(Dispatch to touch targets) * * 經過上面對於ACTION_DOWN的處理後mFirstTouchTarget有兩種情況: * 1 mFirstTouchTarget為null * 2 mFirstTouchTarget不為null * * 當然如果不是ACTION_DOWN就不會經過上面較繁瑣的流程 * 而是從此處開始執行,比如ACTION_MOVE和ACTION_UP */ if (mFirstTouchTarget == null) { /** * 情況1:mFirstTouchTarget為null * * 經過上面的分析mFirstTouchTarget為null就是說Touch事件未被消費. * 即沒有找到能夠消費touch事件的子組件或Touch事件被攔截了, * 則調用ViewGroup的dispatchTransformedTouchEvent()方法處理Touch事件則和普通View一樣. * 即子View沒有消費Touch事件,那麼子View的上層ViewGroup才會調用其onTouchEvent()處理Touch事件. * 在源碼中的注釋為:No touch targets so treat this as an ordinary view. * 也就是說此時ViewGroup像一個普通的View那樣調用dispatchTouchEvent(),且在dispatchTouchEvent() * 中會去調用onTouchEvent()方法. * 具體的說就是在調用dispatchTransformedTouchEvent()時第三個參數為null. * 第三個參數View child為null會做什麼樣的處理呢? * 請參見下面dispatchTransformedTouchEvent()的源碼分析 * * 這就是為什麼子view對於Touch事件處理返回true那麼其上層的ViewGroup就無法處理Touch事件了!!!!!!!!!! * 這就是為什麼子view對於Touch事件處理返回false那麼其上層的ViewGroup才可以處理Touch事件!!!!!!!!!! * */ handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS); } else { /** * 情況2:mFirstTouchTarget不為null即找到了可以消費Touch事件的子View且後續Touch事件可以傳遞到該子View * 在源碼中的注釋為: * 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; //對於非ACTION_DOWN事件繼續傳遞給目標子組件進行處理,依然是遞歸調用dispatchTransformedTouchEvent() 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; } } /** * 處理ACTION_UP和ACTION_CANCEL * 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; } //=====================以上為dispatchTouchEvent()源碼分析====================== //===============以下為dispatchTransformedTouchEvent()源碼分析================= /** * 在dispatchTouchEvent()中調用dispatchTransformedTouchEvent()將事件分發給子View處理 * * Transforms a motion event into the coordinate space of a particular child view, * filters out irrelevant pointer ids, and overrides its action if necessary. * If child is null, assumes the MotionEvent will be sent to this ViewGroup instead. * * 在此請著重注意第三個參數:View child * 在dispatchTouchEvent()中多次調用了dispatchTransformedTouchEvent(),但是有時候第三個參數為null,有時又不是. * 那麼這個參數是否為null有什麼區別呢? * 在如下dispatchTransformedTouchEvent()源碼中可見多次對於child是否為null的判斷,並且均做出如下類似的操作: * if (child == null) { * handled = super.dispatchTouchEvent(event); * } else { * handled = child.dispatchTouchEvent(event); * } * 這個代碼是什麼意思呢?? * 當child == null時會將Touch事件傳遞給該ViewGroup自身的dispatchTouchEvent()處理. * 即super.dispatchTouchEvent(event)正如源碼中的注釋描述的一樣: * If child is null, assumes the MotionEvent will be sent to this ViewGroup instead. * 當child != null時會調用該子view(當然該view可能是一個View也可能是一個ViewGroup)的dispatchTouchEvent(event)處理. * 即child.dispatchTouchEvent(event); * * */ 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; } }
https://github.com/daimajia/AndroidViewHover import android.support.v8.renderscrip
前言如下圖所示,這篇文章要完成的就是這個簡單的示例,後續會繼續添加上動畫和聲音。這裡主要包含了游戲的一些簡單元素和邏輯。在我的多次嘗試後發現想贏它還是挺難的&hellip
今天調試一個bug的時候,情景如下:一個Activity A,需要用startActivityForResult方法開啟Activity B。Activity B的lau
今天帶來的是兩列並排ListView關聯滑動,這裡面有兩個知識點:1、兩個ListView如何並列顯示。2、如何關聯滑動。 第一個問題,好像我之前的博客提到過,就是讓Li