研究Android的事件分發(fā)的原理

帶著幾個問題去研究一下android的事件分發(fā)原理十性,這里只研究事件的分發(fā)機制

1.activity和View樹的事件關系和View樹的事件是從什么地方開始分發(fā)的足丢?
2.android是怎么將事件分發(fā)給對應的View上的梭姓?

activity和View樹的事件關系?
public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();
    }
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    return onTouchEvent(ev);
}

這個dispatchTouchEvent是activity實現Window的Callback接口的方法灾螃,這個是事件的入口處匹舞,activity最先接到系統(tǒng)的事件。
調用getWindow().superDispatchTouchEvent(ev)將事件傳給Window似芝。而android的Window的實現類其實就是PhoneWindow那婉,
這行代碼實際上就是調用PhoneWindow.superDispatchTouchEvent(ev)

@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
    return mDecor.superDispatchTouchEvent(event);
}

PhoneWindow直接調用DecorView.superDispatchTouchEvent(event)。事件開始進入View樹

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    final Window.Callback cb = mWindow.getCallback();
    return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
            ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
}

debug的堆棧調用循序

[圖片上傳失敗...(image-e46aeb-1510570916085)]

android是怎么將事件分發(fā)給對應的View上的党瓮?

android 的事件分發(fā)的核心代碼在ViewGroup.dispatchTouchEvent(ev),該方法重寫了View的dispatchTouchEvent

view的dispatchTouchEvent

public boolean dispatchTouchEvent(MotionEvent event) {
    // If the event should be handled by accessibility focus first.
    if (event.isTargetAccessibilityFocus()) {
        // We don't have focus or no virtual descendant has it, do not handle the event.
        if (!isAccessibilityFocusedViewOrHost()) {
            return false;
        }
        // We have focus and got the event, then use normal event dispatch.
        event.setTargetAccessibilityFocus(false);
    }

    boolean result = false;

    if (mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onTouchEvent(event, 0);
    }

    final int actionMasked = event.getActionMasked();
    if (actionMasked == MotionEvent.ACTION_DOWN) {
        // Defensive cleanup for new gesture
        stopNestedScroll();
    }

    if (onFilterTouchEventForSecurity(event)) {
        if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
            result = true;
        }
        //noinspection SimplifiableIfStatement
        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;
        }
    }

    if (!result && mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
    }

    // Clean up after nested scrolls if this is the end of a gesture;
    // also cancel it if we tried an ACTION_DOWN but we didn't want the rest
    // of the gesture.
    if (actionMasked == MotionEvent.ACTION_UP ||
            actionMasked == MotionEvent.ACTION_CANCEL ||
            (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
        stopNestedScroll();
    }

    return result;
}

這個方法比較簡單详炬,去掉處理內部滑動的部分,改代碼主要是將事件分發(fā)給自己的mOnTouchListener和onTouchEvent
記住View.dispatchTouchEvent(ev)是將事件分發(fā)給自己的

接下來是ViewGroup的dispatchTouchEvent

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

    boolean handled = false;
    if (onFilterTouchEventForSecurity(ev)) {
        final int action = ev.getAction();
        final int actionMasked = action & MotionEvent.ACTION_MASK;

        // Handle an initial down.
        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;
        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;

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

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

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

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

        // Dispatch to touch targets.
        if (mFirstTouchTarget == null) {
            // 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;
                    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;
}

這段代碼可以說的上是非常的復雜寞奸,但是系統(tǒng)的有注釋痕寓,看著注釋可以明白一二。我是從SDK26復制的蝇闭,不同的SDK代碼可能不一樣呻率,但是總體差不多。
主要用一下幾點總結這個方法

1. 通過onFilterTouchEventForSecurity判斷當前View是不是被遮擋住
2. 判斷是否攔截該次事件呻引。如果攔截intercepted=true 不攔截intercepted=false礼仗。

攔截的判斷條件是
1.如果是為down事件,mFirstTouchTarget有值,就會攔截,會走onInterceptTouchEvent方法(這里前提是設置允許事件攔截)元践。mFirstTouchTarget會在down事件的時候就會由這個方法來確定是他的哪一個Child
來接收事件韭脊,mFirstTouchTarget就是接收事件的Child的一個緩存類,mFirstTouchTarget持有接收事件的Child单旁。

2.如果沒有Child沪羔,mFirstTouchTarget等于null,就會直接將intercepted設置為true象浑,同時不回走onInterceptTouchEvent方法蔫饰。

3.mFirstTouchTarget等于null,不回攔截愉豺。

3.從child當中尋找TouchTarget

1.if (!canceled && !intercepted)這個塊中整個就是尋找接收事件的Child的篓吁,然后給mFirstTouchTarget賦值。

尋找child就要解決當child有重疊的時候蚪拦,總是要返回最上面的一個child來接受事件杖剪,android的實現是用倒序遍歷,當找到第一個的時候就是最上面一個

2.dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,View child, int desiredPointerIdBits) 這個方法負責將事件分發(fā)下去驰贷。

在這時第三個參數child就是要接受事件的子view盛嘿,最終掉用child的dispatchTouchEvent(),這個方法要求一個返回值handle括袒,

而這個handle的返回值取決于child的dispatchTouchEvent()次兆。假設child的dispatchTouchEvent()返回的是true,就會執(zhí)行if中的語句箱熬,

這里是確定了child要處理該次事件类垦,并給mFirstTouchTarget賦值狈邑,接下來MOVE事件也會因為mFirstTouchTarget會繼續(xù)分發(fā)給這個View城须。

child的dispatchTouchEvent()返回的是false,在第一次分發(fā)給這個Child后,沒有給mFirstTouchTarget賦值米苹,接下來因為mFirstTouchTarget=null,
沒有消費事件的對象糕伐,不會在分發(fā)下去。

4.真正開始分發(fā)事件

前面都是為了找到mFirstTouchTarget蘸嘶,根據mFirstTouchTarget是否有值良瞧,決定分發(fā)給自己還是Child

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市训唱,隨后出現的幾起案子褥蚯,更是在濱河造成了極大的恐慌,老刑警劉巖况增,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件赞庶,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機歧强,發(fā)現死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進店門澜薄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人摊册,你說我怎么就攤上這事肤京。” “怎么了茅特?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵忘分,是天一觀的道長。 經常有香客問我温治,道長饭庞,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任熬荆,我火速辦了婚禮舟山,結果婚禮上,老公的妹妹穿的比我還像新娘卤恳。我一直安慰自己累盗,他們只是感情好,可當我...
    茶點故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布突琳。 她就那樣靜靜地躺著若债,像睡著了一般。 火紅的嫁衣襯著肌膚如雪拆融。 梳的紋絲不亂的頭發(fā)上蠢琳,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天,我揣著相機與錄音镜豹,去河邊找鬼傲须。 笑死,一個胖子當著我的面吹牛趟脂,可吹牛的內容都是我干的泰讽。 我是一名探鬼主播,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼昔期,長吁一口氣:“原來是場噩夢啊……” “哼已卸!你這毒婦竟也來了?” 一聲冷哼從身側響起硼一,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤累澡,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后般贼,有當地人在樹林里發(fā)現了一具尸體愧哟,經...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡惑申,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了翅雏。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片圈驼。...
    茶點故事閱讀 39,841評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖望几,靈堂內的尸體忽然破棺而出绩脆,到底是詐尸還是另有隱情,我是刑警寧澤橄抹,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布靴迫,位于F島的核電站,受9級特大地震影響楼誓,放射性物質發(fā)生泄漏玉锌。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一疟羹、第九天 我趴在偏房一處隱蔽的房頂上張望主守。 院中可真熱鬧,春花似錦榄融、人聲如沸参淫。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽涎才。三九已至,卻和暖如春力九,著一層夾襖步出監(jiān)牢的瞬間耍铜,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工跌前, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留棕兼,地道東北人。 一個月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓舒萎,卻偏偏與公主長得像程储,于是被迫代替她去往敵國和親蹭沛。 傳聞我的和親對象是個殘疾皇子臂寝,可洞房花燭夜當晚...
    茶點故事閱讀 44,781評論 2 354

推薦閱讀更多精彩內容