AlertDialog中的Builder模式
image.png
序言
AlertDialog是安卓系统中常用的对话框,它采用Builder设计模式,将对话框的创建和表现显示分离。下面我就来分析一下AlertDialog的源码。
跟踪源码
首先看一下AlertDialog的定义:
<code>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方法中做了以下几件事:
- 通过dispatchOnCreate调用AlertDialog.onCreate方法
- 调用AlertDialog的onStart方法
- 将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源码解析
- AlertDialog入门与详解(多种实现示例:自定义布局等)
- Android进阶:七、Retrofit2.0原理解析之最简流程【上】
- Android网络连接判断与处理
- 链表反转,照抄都失败,我到底错哪里了?求助大牛
- Android实现多线程下载文件,支持断点
- RocketMQ中Broker的消息存储源码分析
- 单例模式的理解和示例
未经允许不得转载:杂烩网 » AlertDialog中的Builder模式