zoukankan      html  css  js  c++  java
  • android.app.Dialog(23)里window的那些事(坑)

    不要使用theme去配置Dialog的gravity

    因为如今手机的尺寸比較大(相对于智能机開始的3.5in、4.0in),而Dialog默认都是显示在屏幕中心的位置,用户触摸起来多不便。

    所以大多数产品都会要求Dialog在底部显示。

    所以你可能这样写:

        <style name="BottomDialog" parent="@android:style/Theme.Dialog">
            <item name="android:gravity">bottom</item>
        </style>

    或者这样写:

        dialog.getWindow().setGravity(Gravity.BOTTOM);

    那么哪一种会更好一点呢?心里想,既然xml能够实现,那就用xml吧。毕竟xml有更好的配置性和维护性。

    假设你真这样想的话,等你实践一下,就会发现。通过xml的方式配置Dialog的gravity。根本行不用。反而是另外一种是可行的。

    这不科学啊,可是你冷静一下,去细致看一下Dialog的构造方法。原因并不复杂,你在style里配置的gravity属性,是没实用。都会被w.setGravity(Gravity.CENTER)所覆盖。

    对这样的实现方法,不发表评论,理解并记住就好。

        //Dialog.java
        public Dialog(@NonNull Context context) {
            this(context, 0, true);
        }
    
        public Dialog(@NonNull Context context, @StyleRes int themeResId) {
            this(context, themeResId, true);
        }
    
        Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
            if (createContextThemeWrapper) {
                if (themeResId == 0) {
                    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);
    
            final Window w = new PhoneWindow(mContext);
            mWindow = w;
            w.setCallback(this);
            w.setOnWindowDismissedCallback(this);
            w.setWindowManager(mWindowManager, null, null);
            w.setGravity(Gravity.CENTER);
    
            mListenersHandler = new ListenersHandler(this);
        }

    须要调用setAttributes。而不是仅改变attributes

    问题来了,假设在上一个问题中。

    我不用这种方法

    dialog.getWindow().setGravity(Gravity.BOTTOM);

    而改用

    WindowManager.LayoutParams attributes = dialog.getWindow().getAttributes();
    attributes.gravity = Gravity.BOTTOM;
    //因为attributes是引用类型,所以不须要重写设置一遍。就能够改变它的值
    //dialog.getWindow().setAttributes(attributes);

    假设你也是这么想的,而且也这么做了,实际情况就是:它也行不用。可是你加上了setAttributes这种方法。就是好了。就在我不困惑不解的时候,我看下了setAttributes和setGravity的源代码,我发现他们都不约而同地调用dispatchWindowAttributesChanged这种方法。

    奥。真相大白:原来改变了attributes的里面的值,还要通知Window进行回调一下。

        //Window.java
        public void setAttributes(WindowManager.LayoutParams a) {
            mWindowAttributes.copyFrom(a);
            dispatchWindowAttributesChanged(mWindowAttributes);
        }
    
        protected void dispatchWindowAttributesChanged(WindowManager.LayoutParams attrs) {
            if (mCallback != null) {
                mCallback.onWindowAttributesChanged(attrs);
            }
        }

    反思:还是要尽量,直接使用系统提供的公开方法。这样会避免入坑。

    setAttributes要在setContentView之前调用

    这时候我们想要设置这个dialog充满屏幕的宽(也同是须要调用setBackgroundDrawableResource来设置Dialog的DecorView背景透明,而且没有边框)。能够參考例如以下代码来完毕。

    乍一看。没有什么问题,可是当你执行一下。它也行不通

    Dialog dialog = new Dialog(this, android.R.style.Theme_Dialog);
    dialog.getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
    dialog.getWindow().setBackgroundDrawableResource(android.R.color.transparent);
    dialog.setContentView(R.layout.dialog);
    dialog.show();

    而须要把dialog.setContentView(R.layout.dialog);放在设置Window的前面才行。

    Dialog dialog = new Dialog(this, android.R.style.Theme_Dialog);
    dialog.setContentView(R.layout.dialog);
    dialog.getWindow().setBackgroundDrawableResource(android.R.color.transparent);
    dialog.getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
    dialog.show();

    当我们读一下PhoneWindow的setContentView的源代码,再结合一下onWindowAttributesChanged方法。

    就会恍然大悟,原来是这样。
    哪样尼?就是回调onWindowAttributesChanged的时候。而mDecor根本没有创建出来,任意就直接return掉了。

        //PhoneWindow.java
        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) {
                installDecor();
            } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
                mContentParent.removeAllViews();
            }
    
            if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
                final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                        getContext());
                transitionTo(newScene);
            } else {
                mLayoutInflater.inflate(layoutResID, mContentParent);
            }
            mContentParent.requestApplyInsets();
            final Callback cb = getCallback();
            if (cb != null && !isDestroyed()) {
                cb.onContentChanged();
            }
        }
        //Dialog.java
        public void onWindowAttributesChanged(WindowManager.LayoutParams params) {
            if (mDecor != null) {
                mWindowManager.updateViewLayout(mDecor, params);
            }
        }

    最后:奇怪真是奇怪。眼下仅仅有setLayout有这个问题,而setBackgroundDrawableResource和setDimAmount都没有这个问题。

    欢迎在以下留言。告知一下。

    谢谢。

  • 相关阅读:
    实参和形参
    location对象
    区别 apply,call
    窗体之间的交互(window.opener)
    我的升级脚本总结
    Create elements
    history 对象
    函数参数的属性:callee
    发布app store流程
    【转】如何生成静态页面的五种方案
  • 原文地址:https://www.cnblogs.com/zhchoutai/p/8301415.html
Copyright © 2011-2022 走看看