Android View的事件傳遞機(jī)制

事件傳遞在android當(dāng)中是很重要的一部分歌溉,所以也看了很多別人的講解菠剩,今天就把我自己的理解寫一下
事件傳遞涉及到的東西有

1.兩個(gè)類 View ViewGroup

2.三個(gè)方法 dispatchTouchEvent()水评,onInterceptTouchEvent(),onTouchEvent()

3.三種返回值 true,false,super()

其中View沒有onInterceptTouchEvent()方法

首先看一個(gè)經(jīng)常遇到的布局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="50dp"
    android:background="@color/colorPrimaryDark"
    tools:context="com.zhoufazhan.ViewActivity">

    <LinearLayout
        android:id="@+id/linear"
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:background="@color/colorAccent"
        >

        <Button
            android:id="@+id/button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Button1" />
    </LinearLayout>
</LinearLayout>

LinearLayout嵌套了一個(gè)LinearLayout最里面放一個(gè)Button滚粟。此時(shí)點(diǎn)擊Button按鈕憎亚,默認(rèn)情況下會(huì)調(diào)用前面所提到的3個(gè)方法匆浙,那么是怎么調(diào)用的安寺,調(diào)用的順序是什么,請(qǐng)看下圖(圖是自己畫的首尼,不一定好看挑庶,但一定能說明問題哦)

事件流程圖

第一眼看上去是不是有點(diǎn)眼花繚亂的,不急软能,我來給你理清楚迎捺。

首先重上往下看有三層結(jié)構(gòu): Activity,ViewGroup,View
ViewGroup對(duì)應(yīng)上面的一個(gè)LinearLayout(便于講解我這里只畫了一個(gè)ViewGroup)
View對(duì)應(yīng)Button查排,Activity為系統(tǒng)的布局凳枝,這里不做過多的說明
ViewGroup包含三個(gè)方法,Activity雹嗦,View包含兩個(gè)方法范舀。

我們假設(shè)從點(diǎn)擊開始什么都不做(也就是所有的返回值都是super),從流程圖可以看出依次調(diào)用了罪,Activity锭环,ViewGroup,View的dispatchTouchEvent()方法泊藕,然后在返回依次調(diào)用View,ViewGroup,Activity的onTouchEvent()方法辅辩,這就是人們經(jīng)常說的U型圖。
下面再來詳細(xì)的說一下各個(gè)方法有什么作用。
這里我們只討論ViewGroup和View玫锋,也是我們經(jīng)常用的蛾茉,以及Action_down事件

dispatchTouchEvent()字面就能理解用處是事件的分發(fā),所有View的點(diǎn)擊或是滑動(dòng)首先調(diào)用這個(gè)方法撩鹿,具體是怎么分發(fā)的由它的返回值確定

當(dāng)返回true時(shí)谦炬,事件到此結(jié)束,它自己消費(fèi)掉了节沦,那么button按鈕將不會(huì)有任何反應(yīng)

當(dāng)返回false時(shí)键思,意思就是說告訴以后的事件,到我這里不在分發(fā)了甫贯,直接返回到上一級(jí)吼鳞,調(diào)用上級(jí)的
onTouchEvent,自己不消費(fèi)也不分發(fā)給下面的(站著茅坑..........),所以點(diǎn)擊button不會(huì)有任何反應(yīng)

那么我點(diǎn)擊button為什么會(huì)有反應(yīng)呢叫搁,因?yàn)閂iewGroup默認(rèn)會(huì)調(diào)用super()方法赔桌,注意當(dāng)ViewGroup調(diào)用super后還會(huì)調(diào)用自己的onInterceptTouchEvent(),詢問一下自己是否需要攔截事件,當(dāng)然默認(rèn)是不會(huì)攔截的也就是返回super或是false渴逻,要是你就不想把事件傳遞給button就返回true疾党,告訴ViewGroup攔截事件,然后就會(huì)調(diào)用自己的onTouchEvent
假設(shè)沒有攔截裸卫,就會(huì)來到我們期待已久的View(Button),button都等急死了仿贬,經(jīng)過了這么多攔截終于到自己了。
這時(shí)View也會(huì)和ViewGroup一樣只是沒有了攔截(onInterceptTouchEvent墓贿,因?yàn)樽约壕褪亲詈笠粚恿思肜幔劜簧蠑r截誰,只有被別人攔截的份聋袋,可憐岸游啊)

到此dispatchTouchEvent方法傳遞到底了,該輪到onTouchEvent從底部向上傳遞(如果順利的話)

圖中我們可以清晰的看到onTouchEvent返回true的時(shí)候事件就被消費(fèi)了幽勒。

那么問題來了當(dāng)我們給Button設(shè)置點(diǎn)擊事件的時(shí)候View是怎么處理的嗜侮,來看看源碼一探究竟。

首先調(diào)用的是Button的dispatchTouchEvent啥容,Button的父類TextView木有這個(gè)方法锈颗,在向上找找看,終于在View里面找到了咪惠,代碼如下击吱,不是很長(zhǎng)

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

里面定義了一個(gè)變量result,我們只關(guān)心有返回值的代碼遥昧,這里我用虛線框起來了覆醇,請(qǐng)注意虛線里面的第一個(gè)if判斷里面的mOnTouchListener朵纷,這里就是我們給Button設(shè)置的

button.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                return false;
            }
        });

在第二個(gè)if判斷里面會(huì)調(diào)用自己的onTouchEvent,(跟我們之前分析的一樣),到這里你就知道了onTouch和onTouchEvent誰先調(diào)用了吧永脓,如果onTouch返回true將不會(huì)再調(diào)用onTouchEvent袍辞,如果我們沒有設(shè)置button.setOnTouchListener而是只設(shè)置了button.setOnClickListener,將會(huì)調(diào)用onTouchEvent常摧,再來看看里面做了什么

 public boolean onTouchEvent(MotionEvent event) {
        final float x = event.getX();
        final float y = event.getY();
        final int viewFlags = mViewFlags;
        final int action = event.getAction();

        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                setPressed(false);
            }
            // A disabled view that is clickable still consumes the touch
            // events, it just doesn't respond to them.
            return (((viewFlags & CLICKABLE) == CLICKABLE
                    || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                    || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
        }
        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }

        if (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
                (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
            switch (action) {
                case MotionEvent.ACTION_UP:
                    boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                        // take focus if we don't have it already and we should in
                        // touch mode.
                        boolean focusTaken = false;
                        if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                            focusTaken = requestFocus();
                        }

                        if (prepressed) {
                            // The button is being released before we actually
                            // showed it as pressed.  Make it show the pressed
                            // state now (before scheduling the click) to ensure
                            // the user sees it.
                            setPressed(true, x, y);
                       }

                        if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                            // This is a tap, so remove the longpress check
                            removeLongPressCallback();

                            // Only perform take click actions if we were in the pressed state
                            if (!focusTaken) {
                                // Use a Runnable and post this rather than calling
                                // performClick directly. This lets other visual state
                                // of the view update before click actions start.

//----------------------------------------------------------------------------------------------------------------------------
                                if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }
                                if (!post(mPerformClick)) {
                                    performClick();
                                }
                            }
                        }

                        if (mUnsetPressedState == null) {
                            mUnsetPressedState = new UnsetPressedState();
                        }

                        if (prepressed) {
                            postDelayed(mUnsetPressedState,
                                    ViewConfiguration.getPressedStateDuration());
                        } else if (!post(mUnsetPressedState)) {
                            // If the post failed, unpress right now
                            mUnsetPressedState.run();
                        }

                        removeTapCallback();
                    }
                    mIgnoreNextUpEvent = false;
                    break;

                case MotionEvent.ACTION_DOWN:
                    mHasPerformedLongPress = false;

                    if (performButtonActionOnTouchDown(event)) {
                        break;
                    }

                    // Walk up the hierarchy to determine if we're inside a scrolling container.
                    boolean isInScrollingContainer = isInScrollingContainer();

                    // For views inside a scrolling container, delay the pressed feedback for
                    // a short period in case this is a scroll.
                    if (isInScrollingContainer) {
                        mPrivateFlags |= PFLAG_PREPRESSED;
                        if (mPendingCheckForTap == null) {
                            mPendingCheckForTap = new CheckForTap();
                        }
                        mPendingCheckForTap.x = event.getX();
                        mPendingCheckForTap.y = event.getY();
                        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                    } else {
                        // Not inside a scrolling container, so show the feedback right away
                        setPressed(true, x, y);
                        checkForLongClick(0, x, y);
                    }
                    break;

                case MotionEvent.ACTION_CANCEL:
                    setPressed(false);
                    removeTapCallback();
                    removeLongPressCallback();
                    mInContextButtonPress = false;
                    mHasPerformedLongPress = false;
                    mIgnoreNextUpEvent = false;
                    break;

                case MotionEvent.ACTION_MOVE:
                    drawableHotspotChanged(x, y);

                    // Be lenient about moving outside of buttons
                    if (!pointInView(x, y, mTouchSlop)) {
                        // Outside button
                        removeTapCallback();
                        if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                            // Remove any future long press/tap checks
                            removeLongPressCallback();

                            setPressed(false);
                        }
                    }
                    break;
            }

            return true;
        }

        return false;
    }

代碼有點(diǎn)多搅吁,我們只看看虛線下面兩行,里面有PerformClick排宰,點(diǎn)進(jìn)去看看

public boolean performClick() {
        final boolean result;
        final ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            li.mOnClickListener.onClick(this);
            result = true;
        } else {
            result = false;
        }

        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
        return result;
    }

看到mOnClickListener了嗎似芝,這就是我們?cè)O(shè)置的點(diǎn)擊事件那婉,在這里返回true板甘,也就是onTouchEvent返回true,點(diǎn)擊事件被消費(fèi)了详炬,好了就說這么多了盐类。

搞清楚事件的傳遞機(jī)制有助于我們很多問題,比如滑動(dòng)的沖突呛谜,點(diǎn)擊事件的獲取

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末在跳,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子隐岛,更是在濱河造成了極大的恐慌猫妙,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,734評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件聚凹,死亡現(xiàn)場(chǎng)離奇詭異割坠,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)妒牙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門彼哼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人湘今,你說我怎么就攤上這事敢朱。” “怎么了摩瞎?”我有些...
    開封第一講書人閱讀 164,133評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵拴签,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我旗们,道長(zhǎng)蚓哩,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,532評(píng)論 1 293
  • 正文 為了忘掉前任蚪拦,我火速辦了婚禮杖剪,結(jié)果婚禮上冻押,老公的妹妹穿的比我還像新娘。我一直安慰自己盛嘿,他們只是感情好洛巢,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著次兆,像睡著了一般稿茉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上芥炭,一...
    開封第一講書人閱讀 51,462評(píng)論 1 302
  • 那天漓库,我揣著相機(jī)與錄音,去河邊找鬼园蝠。 笑死渺蒿,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的彪薛。 我是一名探鬼主播茂装,決...
    沈念sama閱讀 40,262評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼善延!你這毒婦竟也來了少态?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,153評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤易遣,失蹤者是張志新(化名)和其女友劉穎彼妻,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體豆茫,經(jīng)...
    沈念sama閱讀 45,587評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡侨歉,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了澜薄。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片为肮。...
    茶點(diǎn)故事閱讀 39,919評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖肤京,靈堂內(nèi)的尸體忽然破棺而出颊艳,到底是詐尸還是另有隱情,我是刑警寧澤忘分,帶...
    沈念sama閱讀 35,635評(píng)論 5 345
  • 正文 年R本政府宣布棋枕,位于F島的核電站,受9級(jí)特大地震影響妒峦,放射性物質(zhì)發(fā)生泄漏重斑。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評(píng)論 3 329
  • 文/蒙蒙 一肯骇、第九天 我趴在偏房一處隱蔽的房頂上張望窥浪。 院中可真熱鬧祖很,春花似錦、人聲如沸漾脂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽骨稿。三九已至笨鸡,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間坦冠,已是汗流浹背形耗。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留辙浑,地道東北人激涤。 一個(gè)月前我還...
    沈念sama閱讀 48,048評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像例衍,于是被迫代替她去往敵國(guó)和親昔期。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容