1. 程式人生 > >android SDK-25事件分發機制--原始碼正確解析

android SDK-25事件分發機制--原始碼正確解析

android SDK-25事件分發機制–原始碼正確解析

Android 事件分發分為View和ViewGroup的事件分發,ViewGroup比View過一個攔截判斷,viewgroup可以攔截事件,從而決定要不要把事件傳遞給子view,因為view沒有子view所以不存在攔截事件的情況。

事件分發主要從事件的分發,攔截,和處理三個函式的呼叫邏輯關係來分析。

public boolean dispatchTouchEvent(MotionEvent event) {

}

public boolean onInterceptTouchEvent(MotionEvent ev) {

}

public boolean onTouchEvent(MotionEvent event) {

}

首先,螢幕上面一個點選事件,通過感測器捕獲到點選,然後知道把點選事件傳遞到activity 到PhoneWindow,再到,DecorView 最後就到我們自己在佈局檔案中的view或者viewgroup。

下面分析dispatchTouchEvent原始碼 :(sdk-25)(==>這個標記為重點,不用全懂,只要把==>這個標記處的邏輯理清楚就行)

 @Override
public boolean dispatchTouchEvent(MotionEvent ev) {

    //檢驗是不是利用輔助功能點選事件,根本不用管 
    if (mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
    }

    // If the event targets the accessibility focused view and this is it, start
    // normal event dispatch. Maybe a descendant is what will handle the click.
    if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
        ev.setTargetAccessibilityFocus(false);
    }
    //輔助功能點選事件的判斷和處理根本不用管,直接不要管,跳轉到下一個重點

    // ==> 一個標誌,初始值為不處理,意識就是不處理MotionEvent
    boolean handled = false;
    if (onFilterTouchEventForSecurity(ev)) {
        final int action = ev.getAction();
        final int actionMasked = action & MotionEvent.ACTION_MASK;

        // Handle an initial down.
        // ==> 處理第一個點選事件(事件分為,點選事件,move事件,up事件,所以點選事件是第一個事件)
        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.
        final boolean intercepted;
        // ==> 判斷要不要打斷,如果不是點選事件,並且mFirstTouchTarget為null,則該viewgroup則打斷攔截事件。
        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 {
            // There are no touch targets and this action is not an initial down
            // so this view group continues to intercept touches.
            intercepted = true;
        }

        // If intercepted, start normal event dispatch. Also if there is already
        // a view that is handling the gesture, do normal event dispatch.
        if (intercepted || mFirstTouchTarget != null) {
            ev.setTargetAccessibilityFocus(false);
        }

        // 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;
         // ==>如果沒有返回並且沒有被攔截,則進行分發
        if (!canceled && !intercepted) {

            // If the event is targeting accessiiblity focus we give it to the
            // view that has accessibility focus and if it does not handle it
            // we clear the flag and dispatch the event to all children as usual.
            // We are looking up the accessibility focused host to avoid keeping
            // state since these events are very rare.
            View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
                    ? findChildWithAccessibilityFocus() : null;
            // ==>如果是down事件,進行遍歷子view,並且把事件分發給子view,前提是down事件的坐
            標必須在子view中等等條件
            ==>這句話,請把下面的原始碼分析完再一定回來看一看,你要思考一下,這裡如果是down事件才
            會進去,如果是move事件那麼不會進去分發了,其實往後看原始碼會發現,在down事件分發給
            一個child,如果這個child消費了這個down事件,那麼這個child就會被儲存起來,以後的
            move(可能0到多次move),up都會直接分發給這個child,就不用每次再去遍歷所有的Child
            這些效率就提高和很多,但是你也會想到,如果一個down事件被某個child消費只有,其它
            child就無法被分發事件了,除非我們手動呼叫child的分發方法,或者打斷事件序列,從發分發一個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 (newTouchTarget == null && childrenCount != 0) {
                    final float x = ev.getX(actionIndex);
                    final float y = ev.getY(actionIndex);
                    // Find a child that can receive the event.
                    // Scan children from front to back.
                    final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                    final boolean customOrder = preorderedList == null
                            && isChildrenDrawingOrderEnabled();
                    final View[] children = mChildren;
                    for (int i = childrenCount - 1; i >= 0; i--) {
                        final int childIndex = getAndVerifyPreorderedIndex(
                                childrenCount, i, customOrder);
                        final View child = getAndVerifyPreorderedView(
                                preorderedList, children, childIndex);

                        // If there is a view that has accessibility focus we want it
                        // to get the event first and if not handled we will perform a
                        // normal dispatch. We may do a double iteration but this is
                        // safer given the timeframe.
                        if (childWithAccessibilityFocus != null) {
                            if (childWithAccessibilityFocus != child) {
                                continue;
                            }
                            childWithAccessibilityFocus = null;
                            i = childrenCount - 1;
                        }
                    // ==>判斷這個子view能否接受點選事件和子view是否包含這個點選的座標,具體的判斷點選這兩個方法進去看
                        if (!canViewReceivePointerEvents(child)
                                || !isTransformedTouchPointInView(x, y, child, null)) {
                            ev.setTargetAccessibilityFocus(false);
                            continue;
                        }

                        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;
                        }

                        resetCancelNextUpFlag(child);
                    // ==>  關鍵之處,這裡將事件傳遞給child進行處理了,馬上進入這個方法看看吧
                        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) 

// ==>dispatchTransformedTouchEvent方法

 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) {
    // ==>如果child為空,直接呼叫該viewgroup自己父類的dispatchTouchEvent,也就是viewdispatchTouchEvent方法,點選進去會發現它會呼叫onTouchEvent,也就是說如果viewGroup如果沒有child那麼他就會呼叫自己的onTouchEvent方法來消費這個事件,這個handle就表明了這個事件viewgroup自己是否消費
        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());
        }
        // ==>我們的child不為空的時候,就呼叫child事件分發方法,於是到這裡可以看到事件的傳遞了,先來分析簡單的情況,如果child是view不是viewgroup,那麼dispatchTouchEvent流程就簡單了,會呼叫onTouchEvent來告訴父view,child它是否消費父親分發給他的事件,這個handle就表明了這個事件clild是否消費
        handled = child.dispatchTouchEvent(transformedEvent);

    }

    // Done.
    transformedEvent.recycle();
    return handled;
}

//這個重要的方法分析完之後,馬上返回剛才原始碼的地方,繼續。。。

//如果剛才,我們的child不為空並且child消費down事件,那麼就很棒了,說明有child處理事件了

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();
            // ==>重點,從上面的if條件可以看出,有child消費down事件才會執行到這裡,這個方法點
        進去發現mFirstTouchTarget = target;     
        這句程式碼,很重要哦。很明顯就是把消費down事件的child賦值給mFirstTouchTarget,從而儲存起來
                            newTouchTarget = addTouchTarget(child, idBitsToAssign);
            // ==> alreadyDispatchedToNewTouchTarget = true; 已經把事件分發給新的觸控目標
                下面在分發的時候會用到
                            alreadyDispatchedToNewTouchTarget = true;
                            break;
                        }

                        // The accessibility focus didn't handle the event, so clear
                        // the flag and do a normal dispatch to all children.
                        ev.setTargetAccessibilityFocus(false);
                    }
                    if (preorderedList != null) preorderedList.clear();
                }
            //這裡,如果是down事件,並且mFirstTouchTarget != null則加入連結串列newTouchTarget,這裡不要管
                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.pointerIdBits |= idBitsToAssign;
                }
            }
        }
        // ==> 由於剛才mFirstTouchTarget被賦值為訊息了down事件的child,所以不為空了
        // Dispatch to touch targets.
        if (mFirstTouchTarget == null) {
        // 這裡非常重要,如果child為空,表明viewgroup在分發事件給child的dispatchTouchEvent被返回了false,說明子view都不消費down事件,那麼這裡會呼叫dispatchTransformedTouchEvent這個重要的方法,點進去就會發現,當child為null的時候,會呼叫viewgroup自己的父類View的dispatchTouchEvent,從而呼叫onTouchEvent,就是說兒子不消費,給老子再看看要不要消費。
            // No touch targets so treat this as an ordinary view.
            handled = dispatchTransformedTouchEvent(ev, canceled, null,
                    TouchTarget.ALL_POINTER_IDS);
        } else {
            // 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;
        // ==>看到這裡,你是不是發現怎麼又呼叫了這個重要的,吧事件分發給child的方法,剛才不是已
            經分發了,這不是第二次又來分發嗎?,當然不會,你看前面的,第一次分發事件的時候已經講
            alreadyDispatchedToNewTouchTarget=true;了
                    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;
            }
        }

        // 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;
}

這裡寫圖片描述
總結:事件分發,viewgroup自己先判斷要不要攔截事件,和有沒有呼叫過requestDisallowInterceptTouchEvent方法來不攔截事件,如果不攔截,當down事件的時候,遍歷child,看child是否消費,child如果訊息,則被儲存下來,後面的事件就不遍歷直接分發給他,如果child不消費,那麼viewgruop繼續執行,呼叫自己onTouchEvent方法來判斷自己是不是要消費事件。