我们在使用 AlertDialog
的时候,如果想改变 POSITIVE_BUTTON
或者 NEGATIVE_BUTTON
的字体颜色、大小时,可能会注意到 AlertDialog.getButton(DialogInterface.BUTTON_POSITIVE)
这个方法,但是当我们在创建了AlertDialog
,想要拿到这 Button
的时候会发现返回的结果是个null
。
尝试之后会发现,在AlertDialog
的show()
执行过之后,这个Button
返回的就不再是null
了,此时就可以对Button
的属性进行设置:
final AlertDialog alertDialog = new AlertDialog.Builder(this).setTitle("Title")
.setMessage("meaasge")
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
}).setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
}).create();
alertDialog.show();
Button button = alertDialog.getButton(DialogInterface.BUTTON_POSITIVE);
button.setTextColor(Color.parseColor("#00c8aa"));
button.setTextSize(10);
或者在onShow(DialogInterface dialog)
的回调中设置:
alertDialog.setOnShowListener(new DialogInterface.OnShowListener() {
@Override
public void onShow(DialogInterface dialog) {
Button button = alertDialog.getButton(DialogInterface.BUTTON_POSITIVE);
button.setTextColor(Color.parseColor("#00c8aa"));
button.setTextSize(10);
}
});
alertDialog.show();
接下来我们就看一下为什么AlertDialog.Builder.create()
已经构建了一个AlertDialog
对象,再去getButton(int which)
会是null
?
有了AlertDialog
实体而getButton(int which)
为null
,那我们就从getButton(int which)
入手看一下源码的处理。点进去getButton(int which)
我们会发现返回的是AlertController
类中的成员变量 Button mButtonPositive
:
//AlertDialog中的getButton(int which)
public Button getButton(int whichButton) {
return mAlert.getButton(whichButton);
}
//AlertController类中的getButton(int which)
public Button getButton(int whichButton) {
switch (whichButton) {
case DialogInterface.BUTTON_POSITIVE:
return mButtonPositive;
case DialogInterface.BUTTON_NEGATIVE:
return mButtonNegative;
case DialogInterface.BUTTON_NEUTRAL:
return mButtonNeutral;
default:
return null;
}
}
AlertController
,这个类几乎包含了AlertDialog
所有的属性。返回的button
是null
,说明这个button
还没有初始化,我们来看一下这个button
是在哪里被初始化的:
private void setupButtons(ViewGroup buttonPanel) {
int BIT_BUTTON_POSITIVE = 1;
int BIT_BUTTON_NEGATIVE = 2;
int BIT_BUTTON_NEUTRAL = 4;
int whichButtons = 0;
mButtonPositive = (Button) buttonPanel.findViewById(android.R.id.button1);
mButtonPositive.setOnClickListener(mButtonHandler);
if (TextUtils.isEmpty(mButtonPositiveText)) {
mButtonPositive.setVisibility(View.GONE);
} else {
mButtonPositive.setText(mButtonPositiveText);
mButtonPositive.setVisibility(View.VISIBLE);
whichButtons = whichButtons | BIT_BUTTON_POSITIVE;
}
mButtonNegative = (Button) buttonPanel.findViewById(android.R.id.button2);
mButtonNegative.setOnClickListener(mButtonHandler);
}
button
在AlertController
类的setupButtons(ViewGroup buttonPanel)
方法中被初始化,通过追踪,setupButtons(ViewGroup buttonPanel)
方法在setupView()
中被执行,setupView()
在installContent()
中被执行:
public void installContent() {
final int contentView = selectContentView();
mDialog.setContentView(contentView);
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);
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);
final ViewGroup topPanel = resolvePanel(customTopPanel, defaultTopPanel);
final ViewGroup contentPanel = resolvePanel(customContentPanel, defaultContentPanel);
final ViewGroup buttonPanel = resolvePanel(customButtonPanel, defaultButtonPanel);
setupContent(contentPanel);
setupButtons(buttonPanel);
setupTitle(topPanel);
}
这个installContent()
方法是公有的且在AlertController
中没有被执行,那必定是在其它类中被AlertController
的实例执行了,AlertController
类是AlertDialog
的一个辅助类且AlertDialog
也持有一个AlertController
类的实例,所以我们在AlertController
类中找这个installContent()
方法:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mAlert.installContent();
}
通过查找,在AlertDialog
中的onCreate(Bundle savedInstanceState)
方法中找到了这个方法。onCreate(Bundle savedInstanceState)
方法是重写了父类Dialog
的方法,那具体的执行过程就要去Dialog
类中找了。
在Dialog
类中能看到onCreate(Bundle savedInstanceState)
在dispatchOnCreate(Bundle savedInstanceState)
中执行:
void dispatchOnCreate(Bundle savedInstanceState) {
if (!mCreated) {
onCreate(savedInstanceState);
mCreated = true;
}
}
dispatchOnCreate(Bundle savedInstanceState)
方法在Dialog
类中被多次执行,而与AlertDialog
直接相关的则是show()
方法:
public void show() {
if (!mCreated) {
dispatchOnCreate(null);
} else {
final Configuration config = mContext.getResources().getConfiguration();
mWindow.getDecorView().dispatchConfigurationChanged(config);
}
onStart();
mDecor = mWindow.getDecorView();
WindowManager.LayoutParams l = mWindow.getAttributes();
mWindowManager.addView(mDecor, l);
mShowing = true;
sendShowMessage();
}
简化之后的show()
方法,mCreated
是一个布尔变量,标记是否被创建,首次调用show()
时,mCreated
为false
,会执行dispatchOnCreate(null)
,就会执行onCreate(Bundle savedInstanceState)
。到此,为什么AlertDialog.getButton(DialogInterface.BUTTON_POSITIVE)
为null
,就很清楚了。AlertDialog
的逻辑是show()
方法中初始化了AlertDialog
上面的控件,然后展示出来,所以在show()
方法执行之后才能得到button
。
那我们也来看一下AlertDialog.Builder.create()
中都干了什么:
public AlertDialog create() {
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
和一些参数的传递,在AlertDialog
的构造方法中初始化了AlertController mAlert
protected AlertDialog(@NonNull Context context, @StyleRes int themeResId) {
super(context, resolveDialogTheme(context, themeResId));
mAlert = new AlertController(getContext(), this, getWindow());
}
在AlertController
的构造方法中也只是传参和初始化布局id
,并没有做初始化控件的操作。
public AlertController(Context context, AppCompatDialog di, Window window) {
mContext = context;
mDialog = 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_android_layout, 0);
mButtonPanelSideLayout = a.getResourceId(R.styleable.AlertDialog_buttonPanelSideLayout, 0);
mListLayout = a.getResourceId(R.styleable.AlertDialog_listLayout, 0);
mMultiChoiceItemLayout = a.getResourceId(R.styleable.AlertDialog_multiChoiceItemLayout, 0);
mSingleChoiceItemLayout = a
.getResourceId(R.styleable.AlertDialog_singleChoiceItemLayout, 0);
mListItemLayout = a.getResourceId(R.styleable.AlertDialog_listItemLayout, 0);
mShowTitle = a.getBoolean(R.styleable.AlertDialog_showTitle, true);
a.recycle();
di.supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
}
所以在AlertDialog.Builder.create()
之后是得不到具体的控件实例的。
在show()
之后我们能得到Button
,那我们能不能得到dialog
上面其他的控件呢?
我们看AlertController
的源码会发现,有多处使用findViewById()
这个方法,使用的是mWindow
,比如:
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);
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);
final ViewGroup topPanel = resolvePanel(customTopPanel, defaultTopPanel);
final ViewGroup contentPanel = resolvePanel(customContentPanel, defaultContentPanel);
final ViewGroup buttonPanel = resolvePanel(customButtonPanel, defaultButtonPanel);
setupContent(contentPanel);
setupButtons(buttonPanel);
setupTitle(topPanel);
}
这个mWindow
变量是通过构造方法传进来的,我们在看下AlertDialog
的构造方法:
protected AlertDialog(@NonNull Context context, @StyleRes int themeResId) {
super(context, resolveDialogTheme(context, themeResId));
mAlert = new AlertController(getContext(), this, getWindow());
}
mWindow
是Dialog
的getWindow()
返回的对象,查看Dialog
的构造方法,会发现这个mWindow
是一个PhoneWindow
实例:
Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
final Window w = new PhoneWindow(mContext);
mWindow = w;
w.setCallback(this);
w.setOnWindowDismissedCallback(this);
w.setWindowManager(mWindowManager, null, null);
w.setGravity(Gravity.CENTER);
mListenersHandler = new ListenersHandler(this);
}
public @Nullable Window getWindow() {
return mWindow;
}
源码使用Window.findViewById(int id)
,那我们应该也能通过获取Dialog
的Window
来获取其他控件。利用这个方法得到AlertDialog
上面的控件来设置各种属性,包括title
、message
都可以设置颜色、大小等属性,可以看下我的上篇博客 Builder模式设置AlertDialog字体大小、颜色等属性
回顾一下AlertDialog的创建流程:
首先,通过AlertDialog
的内部类Builder
,设置了各种属性,然后通过create()
方法实例化了AlertDialog
,在这个过程中创建了Dialog
的PhoneWindow
,再然后是执行show()
方法,在这个方法中,执行了onCreate(Bundle savedInstanceState)
方法,接着是执行AlertController.installContent()
,在这个方法中初始化了各种控件,并设置属性。最后才是show()
方法中的WindowManager.addView(PhoneWindow.getDecorView, WindowManager.LayoutParams)
展示出来。
看一下AlertController.installContent()
这个方法:
public void installContent() {
final int contentView = selectContentView();
mDialog.setContentView(contentView);
setupView();
}
Dialog
类中的setContentView(contentView)
方法:
public void setContentView(@LayoutRes int layoutResID) {
mWindow.setContentView(layoutResID);
}
所以在mDialog.setContentView(contentView)
这句话的执行之后才能在后面使用mWindow.findViewById(int id)
,也就是这样我们才能通过Dialog.getWindow().findViewById(int id)
得到具体的控件。