美文网首页Android精选Android面试
为什么Dialog弹出以后,activity就无法捕捉触摸事件了

为什么Dialog弹出以后,activity就无法捕捉触摸事件了

作者: CDF_cc7d | 来源:发表于2018-12-12 21:40 被阅读103次

    前言

    “当Dialog弹出来时,Dialog下面的控件能否点击?”
    “当然是不能点击啊”
    “那么为什么不能点击呢?”
    “额,emmmm”


    相信不少人对于Dialog使用驾轻就熟了,对于Dialog下面的控件能否点击也是非常清楚,但是当被问之原理时,可能有部分童鞋就答不出来了。那么这里我们就来从源码层面上面讲解下这个原因吧。
    首先我们先来看看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;
            //将回调注入到window当中
            w.setCallback(this);
            w.setOnWindowDismissedCallback(this);
            w.setOnWindowSwipeDismissedCallback(() -> {
                if (mCancelable) {
                    cancel();
                }
            });
            w.setWindowManager(mWindowManager, null, null);
            w.setGravity(Gravity.CENTER);
    
            mListenersHandler = new ListenersHandler(this);
        }
    

    从代码上面可以看出,这里是新建了一个PhoneWindow,Dialog和View之间都是通过Window来交互,这一点跟Activity和View之间的关系也是类似。

        /**
         * 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其实把主要处理逻辑也都交给了window,这里面的window对象其实就是PhoneWindow,所以我们去看下PhoneWindow里面的操作:

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

    重点关注installDecor()。window都会先初始化一个DecorView,然后把我们setContentView进来的View给添加到这个对应的DecorView里面去(Activity里面的Window操作亦是如此)。所以这里先初始化了一个DecorView。然后将window注入到decorView当中去。

        /**
         * 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);
          ...代码省略
        }
    

    这里通过windowManager将decorView添加到window上面去,此处WindowManager是WindowManagerImpl,然后又执行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;
                }
            }
        }
    

    关键一步在这里,我们可以看到new了一个ViewRootImpl的对象,并且将DecorView注入进去,赋值给mView。
    通过原来Android触控机制竟是这样的?这篇文章,这才找到了源头,所有的点击事件通过硬件设备检测,经过底层会调用到ViewRootImpl的ViewPostImeInputStage里面来。但是因为ViewRootImpl存在着多个,到底是哪个会接收到回调呢,十分钟了解Android触摸事件原理(InputManagerService)告诉我们会根据Z轴的高度,获取最近一个窗口,然后执行对应ViewRootImpl里面ViewPostImeInputStage的监听方法->执行mView.dispatchPointerEvent(event)。
    此处的mView便是Dialog所在的DecorView,然后才开始由decorView进行事件分发,我们先看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);
            }
        }
    

    这里会执行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);
        }
    

    因为当前decorView注入的窗口是在创建dialog时新建的,另外dialog本身就是实现了Window.Callback接口,第一步当中已经将dialog注入到window当中了,因此此处的cb对象就是dialog对象,这时就直接调用了dialog的dispatchTouchEvent方法。接下来就是熟知的事件分发流程了。此处便不再多做介绍。


    结论:

    Activity的dispatchTouchEvent也是从对应的decorView的dispatchTouchEvent中分发出来的,而Activity所处的decorView跟Dialog所处的decorView并不属于同一个,所以Activity自然接收不到任何点击事件。

    相关文章

      网友评论

        本文标题:为什么Dialog弹出以后,activity就无法捕捉触摸事件了

        本文链接:https://www.haomeiwen.com/subject/scqehqtx.html