美文网首页
AlertDialog中的Builder模式

AlertDialog中的Builder模式

作者: 左大人 | 来源:发表于2018-09-26 18:52 被阅读0次

image.png

序言

AlertDialog是安卓系统中常用的对话框,它采用Builder设计模式,将对话框的创建和表现显示分离。下面我就来分析一下AlertDialog的源码。

跟踪源码

首先看一下AlertDialog的定义:

public class AlertDialog extends Dialog implements DialogInterface {
    private AlertController mAlert;
    //其他代码省略
}

AlertDialog继承Dialog,实现DialogInterface接口。并且有一个成员变量mAlert : AlertController,我们待会来分析mAlert的作用。
AlertDialog的用法如下:

private void showDialog(Context context) {
    AlertDialog.Builder builder = new AlertDialogBuilder(context);
    builder.setIcon(R.drawable.icon);
    builder.setTitle("标题");
    builder.setMessage("这是对话框显示的内容");
    builder.setPositiveButton("确定", null);
    builder.setNegativeButton("取消", null);
    builder.create().show(); //构建AlertDialog并显示
}

AlertDialog都是通过Builder来构建的,我们看一下Builder的代码:

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

    public Builder(Context context) {
        this(context, resolveDialogTheme(context, ResourceId.ID_NULL));
    }

    public Builder(Context context, int themeResId) {
        P = new AlertController.AlertParams(new ContextThemeWrapper(
                context, resolveDialogTheme(context, themeResId)));
    }

    /**
     * Set the title using the given resource id.
     *
     * @return This Builder object to allow for chaining of calls to set methods
     */
    public Builder setTitle(@StringRes int titleId) {
        P.mTitle = P.mContext.getText(titleId);
        return this;
    }

    //一些列setXXX方法,用来设置对话框需要的各种参数
    
    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;
    }
    
    public AlertDialog show() {
        final AlertDialog dialog = create();
        dialog.show();
        return dialog;
    }

Builer有一个成员变量P : AlertController.AlertParams,通过setXXX设置的参数最终保存在P中,调用create()创建对话框时,又调用了P.allply(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);
        }
        //设置各种属性
        ......
}

可以看到,apply的作用是把AlertController.Params保存的对话框参数设置给AlertController。
Builder.create方法调用之后,创建了AlertDialog对象,接下来调用Builder.show方法展示对话框:

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

转而调用Dialog.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();
    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;
    }

    mWindowManager.addView(mDecor, l);
    mShowing = true;

    sendShowMessage();
}

可以看到,Dialog.show方法中做了以下几件事:

  1. 通过dispatchOnCreate调用AlertDialog.onCreate方法
  2. 调用AlertDialog的onStart方法
  3. 将Dialog的DecorView添加到WindowManager中

很明显,这就是一系列典型的生命周期方法,按照惯例,AlertDialog的内容构建应该在onCreate方法中,我们验证一下:

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

AlertDialog.onCreate调用mAlert : AlertController.installContent方法:

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

该方法很简短,其中有一句代码很显眼mWindow.setContentView(contentView),我们都知道,Activity.setContentView实际就是调用Window.setContentView,可见,Dialog和Activity设置视图的方式相同。
setContentView方法接收layoutId,即contentView是布局id:

private int selectContentView() {
    if (mButtonPanelSideLayout == 0) {
        return mAlertDialogLayout;
    }
    if (mButtonPanelLayoutHint == AlertDialog.LAYOUT_HINT_SIDE) {
        return mButtonPanelSideLayout;
    }
    // TODO: use layout hint side for long messages/lists
    return mAlertDialogLayout;
}

这里有两种布局,一种是带Button,一种不带Button,它们在AlertController的构造方法中初始化:

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

AlertDialog中的布局资源就是alert_dialog.xml这个文件,由于该布局文件内容较长,我们就用一张图描述一下它的结构:


image.png

分析了setContentView之后,我们看一下setupView方法:

private void setupView() {
    final View parentPanel = mWindow.findViewById(R.id.parentPanel);
    final View defaultTopPanel = parentPanel.findViewById(R.id.topPanel);
    final View defaultContentPanel = parentPanel.findViewById(R.id.contentPanel);
    final View defaultButtonPanel = parentPanel.findViewById(R.id.buttonPanel);

    // Install custom content before setting up the title or buttons so
    // that we can handle panel overrides.
    final ViewGroup customPanel = (ViewGroup) parentPanel.findViewById(R.id.customPanel);
    setupCustomContent(customPanel);

    final View customTopPanel = customPanel.findViewById(R.id.topPanel);
    final View customContentPanel = customPanel.findViewById(R.id.contentPanel);
    final View customButtonPanel = customPanel.findViewById(R.id.buttonPanel);

    // Resolve the correct panels and remove the defaults, if needed.
    final ViewGroup topPanel = resolvePanel(customTopPanel, defaultTopPanel);
    final ViewGroup contentPanel = resolvePanel(customContentPanel, defaultContentPanel);
    final ViewGroup buttonPanel = resolvePanel(customButtonPanel, defaultButtonPanel);

    setupContent(contentPanel);
    setupButtons(buttonPanel);
    setupTitle(topPanel);
    //省略其他内容
    ......
}

可以看到,代码中的setupContent、setupButtons、setupTitle就是真正初始化对话框视图。其中还有一个setupCustomContent方法,该方法是初始化自定义的View,即通过setView设置:

/**
 * Set the view resource to display in the dialog.
  */
 public void setView(int layoutResId) {
     mView = null;
     mViewLayoutResId = layoutResId;
     mViewSpacingSpecified = false;
 }

如果不设置自定义view,customPanel就不可见。

上面介绍了对话框的创建和初始化过程,那么怎么显示对话框呢?
在Builder.show方法中有一句:

sendShowMessage();

通过sendShowMessage发送一个显示对话框的消息。

总结

AlertDialog采用了Builder设计模式,把对话框的构建和表示分离开来,使得同样的构建过程可以创建不同的表示。
AlertDialog的构建是在Builder.create方法,而视图初始化和显示则是在Builder.show方法中。
使用自定义AlertDialog时需要注意一点:只有调用Builder.show方法之后,才能获取布局中的View进行初始化

相关文章

网友评论

      本文标题:AlertDialog中的Builder模式

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