AlertDialog是我们平时使用很多de空间。那么,这个控件是怎么实现的呢?AlertDialog继承自Dialog,实现了很多扩展功能。首先,我们先来看一下AlertDialog类的构造器。
protected AlertDialog(Context context) {
this(context, resolveDialogTheme(context, 0), true);
}
/**
* Construct an AlertDialog that uses an explicit theme. The actual style
* that an AlertDialog uses is a private implementation, however you can
* here supply either the name of an attribute in the theme from which
* to get the dialog's style (such as {@link android.R.attr#alertDialogTheme}
* or one of the constants {@link #THEME_TRADITIONAL},
* {@link #THEME_HOLO_DARK}, or {@link #THEME_HOLO_LIGHT}.
*/
protected AlertDialog(Context context, int theme) {
this(context, theme, true);
}
AlertDialog(Context context, int theme, boolean createThemeContextWrapper) {
super(context, resolveDialogTheme(context, theme), createThemeContextWrapper);
mWindow.alwaysReadCloseOnTouchAttr();
mAlert = new AlertController(getContext(), this, getWindow());
}
protected AlertDialog(Context context, boolean cancelable, OnCancelListener cancelListener) {
super(context, resolveDialogTheme(context, 0));
mWindow.alwaysReadCloseOnTouchAttr();
setCancelable(cancelable);
setOnCancelListener(cancelListener);
mAlert = new AlertController(context, this, getWindow());
}
可以看见,这个类的构造器通过protected修饰。因此,我们不能直接new一个AlertDialog。可以看见,在构造函数中,我们new了一个AlertController类,这个类是用于实现主要逻辑的。那么,我们怎么创建一个AlertDialog类呢?原来,在AlertDialog中有一个静态内部类Builder,我们设置属性等功能都是通过这个类实现的。
public static class Builder {
private final AlertController.AlertParams P;
private int mTheme;
/**
* Constructor using a context for this builder and the {@link AlertDialog} it creates.
*/
public Builder(Context context) {
this(context, resolveDialogTheme(context, 0));
}
/**
* Constructor using a context and theme for this builder and
* the {@link AlertDialog} it creates. The actual theme
* that an AlertDialog uses is a private implementation, however you can
* here supply either the name of an attribute in the theme from which
* to get the dialog's style (such as {@link android.R.attr#alertDialogTheme}
* or one of the constants
* {@link AlertDialog#THEME_TRADITIONAL AlertDialog.THEME_TRADITIONAL},
* {@link AlertDialog#THEME_HOLO_DARK AlertDialog.THEME_HOLO_DARK}, or
* {@link AlertDialog#THEME_HOLO_LIGHT AlertDialog.THEME_HOLO_LIGHT}.
*/
public Builder(Context context, int theme) {
P = new AlertController.AlertParams(new ContextThemeWrapper(
context, resolveDialogTheme(context, theme)));
mTheme = theme;
}
/**
* Returns a {@link Context} with the appropriate theme for dialogs created by this Builder.
* Applications should use this Context for obtaining LayoutInflaters for inflating views
* that will be used in the resulting dialogs, as it will cause views to be inflated with
* the correct theme.
*
* @return A Context for built Dialogs.
*/
public Context getContext() {
return P.mContext;
}
/**
* Set the title using the given resource id.
*
* @return This Builder object to allow for chaining of calls to set methods
*/
public Builder setTitle(int titleId) {
P.mTitle = P.mContext.getText(titleId);
return this;
}
/**
* Set the title displayed in the {@link Dialog}.
*
* @return This Builder object to allow for chaining of calls to set methods
*/
public Builder setTitle(CharSequence title) {
P.mTitle = title;
return this;
}
/**
* Set the title using the custom view {@code customTitleView}. The
* methods {@link #setTitle(int)} and {@link #setIcon(int)} should be
* sufficient for most titles, but this is provided if the title needs
* more customization. Using this will replace the title and icon set
* via the other methods.
*
* @param customTitleView The custom view to use as the title.
*
* @return This Builder object to allow for chaining of calls to set methods
*/
public Builder setCustomTitle(View customTitleView) {
P.mCustomTitleView = customTitleView;
return this;
}
/**
* Set the message to display using the given resource id.
*
* @return This Builder object to allow for chaining of calls to set methods
*/
public Builder setMessage(int messageId) {
P.mMessage = P.mContext.getText(messageId);
return this;
}
/**
* Set the message to display.
*
* @return This Builder object to allow for chaining of calls to set methods
*/
public Builder setMessage(CharSequence message) {
P.mMessage = message;
return this;
}
首先,我们可以看见该类中有一个AlertController.AlertParams成员变量。原来,我们设置的各种标题等信息,都保存在了该成员变量中,我们观察这里的API可以发现,方法里面会返回this(指Builder),这样的话,可以使用构建链,多次调用传入参数。
在参数传递完毕后,我们就需要调用create方法,创建一个AlertDialog。接下来,我们看下create方法做了什么。
public AlertDialog create() {
final AlertDialog dialog = new AlertDialog(P.mContext, mTheme, 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,并传入一些参数。上面我们也看见了AlertDialog的构造函数,这时便完成了AlertDialog的创建。接着会调用apply方法,将P中设置的属性,传递给AlertController的成员变量。
属性都初始化好了后,我们就需要调用show方法来展示了。在源码中,最终会调用父类中的show方法展示。那么,我们就看看show方法到底是怎么实现的。
public void show() {
//如果弹窗已经show出来了
if (mShowing) {
//如果顶级view存在则设置窗口window,并显示顶级View
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);
}
//获得Windowd的顶级View,并将其添加到对应的WindowManager中,然后发送show的消息
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 {
}
show方法会根据弹窗是否show出来,然后判断顶级View是否存在,设置窗口,并显示DecorView。如果Dialog未被创建,则调用dispatchOnCreate方法。在该方法中会调用onCreate方法。
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mAlert.installContent();
}
在该方法中调用了installContent方法。
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();
}
第一行确定窗口的样式,第二行确定窗口的总体布局。
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;
}
默认返回布局,该布局在AlertController的构造函数中完成初始化。
public AlertController(Context context, DialogInterface di, Window window) {
mContext = context;
mDialogInterface = di;
mWindow = window;
mHandler = new ButtonHandler(di);
//获得AlertDialog相关的属性集
TypedArray a = context.obtainStyledAttributes(null,
com.android.internal.R.styleable.AlertDialog,
com.android.internal.R.attr.alertDialogStyle, 0);
//获取不同布局在安卓系统中对应的id
mAlertDialogLayout = a.getResourceId(com.android.internal.R.styleable.AlertDialog_layout,
com.android.internal.R.layout.alert_dialog);
mButtonPanelSideLayout = a.getResourceId(
com.android.internal.R.styleable.AlertDialog_buttonPanelSideLayout, 0);
mListLayout = a.getResourceId(
com.android.internal.R.styleable.AlertDialog_listLayout,
com.android.internal.R.layout.select_dialog);
mMultiChoiceItemLayout = a.getResourceId(
com.android.internal.R.styleable.AlertDialog_multiChoiceItemLayout,
com.android.internal.R.layout.select_dialog_multichoice);
mSingleChoiceItemLayout = a.getResourceId(
com.android.internal.R.styleable.AlertDialog_singleChoiceItemLayout,
com.android.internal.R.layout.select_dialog_singlechoice);
mListItemLayout = a.getResourceId(
com.android.internal.R.styleable.AlertDialog_listItemLayout,
com.android.internal.R.layout.select_dialog_item);
a.recycle();
}