首先看一下AlertDialog简单的用法。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage("hello")
.setTitle("AlertDialog")
.create()
.show();
}
一步一步来看看到底在源码中做了什么。
首先调用AlertDialog.Builder()创建了一个Builder类的对象,它是一个AlertDialog的内部类。
public class AlertDialog {
...
public static class Builder {
...
public Builder(@NonNull Context context) {
this(context, resolveDialogTheme(context, 0));
}
...
}
...
}
通过this调用另一个构造方法。
public Builder(@NonNull Context context, @StyleRes int themeResId) {
P = new AlertController.AlertParams(new ContextThemeWrapper(
context, resolveDialogTheme(context, themeResId)));
mTheme = themeResId;
}
创建了一个AlertParams类的对象P。
class AlertController {
...
public static class AlertParams {
...
public AlertParams(Context context) {
mContext = context;
mCancelable = true;
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
...
}
...
}
AlertParams是AlertController的静态内部类。用户在调用Builder方法后会创建一个对应的AlertParams实例P。
接着调用了一系列Builder的set方法。我们看其中一个setTitle方法。
public Builder setTitle(@Nullable CharSequence title) {
P.mTitle = title;
return this;
}
为P中的对应属性赋值。setMessage和其他的一些set方法也是类似的
public Builder setMessage(@Nullable CharSequence message) {
P.mMessage = message;
return this;
}
通过我们调用一系列的set方法,P中的各个属性已经被填充为我们传入的参数。
接着调用了Builder的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实例。接着调用了P的apply方法。
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);
}
...
}
这里的dialog是上一步创建的AlertController对象。调用了一系列AlertController的set方法。mTitle、mIcon、mMessage这些就是P中的那些属性。我们在前面已经通过builder的set方法为其中的一部分赋了值。就是说,我们如果设置了这些参数,会继续调用AlertController中对应的set方法。
if (mTitle != null) {
dialog.setTitle(mTitle);
}
看其中的一个,其他都是类似的。
```javascript
public void setTitle(CharSequence title) {
mTitle = title;
if (mTitleView != null) {
mTitleView.setText(title);
}
}
将这些P中设置的值传给了AlertController。回到create方法。最后返回了创建的AlertDialog实例。到这里我们知道了,现在我们拥有了一个AlertDialog,在这个AlertDialog中还包含了一个AlertController,而AlertController中保存着我们传进去的参数。接着还有最后一步,调用builder的show方法。
因为前面调用create方法时返回的是一个AlertDialog,调用的show方法其实是AlertDialog的show方法。但是AlertDialog是继承自AppCompatDialog,而AppCompatDialog又是继承自Dialog,所以这里的show()方法是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();
...
}
调用了dispatchOnCreate()。
void dispatchOnCreate(Bundle savedInstanceState) {
if (!mCreated) {
onCreate(savedInstanceState);
mCreated = true;
}
}
调用onCreate()
protected void onCreate(Bundle savedInstanceState) {
}
这个方法是在AlertDialog中实现的。
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mAlert.installContent();
}
调用了mAlert的installContent方法。mAlert就是前面创建的AlertController。
public void installContent() {
final int contentView = selectContentView();
mDialog.setContentView(contentView);
setupView();
}
调用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);
final boolean hasCustomPanel = customPanel != null
&& customPanel.getVisibility() != View.GONE;
final boolean hasTopPanel = topPanel != null
&& topPanel.getVisibility() != View.GONE;
final boolean hasButtonPanel = buttonPanel != null
&& buttonPanel.getVisibility() != View.GONE;
// Only display the text spacer if we don't have buttons.
if (!hasButtonPanel) {
if (contentPanel != null) {
final View spacer = contentPanel.findViewById(R.id.textSpacerNoButtons);
if (spacer != null) {
spacer.setVisibility(View.VISIBLE);
}
}
}
...
}
看一下setupTitle方法
private void setupTitle(ViewGroup topPanel) {
if (mCustomTitleView != null) {
// Add the custom title view directly to the topPanel layout
LayoutParams lp = new LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
topPanel.addView(mCustomTitleView, 0, lp);
// Hide the title template
View titleTemplate = mWindow.findViewById(R.id.title_template);
titleTemplate.setVisibility(View.GONE);
} else {
mIconView = (ImageView) mWindow.findViewById(android.R.id.icon);
final boolean hasTextTitle = !TextUtils.isEmpty(mTitle);
if (hasTextTitle && mShowTitle) {
// Display the title if a title is supplied, else hide it.
mTitleView = (TextView) mWindow.findViewById(R.id.alertTitle);
mTitleView.setText(mTitle);
// Do this last so that if the user has supplied any icons we
// use them instead of the default ones. If the user has
// specified 0 then make it disappear.
if (mIconId != 0) {
mIconView.setImageResource(mIconId);
} else if (mIcon != null) {
mIconView.setImageDrawable(mIcon);
} else {
// Apply the padding from the icon to ensure the title is
// aligned correctly.
mTitleView.setPadding(mIconView.getPaddingLeft(),
mIconView.getPaddingTop(),
mIconView.getPaddingRight(),
mIconView.getPaddingBottom());
mIconView.setVisibility(View.GONE);
}
} else {
// Hide the title template
final View titleTemplate = mWindow.findViewById(R.id.title_template);
titleTemplate.setVisibility(View.GONE);
mIconView.setVisibility(View.GONE);
topPanel.setVisibility(View.GONE);
}
}
}
在这个方法中通过findViewById找到了textview,然后调用setText。这段代码看着就很熟悉了,和我们自己向TextView中设置文本是一样的。其他的一些属性设置流程都是相似的。
以上就是整个AlertDialog创建的大致流程,它是通过一个建造者模式来实现的。总结一下这个AlertDialog建造的流程。
- 首先通过AlertDialog.Builder创建builder,在内部创建了AlertParams。
- 用户调用一系列builder的set方法填充AlertParams中对应的属性值.
- 调用builder的create方法创建AlertDialog和自己的AlertController,接着apply方法将AlertParams中填充的属性继续填充到AlertController中。
- 调用show方法实际会调用AlertController的installContent方法,然后通过一系列内部调用,将AlertController中填充的值设置到界面上。