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方法中做了以下几件事:
- 通过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进行初始化。
网友评论