美文网首页Android开发学习之鸿蒙&Android
Android弹窗探究之AlertDialog(三)—— Dia

Android弹窗探究之AlertDialog(三)—— Dia

作者: 乌托邦式的爱情 | 来源:发表于2021-07-07 12:35 被阅读0次

    在对AlertDialog进行封装之前,我们有必要先了解一下AlertDialog的源码实现。一般情况下一行代码我们就可以创建一个弹框,如下所示:

    new AlertDialog.Builder(this).create().show();
    

    下面我们就一段段来分析,首先点击进去看下AlertDialog

    public class AlertDialog extends AppCompatDialog implements DialogInterface {
    ...
    }
    public class AppCompatDialog extends Dialog implements AppCompatCallback {
    ...
    }
    

    也就是说我们的AlertDialog是继承自AppCompatDialog,而AppCompatDialog是继承自我们的Dialog,这个关系知道是怎么一回事就可以了,接下来进入到Builder里面。

    public static class Builder {
        private final AlertController.AlertParams P;
        private final int mTheme;
    
        /**
         * Creates a builder for an alert dialog that uses the default alert
         * dialog theme.
         * <p>
         * The default alert dialog theme is defined by
         * {@link android.R.attr#alertDialogTheme} within the parent
         * {@code context}'s theme.
         *
         * @param context the parent context
         */
        public Builder(@NonNull Context context) {
            this(context, resolveDialogTheme(context, 0));
        }
    
        /**
         * Creates a builder for an alert dialog that uses an explicit theme
         * resource.
         * <p>
         * The specified theme resource ({@code themeResId}) is applied on top
         * of the parent {@code context}'s theme. It may be specified as a
         * style resource containing a fully-populated theme, such as
         * {@link R.style#Theme_AppCompat_Dialog}, to replace all
         * attributes in the parent {@code context}'s theme including primary
         * and accent colors.
         * <p>
         * To preserve attributes such as primary and accent colors, the
         * {@code themeResId} may instead be specified as an overlay theme such
         * as {@link R.style#ThemeOverlay_AppCompat_Dialog}. This will
         * override only the window attributes necessary to style the alert
         * window as a dialog.
         * <p>
         * Alternatively, the {@code themeResId} may be specified as {@code 0}
         * to use the parent {@code context}'s resolved value for
         * {@link android.R.attr#alertDialogTheme}.
         *
         * @param context the parent context
         * @param themeResId the resource ID of the theme against which to inflate
         *                   this dialog, or {@code 0} to use the parent
         *                   {@code context}'s default alert dialog theme
         */
        public Builder(@NonNull Context context, @StyleRes int themeResId) {
            P = new AlertController.AlertParams(new ContextThemeWrapper(
                    context, resolveDialogTheme(context, themeResId)));
            mTheme = themeResId;
        }
    ...
    }
    

    可以看到,我们的Builder并不是直接在AlertDialog里面去创建方法,而是创建了一个静态内部类Builder,为什么会这样设计,了解设计模式的朋友可能会明白,这是用了建造者设计模式,便于为最后生成的AlertDialog进行个性化定制。在Builder里面有两个构造方法:

    public Builder(@NonNull Context context) {
        this(context, resolveDialogTheme(context, 0));
    }
    public Builder(@NonNull Context context, @StyleRes int themeResId) {
        P = new AlertController.AlertParams(new ContextThemeWrapper(
                context, resolveDialogTheme(context, themeResId)));
        mTheme = themeResId;
    }
    

    注意看,第一个构造方法最终会执行到第二个构造方法里面,那么两个构造方法的区别是什么呢?注意看第二个构造方法的第二个参数themeResId,意思很明显AlertDialog的设置的主题,所以这里我们就很好去理解了,即:

    在创建AlertDialog的Builder的时候,如果我们不给AlertDialog指定主题,则系统会默认使用系统自带的主题样式。

    继续研究构造函数,里面有一个很重要的参数:P。这个P是在Builder里面声明的一个静态类:

    public static class Builder {
        private final AlertController.AlertParams P;
        private final int mTheme;
    

    在调用构造函数的时候会实例化P。那么这个P又是什么呢?点进去看看:

    class AlertController {
    
          ...
          
         public static class AlertParams {
                  public final Context mContext;
                  public final LayoutInflater mInflater;
         }
    }
    

    可以很明显的看到,P是AlertController类里面的一个静态内部类,我们先知道这个概念,后面再详细介绍。

    接下来我们进入到create方法

    public AlertDialog create() {
        // We can't use Dialog's 3-arg constructor with the createThemeContextWrapper param,
        // so we always have to re-set the theme
        final AlertDialog dialog = new AlertDialog(P.mContext, mTheme);
        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的实例

    protected AlertDialog(@NonNull Context context, @StyleRes int themeResId) {
        super(context, resolveDialogTheme(context, themeResId));
        mAlert = new AlertController(getContext(), this, getWindow());
    }
    

    在这里创建了AlertController这个类,在AlertController这个构造方法里面主要是去设置一些属性

    public AlertController(Context context, AppCompatDialog di, Window window) {
        mContext = context;
        mDialog = 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_android_layout, 0);
        mButtonPanelSideLayout = a.getResourceId(R.styleable.AlertDialog_buttonPanelSideLayout, 0);
    
        mListLayout = a.getResourceId(R.styleable.AlertDialog_listLayout, 0);
        mMultiChoiceItemLayout = a.getResourceId(R.styleable.AlertDialog_multiChoiceItemLayout, 0);
        mSingleChoiceItemLayout = a
                .getResourceId(R.styleable.AlertDialog_singleChoiceItemLayout, 0);
        mListItemLayout = a.getResourceId(R.styleable.AlertDialog_listItemLayout, 0);
        mShowTitle = a.getBoolean(R.styleable.AlertDialog_showTitle, true);
        mButtonIconDimen = a.getDimensionPixelSize(R.styleable.AlertDialog_buttonIconDimen, 0);
    
        a.recycle();
    
        /* We use a custom title so never request a window title */
        di.supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
    }
    

    继续向下看:
    P.apply(dialog.mAlert);

    public void apply(AlertController dialog) {
        if (mCustomTitleView != null) {
            dialog.setCustomTitle(mCustomTitleView);
        } else {
            if (mTitle != null) {
                dialog.setTitle(mTitle);
            }
            if (mIcon != null) {
                dialog.setIcon(mIcon);
            }
            if (mIconId != 0) {
                dialog.setIcon(mIconId);
            }
            if (mIconAttrId != 0) {
                dialog.setIcon(dialog.getIconAttributeResId(mIconAttrId));
            }
        }
        if (mMessage != null) {
            dialog.setMessage(mMessage);
        }
        if (mPositiveButtonText != null || mPositiveButtonIcon != null) {
            dialog.setButton(DialogInterface.BUTTON_POSITIVE, mPositiveButtonText,
                    mPositiveButtonListener, null, mPositiveButtonIcon);
        }
        if (mNegativeButtonText != null || mNegativeButtonIcon != null) {
            dialog.setButton(DialogInterface.BUTTON_NEGATIVE, mNegativeButtonText,
                    mNegativeButtonListener, null, mNegativeButtonIcon);
        }
        if (mNeutralButtonText != null || mNeutralButtonIcon != null) {
            dialog.setButton(DialogInterface.BUTTON_NEUTRAL, mNeutralButtonText,
                    mNeutralButtonListener, null, mNeutralButtonIcon);
        }
        // For a list, the client can either supply an array of items or an
        // adapter or a cursor
        if ((mItems != null) || (mCursor != null) || (mAdapter != null)) {
            createListView(dialog);
        }
        if (mView != null) {
            if (mViewSpacingSpecified) {
                dialog.setView(mView, mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight,
                        mViewSpacingBottom);
            } else {
                dialog.setView(mView);
            }
        } else if (mViewLayoutResId != 0) {
            dialog.setView(mViewLayoutResId);
        }
    
        /*
        dialog.setCancelable(mCancelable);
        dialog.setOnCancelListener(mOnCancelListener);
        if (mOnKeyListener != null) {
            dialog.setOnKeyListener(mOnKeyListener);
        }
        */
    }
    

    首先在AlertController类里面的静态内部类AlertParams里调用了apply方法,而在这个方法里面其实不难看出是给我们的AlertController里面的属性赋值,而值是来自于AlertParams声明的属性。其他的几行代码很好理解,就是给我们的AlertDialog设置监听,最后返回AlertDialog实例,最后调用show方法进行展示即可。

    到这里,AlertDialog的源码我们大致走了一遍,我们可以大致梳理一下其中几个关键类的关系。


    1.png

    AlertController:顾名思义AlertDialog的控制器类,主要是用于处理AlertDialog的各种系统内置属性,持有AlertDialog的实例。

    AlertParams:AlertController的内部类,是一个static的类,声明各种通用的属性,比如文本,图片,视图等等。在执行apply方法的时候,将获取的值传递给AlertController。

    Builder:AlertDialog的内部类,是一个static的类,用于构建AlertDialog。同时持有AlertController.AlertParams的实例。

    了解了这几个类的主要作用,接下来我们就通过一个具体的实例来看看AlertDialog到底是怎么运行的,简单例子:

    val dialog = AlertDialog.Builder(this)
    dialog.setIcon(R.mipmap.ic_launcher)
    dialog.setTitle("对话框")
    dialog.setMessage("这是一个普通的对话框")
    dialog.show()
    

    一步一步分析:

    val dialog = AlertDialog.Builder(this)
    

    调用AlertDialog内部类Builder类的构造方法,实例化AlertController.AlertParams类

    dialog.setIcon(R.mipmap.ic_launcher)
    dialog.setTitle("对话框")
    dialog.setMessage("这是一个普通的对话框")
    

    给AlertController.AlertParams类里面的图标、标题、信息设置具体的值。

    dialog.show()
    

    这个会是分析的重点:
    (1)首先会去执行AlertDialog.Builder类里面的show()方法

    public AlertDialog show() {
        final AlertDialog dialog = create();
        dialog.show();
        return dialog;
    }
    

    (2)进入create方法

    @NonNull
    public AlertDialog create() {
        // We can't use Dialog's 3-arg constructor with the createThemeContextWrapper param,
        // so we always have to re-set the theme
        final AlertDialog dialog = new AlertDialog(P.mContext, mTheme);
        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的实例,然后最终将这个实例返回,P是在Builder的时候就会被实例化的AlertController.AlertParams实例。接着执行AlertController.AlertParams的apply方法,在这里将我们赋值的title、message和icon赋值给AlertController,也可以理解为对AlertController进行封装,接下来继续对AlertDialog的各种回调进行监听。
    (2)继续执行到show()方法

    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);
        } else {
            // Fill the DecorView in on any configuration changes that
            // may have occured while it was removed from the WindowManager.
            final Configuration config = mContext.getResources().getConfiguration();
            mWindow.getDecorView().dispatchConfigurationChanged(config);
        }
    
        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();
        boolean restoreSoftInputMode = false;
        if ((l.softInputMode
                & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) {
            l.softInputMode |=
                    WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
            restoreSoftInputMode = true;
        }
    
        mWindowManager.addView(mDecor, l);
        if (restoreSoftInputMode) {
            l.softInputMode &=
                    ~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
        }
    
        mShowing = true;
    
        sendShowMessage();
    }
    

    首先判断有没有被创建过,也可以理解为AlertDialog所对应的视图部分,如果没有,执行
    dispatchOnCreate(null)方法

    void dispatchOnCreate(Bundle savedInstanceState) {
        if (!mCreated) {
            onCreate(savedInstanceState);
            mCreated = true;
        }
    }
    

    在这个方法里面会去执行onCreate(savedInstanceState)方法,所以我们找到AlertDialog的onCreate(savedInstanceState)方法

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

    在这个方法里面最终会执行到AlertController的installContent方法

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

    这个方法比较重要,首先是通过selectContentView()获取到我们的View,然后将View添加到mDialog里面去,最终执行setupView()方法对这个视图赋值,最后调用

    mWindowManager.addView(mDecor, l);
    

    将页面进行展示出来。大概的时序图为:


    2.png

    了解了原理之后,我们就开始进入实战部分了

    首先类的结构如下所示:


    3.png

    首先创建AlertController类,主要的功能有:
    (1)持有AlertDialog的实例
    (2)创建内部类AlertParams

    class AlertController {
    
        private AlertDialog mDialog;
        private Window mWindow;
        private DialogViewHelper mViewHelper;
    
        public AlertController(AlertDialog dialog, Window window) {
            this.mDialog = dialog;
            this.mWindow = window;
        }
    
        public AlertDialog getDialog() {
            return mDialog;
        }
    
        public Window getWindow() {
            return mWindow;
        }
    
        // 设置TextView
        public void setText(int viewId, CharSequence text) {
            mViewHelper.setText(viewId, text);
        }
    
        public void setViewHelper(DialogViewHelper viewHelper) {
            this.mViewHelper = viewHelper;
        }
    
    
        // 设置点击事件
        public void setOnClickListener(int viewId, View.OnClickListener listener) {
            mViewHelper.setOnClickListener(viewId, listener);
        }
    
        public <T extends View> T getView(int viewId) {
            return mViewHelper.getView(viewId);
        }
    
        public static class AlertParams {
    
            public Context mContext;
            public int mThemeResId;
            // 点击空白区是否可以取消
            public boolean mCancelable = true;
            // dialog cancel监听
            public DialogInterface.OnCancelListener mOnCancelListener;
            // dialog dismiss 监听
            public DialogInterface.OnDismissListener mOnDismissListener;
            // dialog 按键监听
            public DialogInterface.OnKeyListener mOnKeyListener;
            // 布局View
            public View mView;
            // 布局LayoutId
            public int mViewLayoutResId;
            // 存放所有的文本
            public SparseArray<CharSequence> mTextArray = new SparseArray<>();
            // 存放所有的点击事件
            public SparseArray<View.OnClickListener> mClickArray = new SparseArray<>();
            // 设置宽度
            public int mWidth = ViewGroup.LayoutParams.WRAP_CONTENT;
            // 设置动画
            public int mAnimations = 0;
            // 设置从底部弹出
            public int mGravity = Gravity.CENTER;
            // 设置高度
            public int mHeight = ViewGroup.LayoutParams.WRAP_CONTENT;
    
            public AlertParams(Context context, int themeResId) {
                this.mContext = context;
                this.mThemeResId = themeResId;
            }
    
            // 绑定和设置参数
            public void apply(AlertController mAlert) {
                DialogViewHelper viewHelper = null;
                // 设置布局
                if (mViewLayoutResId != 0) {
                    viewHelper = new DialogViewHelper(mContext, mViewLayoutResId);
                }
    
                if (mView != null) {
                    viewHelper = new DialogViewHelper();
                    viewHelper.setContentView(mView);
                }
    
                mAlert.setViewHelper(viewHelper);
    
                if (viewHelper == null) {
                    throw new IllegalArgumentException("请设置布局setContentView()");
                }
    
                // 给Dialog设置布局
                mAlert.getDialog().setContentView(viewHelper.getContentView());
    
                // 设置文本
                int textArraySize = mTextArray.size();
                for (int i = 0; i < textArraySize; i++) {
                    viewHelper.setText(mTextArray.keyAt(i), mTextArray.valueAt(i));
                }
    
                // 设置点击事件
                int textClickSize = mClickArray.size();
                for (int i = 0; i < textClickSize; i++) {
                    viewHelper.setOnClickListener(mClickArray.keyAt(i), mClickArray.valueAt(i));
                }
    
    
                Window window = mAlert.getWindow();
                window.setGravity(mGravity);
                if (mAnimations != 0)
                    window.setWindowAnimations(mAnimations);
                WindowManager.LayoutParams params = window.getAttributes();
                params.width = mWidth;
                params.height = mHeight;
                window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
                window.setAttributes(params);
    
            }
        }
    }
    

    创建AlertDialog继承自Dialog,其包含的主要内容有:
    (1)持有AlertController的实例
    (2)创建内部类Builder

    public class AlertDialog extends Dialog {
    
        final AlertController mAlert;
    
        public AlertDialog(@NonNull Context context) {
            this(context, 0);
        }
    
        public AlertDialog(@NonNull Context context, @StyleRes int themeResId) {
            super(context, themeResId);
            mAlert = new AlertController(this, getWindow());
        }
    
    
        // 设置TextView
        public void setText(int viewId, CharSequence text) {
            mAlert.setText(viewId, text);
        }
    
    
        // 设置点击事件
        public void setOnClickListener(int viewId, View.OnClickListener listener) {
            mAlert.setOnClickListener(viewId, listener);
        }
    
        public <T extends View> T getView(int viewId) {
            return mAlert.getView(viewId);
        }
    
    
        public static class Builder {
    
            private final AlertController.AlertParams P;
    
            public Builder(@NonNull Context context) {
                this(context, R.style.MyDialog);
            }
    
            public Builder(@NonNull Context context, @StyleRes int themeResId) {
                P = new AlertController.AlertParams(context, themeResId);
                P.mThemeResId = themeResId;
            }
    
            // 设置View
            public Builder setContentView(int layoutResId) {
                P.mView = null;
                P.mViewLayoutResId = layoutResId;
                return this;
            }
    
            // 设置View
            public Builder setContentView(View view) {
                P.mView = view;
                P.mViewLayoutResId = 0;
                return this;
            }
    
            // 设置文本
            public Builder setText(int viewId, CharSequence text) {
                P.mTextArray.put(viewId, text);
                return this;
            }
    
            // 设置全屏
            public Builder fullWith() {
                P.mWidth = ViewGroup.LayoutParams.MATCH_PARENT;
                return this;
            }
    
            // 设置动画
            public Builder fromBottom(boolean isAnimation) {
                if (isAnimation) {
                    P.mAnimations = R.style.dialog_from_bottom_anim;
                }
                P.mGravity = Gravity.BOTTOM;
                return this;
            }
    
            // 设置Dialog的宽高
            public Builder setWidthAndHeight(int width, int height) {
                P.mWidth = width;
                P.mHeight = height;
                return this;
            }
    
            // 设置默认动画
            public Builder addDefaultAnimation() {
                P.mAnimations = R.style.dialog_scale_anim;
                return this;
            }
    
            // 添加动画
            public Builder setAnimations(int styleAnimation) {
                P.mAnimations = styleAnimation;
                return this;
            }
    
            // 设置点击事件
            public Builder setOnClickListener(int viewId, View.OnClickListener listener) {
                P.mClickArray.put(viewId, listener);
                return this;
            }
    
    
            public Builder setCancelable(boolean cancelable) {
                P.mCancelable = cancelable;
                return this;
            }
    
    
            public Builder setOnCancelListener(OnCancelListener onCancelListener) {
                P.mOnCancelListener = onCancelListener;
                return this;
            }
    
    
            public Builder setOnDismissListener(OnDismissListener onDismissListener) {
                P.mOnDismissListener = onDismissListener;
                return this;
            }
    
    
            public Builder setOnKeyListener(OnKeyListener onKeyListener) {
                P.mOnKeyListener = onKeyListener;
                return this;
            }
    
            public AlertDialog create() {
                final AlertDialog dialog = new AlertDialog(P.mContext, P.mThemeResId);
                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;
            }
    
            public AlertDialog show() {
                final AlertDialog dialog = create();
                dialog.show();
                return dialog;
            }
        }
    }
    

    最后附上我们的DialogViewHelper处理类

    public class DialogViewHelper {
    
        private View mContentView = null;
        private SparseArray<WeakReference<View>> mViews;
    
        public DialogViewHelper(Context mContext, int mViewLayoutResId) {
            this();
            mContentView = LayoutInflater.from(mContext).inflate(mViewLayoutResId, null);
        }
    
        public DialogViewHelper() {
            mViews = new SparseArray<>();
        }
    
        // 设置布局
        public void setContentView(View mView) {
            this.mContentView = mView;
        }
    
        // 设置TextView
        public void setText(int viewId, CharSequence text) {
            TextView tv = getView(viewId);
            if (tv != null) {
                tv.setText(text);
            }
        }
    
    
        // 设置点击事件
        public void setOnClickListener(int viewId, View.OnClickListener listener) {
            View tv = getView(viewId);
            if (tv != null) {
                tv.setOnClickListener(listener);
            }
        }
    
        public <T extends View> T getView(int viewId) {
            WeakReference<View> viewReference = mViews.get(viewId);
            View view = null;
            if (viewReference != null) {
                view = viewReference.get();
            }
            if (view == null) {
                view = mContentView.findViewById(viewId);
                if (view != null)
                    mViews.put(viewId, new WeakReference<>(view));
            }
            return (T) view;
        }
    
        // 获取Content内容的View
        public View getContentView() {
            return mContentView;
        }
    }
    

    到这里,一个根据AlertDialog源码搭建的通用型AlertDialog就搭建完成啦。

    最后附上一个使用例子:
    new AlertDialog.Builder(this)
                    .setContentView(R.layout.item_permission)
                    .setText(R.id.item_delete_content, getString(R.string.sure_refruse_permission))
                    .setOnClickListener(R.id.item_delete_cancel, new NoDoubleClickListener() {
                        @Override
                        public void onNoDoubleClick(View v) {
                            if (mXXPermissions != null && mXXPermissions.isShowing()) {
                                mXXPermissions.dismiss();
                            }
                            Util.checkPermission(MainActivity.this);
                        }
                    })
                    .setOnClickListener(R.id.item_delete_sure, new NoDoubleClickListener() {
                        @Override
                        public void onNoDoubleClick(View v) {
                            if (mXXPermissions != null && mXXPermissions.isShowing()) {
                                mXXPermissions.dismiss();
                            }
                            requestPermission();
                        }
                    })
                    .setCancelable(false)
                    .fullWith()
                    .create();
    

    相关文章

      网友评论

        本文标题:Android弹窗探究之AlertDialog(三)—— Dia

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