Dialog,AlertDialog和DialogFragment

Dialog,AlertDialog和DialogFragment区别

这几天做项目突然想到一个问题我们常用的Dialog,AlertDialog和DialogFragment到底有什么区别。

Dialog文档地址:https://developer.android.google.cn/reference/android/app/Dialog.html

AletDialog文档地址:https://developer.android.google.cn/reference/android/app/AlertDialog.html

DialogFragment地址:https://developer.android.google.cn/reference/android/app/DialogFragment.html

Dialog开发指南:https://developer.android.google.cn/guide/topics/ui/dialogs.html

Dialog设计指南:https://material.io/guidelines/components/dialogs.html#

Dialog样式解释:https://developer.android.google.cn/reference/android/R.styleable.html#Theme_alertDialogTheme

根据Dialog开发指南上面的解释,Dialog是对话框的基类,应该避免直接实例化,而使用它的一个子类:

AlertDialog

可显示标题的对话框,最多三个按钮,可选项目列表或自定义布局。

DatePickerDialog TimePickerDialog

带预定义UI的对话框,允许用户选择日期或时间。

这些类定义了对话框的样式和结构,但是您应该使用a DialogFragment作为对话框的容器。DialogFragment类提供了你需要创建您的对话框,并管理其外观,而不是调用方法上的所有控件Dialog对象。

使用DialogFragment管理对话框中确保其正确处理这样当用户按下作为生命周期事件后退按钮或旋转屏幕。这个DialogFragment类还允许您重复使用对话框的UI作为更大UI中的嵌入式组件,就像传统的一样Fragment(例如,当您希望对话框UI在大屏幕和小屏幕上显示不同时)。

他们之间的关系已经解释清楚了。

一个小问题

再来看看我在开发过程遇到的一个问题:

Dialog:

            val dialog = Dialog(this)
            dialog.setTitle("对话框")
            dialog.setContentView(
                    with(ctx) {
                        verticalLayout {
                            textView("消息") {
                                textSize = 16F
                                textColor = Color.WHITE
                                padding = dip(10)
                            }
                            linearLayout {
                                button("取消") {
                                    textSize = 14F
                                    textColor = Color.parseColor("#33B5E5")
                                    backgroundColor = Color.TRANSPARENT
                                    onClick {
                                        newToast("点击了取消")
                                    }
                                }.lparams(0) {
                                    weight = 1F
                                }

                                button("确定") {
                                    textSize = 14F
                                    textColor = Color.parseColor("#33B5E5")
                                    backgroundColor = Color.TRANSPARENT
                                    onClick {
                                        dialog.cancel()
                                    }
                                }.lparams(0) {
                                    weight = 1F
                                }
                            }.lparams(matchParent) {
                                topMargin = dip(10)
                                bottomMargin = dip(10)
                            }
                        }
                    }
            )
            dialog.show()

这是一个给Dialog添加自定义布局(setContentView())的逻辑,运行是没有问题。

AlertDialog:

 val alertDialog = AlertDialog.Builder(this).create()
            alertDialog.setTitle("对话框")
            alertDialog.setContentView(
                    with(ctx) {
                        verticalLayout {
                            textView("消息") {
                                textSize = 16F
                                textColor = Color.WHITE
                                padding = dip(10)
                            }
                            linearLayout {
                                button("取消") {
                                    textSize = 14F
                                    textColor = Color.parseColor("#33B5E5")
                                    backgroundColor = Color.TRANSPARENT
                                    onClick {
                                        newToast("点击了取消")
                                    }
                                }.lparams(0) {
                                    weight = 1F
                                }

                                button("确定") {
                                    textSize = 14F
                                    textColor = Color.parseColor("#33B5E5")
                                    backgroundColor = Color.TRANSPARENT
                                    onClick {
                                        alertDialog.cancel()
                                    }
                                }.lparams(0) {
                                    weight = 1F
                                }
                            }.lparams(matchParent) {
                                topMargin = dip(10)
                                bottomMargin = dip(10)
                            }
                        }
                    }
            )
            alertDialog.show()

这里我想给AlertDialog添加一个自定义布局(setContentView()),运行发现报错了。

 android.util.AndroidRuntimeException: requestFeature() must be called before adding content
 at com.android.internal.policy.impl.PhoneWindow.requestFeature(PhoneWindow.java:227)
 at com.android.internal.app.AlertController.installContent(AlertController.java:234)
 at android.app.AlertDialog.onCreate(AlertDialog.java:337)
 at android.app.Dialog.dispatchOnCreate(Dialog.java:355)
 at android.app.Dialog.show(Dialog.java:260)
 at com.payeco.keyboardtest.MainActivity$onCreate$2.onClick(MainActivity.kt:109)
 at android.view.View.performClick(View.java:4240)
 at android.view.View$PerformClick.run(View.java:17749)
 at android.os.Handler.handleCallback(Handler.java:730)
 at android.os.Handler.dispatchMessage(Handler.java:92)
 at android.os.Looper.loop(Looper.java:137)
 at android.app.ActivityThread.main(ActivityThread.java:5215)
 at java.lang.reflect.Method.invokeNative(Native Method)
 at java.lang.reflect.Method.invoke(Method.java:525)
 at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:760)
 at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:576)
 at dalvik.system.NativeStart.main(Native Method)

注意:这里有一个细节,如果你用的android.support.v7.app.AlertDialog(或者API>=24)的AlertDialog不会报错,但是它会忽略setContentView中的视图,只保留setTitle()。至于为什么后面说,先看看为什么报错。

那么我们来跟着错误提示来看看对应的代码:

PhoneWindow.java(注意:这个类用AS是查看不到的,需要用Source Insight)部分,API23:

 @Override
    public boolean requestFeature(int featureId) {
        if (mContentParent != null) {
            throw new AndroidRuntimeException("requestFeature() must be called before adding content");
        }
        final int features = getFeatures();
        final int newFeatures = features | (1 << featureId);
        if ((newFeatures & (1 << FEATURE_CUSTOM_TITLE)) != 0 &&
                (newFeatures & ~CUSTOM_TITLE_COMPATIBLE_FEATURES) != 0) {
            // Another feature is enabled and the user is trying to enable the custom title feature
            // or custom title feature is enabled and the user is trying to enable another feature
            throw new AndroidRuntimeException(
                    "You cannot combine custom titles with other title features");
        }
        if ((features & (1 << FEATURE_NO_TITLE)) != 0 && featureId == FEATURE_ACTION_BAR) {
            return false; // Ignore. No title dominates.
        }
        if ((features & (1 << FEATURE_ACTION_BAR)) != 0 && featureId == FEATURE_NO_TITLE) {
            // Remove the action bar feature if we have no title. No title dominates.
            removeFeature(FEATURE_ACTION_BAR);
        }

        if ((features & (1 << FEATURE_ACTION_BAR)) != 0 && featureId == FEATURE_SWIPE_TO_DISMISS) {
            throw new AndroidRuntimeException(
                    "You cannot combine swipe dismissal and the action bar.");
        }
        if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0 && featureId == FEATURE_ACTION_BAR) {
            throw new AndroidRuntimeException(
                    "You cannot combine swipe dismissal and the action bar.");
        }

        if (featureId == FEATURE_INDETERMINATE_PROGRESS &&
                getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) {
            throw new AndroidRuntimeException("You cannot use indeterminate progress on a watch.");
        }
        return super.requestFeature(featureId);
    }

从上面可以看到当mContentParent 不为null的时候就会报错。那我们接着看它什么时候被赋值的,API23

 // This is the view in which the window contents are placed. It is either
    // mDecor itself, or a child of mDecor where the contents go.
    private ViewGroup mContentParent;

我在PhoneWindow.java中找到了这个方法,API23

 private void installDecor() {
        if (mDecor == null) {
            mDecor = generateDecor();
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        }
        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 = (TextView)findViewById(R.id.title);
                if (mTitleView != null) {
                    mTitleView.setLayoutDirection(mDecor.getLayoutDirection());
                    if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {
                        View titleContainer = findViewById(
                                R.id.title_container);
                        if (titleContainer != null) {
                            titleContainer.setVisibility(View.GONE);
                        } else {
                            mTitleView.setVisibility(View.GONE);
                        }
                        if (mContentParent instanceof FrameLayout) {
                            ((FrameLayout)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被很多方法都有调用,但是setContentView()的几个重载方法引起了我的注意,API23

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

    @Override
    public void setContentView(View view) {
        setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    }

    @Override
    public void setContentView(View view, ViewGroup.LayoutParams params) {
        // 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)) {
            view.setLayoutParams(params);
            final Scene newScene = new Scene(mContentParent, view);
            transitionTo(newScene);
        } else {
            mContentParent.addView(view, params);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
    }

这里需要注意一点就是,调用任何一个PhoneWindow#setContentView都可以使mContentParent 不为null。错误提示的第一个类PhoneWindow.java我们研究完了,再看看错误中说的AlertController类。

AlertController.java(注意:这个类用AS是查看不到的,需要用Source Insight)部分,API23:

public void installContent() {
        /* We use a custom title so never request a window title */
        mWindow.requestFeature(Window.FEATURE_NO_TITLE);
        int contentView = selectContentView();
        mWindow.setContentView(contentView);
        setupView();
        setupDecor();
    }

里面的 mWindow.requestFeature(Window.FEATURE_NO_TITLE);这个语句很有问题啊,因为我们已经有了一个结论:如果PhoneWindow#requestFeature之前调用PhoneWindow#setContent就会报错。结果已经在眼前了,我们再看看错误信息说的Dialog.java.

Dialog.java,API23

 /**
     * 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() {
        if (mShowing) {
            if (mDecor != null) {
                if (mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
                    mWindow.invalidatePanelMenu(Window.FEATURE_ACTION_BAR);
                }
                mDecor.setVisibility(View.VISIBLE);
            }
            return;
        }

        mCanceled = false;
        
        if (!mCreated) {
            dispatchOnCreate(null);
        }

        onStart();
        mDecor = mWindow.getDecorView();

        if (mActionBar == null && mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
            final ApplicationInfo info = mContext.getApplicationInfo();
            mWindow.setDefaultIcon(info.icon);
            mWindow.setDefaultLogo(info.logo);
            mActionBar = new WindowDecorActionBar(this);
        }

        WindowManager.LayoutParams l = mWindow.getAttributes();
        if ((l.softInputMode
                & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) {
            WindowManager.LayoutParams nl = new WindowManager.LayoutParams();
            nl.copyFrom(l);
            nl.softInputMode |=
                    WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
            l = nl;
        }

        try {
            mWindowManager.addView(mDecor, l);
            mShowing = true;
    
            sendShowMessage();
        } finally {
        }
    }

 // internal method to make sure mcreated is set properly without requiring
    // users to call through to super in onCreate
    void dispatchOnCreate(Bundle savedInstanceState) {
        if (!mCreated) {
            onCreate(savedInstanceState);
            mCreated = true;
        }
    }

 /**
     * Similar to {@link Activity#onCreate}, you should initialize your dialog
     * in this method, including calling {@link #setContentView}.
     * @param savedInstanceState If this dialog is being reinitalized after a
     *     the hosting activity was previously shut down, holds the result from
     *     the most recent call to {@link #onSaveInstanceState}, or null if this
     *     is the first time.
     */
    protected void onCreate(Bundle savedInstanceState) {
    }

onCreate(Bunlde)在AlertDialog中被重写了,API23

  @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mAlert.installContent();
    }

现在结果已经很清楚了 val alertDialog = AlertDialog.Builder(this).create()(这里执行了AlertDialogde 构造方法)-> alertDialog.setTitle("对话框")->alertDialog.setContentView(View)(mContentParent被赋值)-> alertDialog.show()(这里最终调用了AlertController#installContent(),所以mWindow.requestFeature(int)被调用,报错啦),我这么说可能不会有人相信,我们接着看源码。

AlertDialog$Builder.java,API23

 /**
         * Creates an {@link AlertDialog} with the arguments supplied to this
         * builder.
         * <p>
         * Calling this method does not display the dialog. If no additional
         * processing is needed, {@link #show()} may be called instead to both
         * create and display the dialog.
         */
         public AlertDialog create() {
            // Context has already been wrapped with the appropriate theme.
            final AlertDialog dialog = new AlertDialog(P.mContext, 0, false);
            P.apply(dialog.mAlert);
            dialog.setCancelable(P.mCancelable);
            if (P.mCancelable) {
                dialog.setCanceledOnTouchOutside(true);
            }
            dialog.setOnCancelListener(P.mOnCancelListener);
            dialog.setOnDismissListener(P.mOnDismissListener);
            if (P.mOnKeyListener != null) {
                dialog.setOnKeyListener(P.mOnKeyListener);
            }
            return dialog;
        }

 AlertDialog(Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
        super(context, createContextThemeWrapper ? resolveDialogTheme(context, themeResId) : 0,
                createContextThemeWrapper);


        mWindow.alwaysReadCloseOnTouchAttr();
        mAlert = new AlertController(getContext(), this, getWindow());
    }

OK!到现在为止就已经解释了API24以下AlertDialog在show()之前setContentView为什么会报错了。对了!就是show之后setContentView不会报错。

现在回答一下API24以上(包括24)不报错的原因。先看代码:

PhoneWindow.java,API24

 @Override
    public boolean requestFeature(int featureId) {
        if (mContentParentExplicitlySet) {
            throw new AndroidRuntimeException("requestFeature() must be called before adding content");
        }
        final int features = getFeatures();
        final int newFeatures = features | (1 << featureId);
        if ((newFeatures & (1 << FEATURE_CUSTOM_TITLE)) != 0 &&
                (newFeatures & ~CUSTOM_TITLE_COMPATIBLE_FEATURES) != 0) {
            // Another feature is enabled and the user is trying to enable the custom title feature
            // or custom title feature is enabled and the user is trying to enable another feature
            throw new AndroidRuntimeException(
                    "You cannot combine custom titles with other title features");
        }
        if ((features & (1 << FEATURE_NO_TITLE)) != 0 && featureId == FEATURE_ACTION_BAR) {
            return false; // Ignore. No title dominates.
        }
        if ((features & (1 << FEATURE_ACTION_BAR)) != 0 && featureId == FEATURE_NO_TITLE) {
            // Remove the action bar feature if we have no title. No title dominates.
            removeFeature(FEATURE_ACTION_BAR);
        }

        if ((features & (1 << FEATURE_ACTION_BAR)) != 0 && featureId == FEATURE_SWIPE_TO_DISMISS) {
            throw new AndroidRuntimeException(
                    "You cannot combine swipe dismissal and the action bar.");
        }
        if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0 && featureId == FEATURE_ACTION_BAR) {
            throw new AndroidRuntimeException(
                    "You cannot combine swipe dismissal and the action bar.");
        }

        if (featureId == FEATURE_INDETERMINATE_PROGRESS &&
                getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) {
            throw new AndroidRuntimeException("You cannot use indeterminate progress on a watch.");
        }
        return super.requestFeature(featureId);
    }
    // Whether the client has explicitly set the content view. If false and mContentParent is not
    // null, then the content parent was set due to window preservation.
    private boolean mContentParentExplicitlySet = false;

@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) {
            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();
        }
        mContentParentExplicitlySet = true;
    }

    @Override
    public void setContentView(View view) {
        setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    }

    @Override
    public void setContentView(View view, ViewGroup.LayoutParams params) {
        // 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)) {
            view.setLayoutParams(params);
            final Scene newScene = new Scene(mContentParent, view);
            transitionTo(newScene);
        } else {
            mContentParent.addView(view, params);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

可以看到API24判定条件换了,换成了如果mContentParentExplicitlySet为true就报错,mContentParentExplicitlySet也是在调用PhoneWindow#setContentView就变成了true。按理说逻辑一样的啊,如果在AlertController#installContent调用PhoneWindow#requestFeature还是会报错。

AlertController.java,API24

 public void installContent() {
        final int contentView = selectContentView();
        mDialog.setContentView(contentView);
        setupView();
    }

但是AlertController没有调用PhoneWindow#requestFeature啊,所以不会报错了。API24将PhoneWindow#requestFeature放在了AlertController的构造方法中。

AlertController.java,API24

 public AlertController(Context context, DialogInterface di, Window window) {
        mContext = context;
        mDialogInterface = di;
        mWindow = window;
        mHandler = new ButtonHandler(di);

        final TypedArray a = context.obtainStyledAttributes(null,
                R.styleable.AlertDialog, R.attr.alertDialogStyle, 0);

        mAlertDialogLayout = a.getResourceId(
                R.styleable.AlertDialog_layout, R.layout.alert_dialog);
        mButtonPanelSideLayout = a.getResourceId(
                R.styleable.AlertDialog_buttonPanelSideLayout, 0);
        mListLayout = a.getResourceId(
                R.styleable.AlertDialog_listLayout, R.layout.select_dialog);

        mMultiChoiceItemLayout = a.getResourceId(
                R.styleable.AlertDialog_multiChoiceItemLayout,
                R.layout.select_dialog_multichoice);
        mSingleChoiceItemLayout = a.getResourceId(
                R.styleable.AlertDialog_singleChoiceItemLayout,
                R.layout.select_dialog_singlechoice);
        mListItemLayout = a.getResourceId(
                R.styleable.AlertDialog_listItemLayout,
                R.layout.select_dialog_item);
        mShowTitle = a.getBoolean(R.styleable.AlertDialog_showTitle, true);

        a.recycle();

        /* We use a custom title so never request a window title */
        window.requestFeature(Window.FEATURE_NO_TITLE);
    }

 所以PhoneWindow#requestFeature肯定在PhoneWindow#setContentView之前执行就不会报错。虽然不会报错但是还是不推荐这么做,因为setContentView(View)的视图并不会显示。

    那么AlertDialog怎么自定义View呢?通过这个AlertDialog.Builder().setView()。

    顺便提一下dismiss()和cancle得区别:

    我的理解是正常结束对话框用dismiss(),用户明确地离开对话框而不完成任务用cancle().用户触摸对话列表中的项目时,系统也会dismiss()对话框,除非列表使用单选按钮或复选框,或者你显示调用dismiss(),需要在对话框消失时执行某些操作,则可以onDismiss(). 点击后退按钮,触摸屏的对话框区域外系统会cancle()对话框,或者你显式调用cancel.如果需要在cancle对话框是执行某些操作可以setOnCancelListener(),注意系统每次调用cancle()都会执行dismiss操作。

DialogFragment的使用

实现应覆盖此类并实现 onCreateView(LayoutInflater, ViewGroup, Bundle)以提供对话框的内容。或者,他们可以重写 onCreateDialog(Bundle)以创建一个完全自定义的对话框,例如AlertDialog及其自己的内容。

创建一个DialogFragment:
public class FireMissilesDialogFragment extends DialogFragment {
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        // Use the Builder class for convenient dialog construction
        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
        builder.setMessage(R.string.dialog_fire_missiles)
               .setPositiveButton(R.string.fire, new DialogInterface.OnClickListener() {
                   public void onClick(DialogInterface dialog, int id) {
                       // FIRE ZE MISSILES!
                   }
               })
               .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
                   public void onClick(DialogInterface dialog, int id) {
                       // User cancelled the dialog
                   }
               });
        // Create the AlertDialog object and return it
        return builder.create();
    }
}
显示对话框或嵌入Fragment:

您可能有一种UI设计,在这种设计中,您希望某些UI在某些情况下显示为对话框,但在其他情况下可能显示为全屏或嵌入片段(可能取决于设备是大屏幕还是小屏幕)。该DialogFragment 班为您提供这种灵活性,因为它仍然可以表现为一个可嵌入Fragment。
但是,在这种情况下,您不能使用AlertDialog.Builder 或其他Dialog对象来构建对话框。如果您希望DialogFragment可以嵌入,则必须在布局中定义对话框的UI,然后在onCreateView()回调中加载布局 。
这是一个DialogFragment可以作为对话框或可嵌入片段显示的示例(使用名为布局purchase_items.xml):

public class CustomDialogFragment extends DialogFragment {
    /** The system calls this to get the DialogFragment's layout, regardless
        of whether it's being displayed as a dialog or an embedded fragment. */
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        // Inflate the layout to use as dialog or embedded fragment
        return inflater.inflate(R.layout.purchase_items, container, false);
    }

    /** The system calls this only when creating the layout in a dialog. */
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        // The only reason you might override this method when using onCreateView() is
        // to modify any dialog characteristics. For example, the dialog includes a
        // title by default, but your custom layout might not need it. So here you can
        // remove the dialog title, but you must call the superclass to get the Dialog.
        Dialog dialog = super.onCreateDialog(savedInstanceState);
        dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
        return dialog;
    }
}
public void showDialog() {
    FragmentManager fragmentManager = getSupportFragmentManager();
    CustomDialogFragment newFragment = new CustomDialogFragment();

    if (mIsLargeLayout) {
        // The device is using a large layout, so show the fragment as a dialog
        newFragment.show(fragmentManager, "dialog");
    } else {
        // The device is smaller, so show the fragment fullscreen
        FragmentTransaction transaction = fragmentManager.beginTransaction();
        // For a little polish, specify a transition animation
        transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
        // To make it fullscreen, use the 'content' root view as the container
        // for the fragment, which is always the root view for the activity
        transaction.add(android.R.id.content, newFragment)
                   .addToBackStack(null).commit();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值