為什么Dialog彈出以后叹洲,activity就無(wú)法捕捉觸摸事件了?

前言

“當(dāng)Dialog彈出來(lái)時(shí)工禾,Dialog下面的控件能否點(diǎn)擊运提?”
“當(dāng)然是不能點(diǎn)擊啊”
“那么為什么不能點(diǎn)擊呢?”
“額闻葵,emmmm”


相信不少人對(duì)于Dialog使用駕輕就熟了民泵,對(duì)于Dialog下面的控件能否點(diǎn)擊也是非常清楚,但是當(dāng)被問(wèn)之原理時(shí)槽畔,可能有部分童鞋就答不出來(lái)了栈妆。那么這里我們就來(lái)從源碼層面上面講解下這個(gè)原因吧。
首先我們先來(lái)看看Dialog是怎么使用的:

            Dialog dialog = new Dialog(TestActivity.this);
            dialog.setContentView(R.layout.dialog_test);
            dialog.show();

那么我們看到有三步厢钧,我們一步一步講

    Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
        if (createContextThemeWrapper) {
            if (themeResId == ResourceId.ID_NULL) {
                final TypedValue outValue = new TypedValue();
                context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true);
                themeResId = outValue.resourceId;
            }
            mContext = new ContextThemeWrapper(context, themeResId);
        } else {
            mContext = context;
        }

        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        //新建PhoneWindow
        final Window w = new PhoneWindow(mContext);
        mWindow = w;
        //將回調(diào)注入到window當(dāng)中
        w.setCallback(this);
        w.setOnWindowDismissedCallback(this);
        w.setOnWindowSwipeDismissedCallback(() -> {
            if (mCancelable) {
                cancel();
            }
        });
        w.setWindowManager(mWindowManager, null, null);
        w.setGravity(Gravity.CENTER);

        mListenersHandler = new ListenersHandler(this);
    }

從代碼上面可以看出鳞尔,這里是新建了一個(gè)PhoneWindow,Dialog和View之間都是通過(guò)Window來(lái)交互早直,這一點(diǎn)跟Activity和View之間的關(guān)系也是類似寥假。

    /**
     * Set the screen content from a layout resource.  The resource will be
     * inflated, adding all top-level views to the screen.
     * 
     * @param layoutResID Resource ID to be inflated.
     */
    public void setContentView(@LayoutRes int layoutResID) {
        mWindow.setContentView(layoutResID);
    }

代碼就一行,這里我們可以看到Dialog其實(shí)把主要處理邏輯也都交給了window霞扬,這里面的window對(duì)象其實(shí)就是PhoneWindow糕韧,所以我們?nèi)タ聪翽honeWindow里面的操作:

    @Override
    public void setContentView(int layoutResID) {
        // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
        // decor, when theme attributes and the like are crystalized. Do not check the feature
        // before this happens.
        if (mContentParent == null) {
            //初始化decorView
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }
        ...代碼省略...
    }
    private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
            mDecor = generateDecor(-1);
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        } else {
            mDecor.setWindow(this);
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);

            // Set up decor part of UI to ignore fitsSystemWindows if appropriate.
            mDecor.makeOptionalFitsSystemWindows();

            final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(
                    R.id.decor_content_parent);

            if (decorContentParent != null) {
                mDecorContentParent = decorContentParent;
                mDecorContentParent.setWindowCallback(getCallback());
                if (mDecorContentParent.getTitle() == null) {
                    mDecorContentParent.setWindowTitle(mTitle);
                }

                final int localFeatures = getLocalFeatures();
                for (int i = 0; i < FEATURE_MAX; i++) {
                    if ((localFeatures & (1 << i)) != 0) {
                        mDecorContentParent.initFeature(i);
                    }
                }

                mDecorContentParent.setUiOptions(mUiOptions);

                if ((mResourcesSetFlags & FLAG_RESOURCE_SET_ICON) != 0 ||
                        (mIconRes != 0 && !mDecorContentParent.hasIcon())) {
                    mDecorContentParent.setIcon(mIconRes);
                } else if ((mResourcesSetFlags & FLAG_RESOURCE_SET_ICON) == 0 &&
                        mIconRes == 0 && !mDecorContentParent.hasIcon()) {
                    mDecorContentParent.setIcon(
                            getContext().getPackageManager().getDefaultActivityIcon());
                    mResourcesSetFlags |= FLAG_RESOURCE_SET_ICON_FALLBACK;
                }
                if ((mResourcesSetFlags & FLAG_RESOURCE_SET_LOGO) != 0 ||
                        (mLogoRes != 0 && !mDecorContentParent.hasLogo())) {
                    mDecorContentParent.setLogo(mLogoRes);
                }

                // Invalidate if the panel menu hasn't been created before this.
                // Panel menu invalidation is deferred avoiding application onCreateOptionsMenu
                // being called in the middle of onCreate or similar.
                // A pending invalidation will typically be resolved before the posted message
                // would run normally in order to satisfy instance state restoration.
                PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
                if (!isDestroyed() && (st == null || st.menu == null) && !mIsStartingWindow) {
                    invalidatePanelMenu(FEATURE_ACTION_BAR);
                }
            } else {
                mTitleView = findViewById(R.id.title);
                if (mTitleView != null) {
                    if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {
                        final View titleContainer = findViewById(R.id.title_container);
                        if (titleContainer != null) {
                            titleContainer.setVisibility(View.GONE);
                        } else {
                            mTitleView.setVisibility(View.GONE);
                        }
                        mContentParent.setForeground(null);
                    } else {
                        mTitleView.setText(mTitle);
                    }
                }
            }

            if (mDecor.getBackground() == null && mBackgroundFallbackResource != 0) {
                mDecor.setBackgroundFallback(mBackgroundFallbackResource);
            }

            // Only inflate or create a new TransitionManager if the caller hasn't
            // already set a custom one.
            if (hasFeature(FEATURE_ACTIVITY_TRANSITIONS)) {
                if (mTransitionManager == null) {
                    final int transitionRes = getWindowStyle().getResourceId(
                            R.styleable.Window_windowContentTransitionManager,
                            0);
                    if (transitionRes != 0) {
                        final TransitionInflater inflater = TransitionInflater.from(getContext());
                        mTransitionManager = inflater.inflateTransitionManager(transitionRes,
                                mContentParent);
                    } else {
                        mTransitionManager = new TransitionManager();
                    }
                }

                mEnterTransition = getTransition(mEnterTransition, null,
                        R.styleable.Window_windowEnterTransition);
                mReturnTransition = getTransition(mReturnTransition, USE_DEFAULT_TRANSITION,
                        R.styleable.Window_windowReturnTransition);
                mExitTransition = getTransition(mExitTransition, null,
                        R.styleable.Window_windowExitTransition);
                mReenterTransition = getTransition(mReenterTransition, USE_DEFAULT_TRANSITION,
                        R.styleable.Window_windowReenterTransition);
                mSharedElementEnterTransition = getTransition(mSharedElementEnterTransition, null,
                        R.styleable.Window_windowSharedElementEnterTransition);
                mSharedElementReturnTransition = getTransition(mSharedElementReturnTransition,
                        USE_DEFAULT_TRANSITION,
                        R.styleable.Window_windowSharedElementReturnTransition);
                mSharedElementExitTransition = getTransition(mSharedElementExitTransition, null,
                        R.styleable.Window_windowSharedElementExitTransition);
                mSharedElementReenterTransition = getTransition(mSharedElementReenterTransition,
                        USE_DEFAULT_TRANSITION,
                        R.styleable.Window_windowSharedElementReenterTransition);
                if (mAllowEnterTransitionOverlap == null) {
                    mAllowEnterTransitionOverlap = getWindowStyle().getBoolean(
                            R.styleable.Window_windowAllowEnterTransitionOverlap, true);
                }
                if (mAllowReturnTransitionOverlap == null) {
                    mAllowReturnTransitionOverlap = getWindowStyle().getBoolean(
                            R.styleable.Window_windowAllowReturnTransitionOverlap, true);
                }
                if (mBackgroundFadeDurationMillis < 0) {
                    mBackgroundFadeDurationMillis = getWindowStyle().getInteger(
                            R.styleable.Window_windowTransitionBackgroundFadeDuration,
                            DEFAULT_BACKGROUND_FADE_DURATION_MS);
                }
                if (mSharedElementsUseOverlay == null) {
                    mSharedElementsUseOverlay = getWindowStyle().getBoolean(
                            R.styleable.Window_windowSharedElementsUseOverlay, true);
                }
            }
        }
    }

重點(diǎn)關(guān)注installDecor()。window都會(huì)先初始化一個(gè)DecorView喻圃,然后把我們setContentView進(jìn)來(lái)的View給添加到這個(gè)對(duì)應(yīng)的DecorView里面去(Activity里面的Window操作亦是如此)萤彩。所以這里先初始化了一個(gè)DecorView。然后將window注入到decorView當(dāng)中去斧拍。

    /**
     * Start the dialog and display it on screen.  The window is placed in the
     * application layer and opaque.  Note that you should not override this
     * method to do initialization when the dialog is shown, instead implement
     * that in {@link #onStart}.
     */
    public void show() {
        ...代碼省略...

        mWindowManager.addView(mDecor, l);
      ...代碼省略
    }

這里通過(guò)windowManager將decorView添加到window上面去雀扶,此處WindowManager是WindowManagerImpl,然后又執(zhí)行WindowManagerGlobal的addView方法。

    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
             ...代碼省略...

            root = new ViewRootImpl(view.getContext(), display);

            view.setLayoutParams(wparams);

            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);

            // do this last because it fires off messages to start doing things
            try {
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
        }
    }

關(guān)鍵一步在這里饮焦,我們可以看到new了一個(gè)ViewRootImpl的對(duì)象怕吴,并且將DecorView注入進(jìn)去,賦值給mView县踢。
通過(guò)原來(lái)Android觸控機(jī)制竟是這樣的?這篇文章伟件,這才找到了源頭硼啤,所有的點(diǎn)擊事件通過(guò)硬件設(shè)備檢測(cè),經(jīng)過(guò)底層會(huì)調(diào)用到ViewRootImpl的ViewPostImeInputStage里面來(lái)斧账。但是因?yàn)閂iewRootImpl存在著多個(gè)谴返,到底是哪個(gè)會(huì)接收到回調(diào)呢煞肾,十分鐘了解Android觸摸事件原理(InputManagerService)告訴我們會(huì)根據(jù)Z軸的高度,獲取最近一個(gè)窗口嗓袱,然后執(zhí)行對(duì)應(yīng)ViewRootImpl里面ViewPostImeInputStage的監(jiān)聽方法->執(zhí)行mView.dispatchPointerEvent(event)籍救。
此處的mView便是Dialog所在的DecorView,然后才開始由decorView進(jìn)行事件分發(fā),我們先看View的dispatchPointerEvent方法:

    /**
     * Dispatch a pointer event.
     * <p>
     * Dispatches touch related pointer events to {@link #onTouchEvent(MotionEvent)} and all
     * other events to {@link #onGenericMotionEvent(MotionEvent)}.  This separation of concerns
     * reinforces the invariant that {@link #onTouchEvent(MotionEvent)} is really about touches
     * and should not be expected to handle other pointing device features.
     * </p>
     *
     * @param event The motion event to be dispatched.
     * @return True if the event was handled by the view, false otherwise.
     * @hide
     */
    public final boolean dispatchPointerEvent(MotionEvent event) {
        if (event.isTouchEvent()) {
            return dispatchTouchEvent(event);
        } else {
            return dispatchGenericMotionEvent(event);
        }
    }

這里會(huì)執(zhí)行decorView的dispatchTouchEvent:

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

因?yàn)楫?dāng)前decorView注入的窗口是在創(chuàng)建dialog時(shí)新建的渠抹,另外dialog本身就是實(shí)現(xiàn)了Window.Callback接口蝙昙,第一步當(dāng)中已經(jīng)將dialog注入到window當(dāng)中了,因此此處的cb對(duì)象就是dialog對(duì)象梧却,這時(shí)就直接調(diào)用了dialog的dispatchTouchEvent方法奇颠。接下來(lái)就是熟知的事件分發(fā)流程了。此處便不再多做介紹放航。


結(jié)論:

Activity的dispatchTouchEvent也是從對(duì)應(yīng)的decorView的dispatchTouchEvent中分發(fā)出來(lái)的烈拒,而Activity所處的decorView跟Dialog所處的decorView并不屬于同一個(gè),所以Activity自然接收不到任何點(diǎn)擊事件广鳍。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末荆几,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子赊时,更是在濱河造成了極大的恐慌吨铸,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,744評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蛋叼,死亡現(xiàn)場(chǎng)離奇詭異焊傅,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)狈涮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,505評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門狐胎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人歌馍,你說(shuō)我怎么就攤上這事握巢。” “怎么了松却?”我有些...
    開封第一講書人閱讀 163,105評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵暴浦,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我晓锻,道長(zhǎng)歌焦,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,242評(píng)論 1 292
  • 正文 為了忘掉前任砚哆,我火速辦了婚禮独撇,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己纷铣,他們只是感情好卵史,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,269評(píng)論 6 389
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著搜立,像睡著了一般以躯。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上啄踊,一...
    開封第一講書人閱讀 51,215評(píng)論 1 299
  • 那天忧设,我揣著相機(jī)與錄音,去河邊找鬼社痛。 笑死见转,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的蒜哀。 我是一名探鬼主播斩箫,決...
    沈念sama閱讀 40,096評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼撵儿!你這毒婦竟也來(lái)了乘客?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,939評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤淀歇,失蹤者是張志新(化名)和其女友劉穎易核,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體浪默,經(jīng)...
    沈念sama閱讀 45,354評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡牡直,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,573評(píng)論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了纳决。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片碰逸。...
    茶點(diǎn)故事閱讀 39,745評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖阔加,靈堂內(nèi)的尸體忽然破棺而出饵史,到底是詐尸還是另有隱情,我是刑警寧澤胜榔,帶...
    沈念sama閱讀 35,448評(píng)論 5 344
  • 正文 年R本政府宣布胳喷,位于F島的核電站,受9級(jí)特大地震影響夭织,放射性物質(zhì)發(fā)生泄漏吭露。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,048評(píng)論 3 327
  • 文/蒙蒙 一尊惰、第九天 我趴在偏房一處隱蔽的房頂上張望奴饮。 院中可真熱鬧纬向,春花似錦择浊、人聲如沸戴卜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,683評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)投剥。三九已至,卻和暖如春担孔,著一層夾襖步出監(jiān)牢的瞬間江锨,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,838評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工糕篇, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留啄育,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,776評(píng)論 2 369
  • 正文 我出身青樓拌消,卻偏偏與公主長(zhǎng)得像挑豌,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子墩崩,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,652評(píng)論 2 354

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