AlertDialog的显示过程
一、相关类
- AlertController 具体显示逻辑
- AlertController.AlertParams 存放Dialog相关参数信息
- AlertDialog.Builder 用来创建AlertDialog对象的实例
二、创建代码
通过上述相关类我们可以知道,AlertDialog的创建过程使用了Builder模式
public void showAlertDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
builder.setTitle("标题")
.setMessage("内容")
.setNegativeButton("Button1", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
})
.setNeutralButton("Button2", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
})
.setPositiveButton("Button3", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
})
.create()
.show();
}
三、流程分析
1、Builder的创建
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this)
从代码中可以看出,调用Builder的构造函数创建对象,我们接着看该类的构造方法
public static class Builder {
//封装了 AlertController.AlertParams对象,用final修饰,意味着需要在构造方法中
//进行初始化
private final AlertController.AlertParams P;
public Builder(Context context) {
this(context, resolveDialogTheme(context, Resources.ID_NULL));
}
public Builder(Context context, int themeResId) {
P = new AlertController.AlertParams(new ContextThemeWrapper(
context, resolveDialogTheme(context, themeResId)));
}
}
在构造方法中,我们创建了AlertController.AlertParams的实例P,接着我们开始设置参数信息,如设置标题,设置Message,设置Button等,这写方法最终都会将相应的参数封装在P对应的参数中
//设置Title
public Builder setTitle(@StringRes int titleId) {
P.mTitle = P.mContext.getText(titleId);
return this;
}
//set the message to display using the given resource id.
public Builder setMessage(@StringRes int messageId) {
P.mMessage = P.mContext.getText(messageId);
return this;
}
/**
* Set a listener to be invoked when the positive button of the dialog is pressed.
* @param textId The resource id of the text to display in the positive button
* @param listener The {@link DialogInterface.OnClickListener} to use.
*
* @return This Builder object to allow for chaining of calls to set methods
*/
public Builder setPositiveButton(@StringRes int textId, final OnClickListener listener) {
P.mPositiveButtonText = P.mContext.getText(textId);
P.mPositiveButtonListener = listener;
return this;
}
2、创建AlertDialog对象的实例
AlertDialog dialog = builder.create();
我们接下来分析create方法的的源码
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;
}
从源码我们可以看出,主要分为3步:
2.1 调用Alertdialog的构造方法创建该对象的实例dialog
最终会调用掉下面的构造方法,在构造方法中,创建了AlertController对象的实例,该对象主要用来进行Dialog的布局准备工作
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());
}
我们接下来看AlertController类的源码
public AlertController(Context context, DialogInterface di, Window window) {
mContext = context;
mDialogInterface = di;
mWindow = window;
mHandler = new ButtonHandler(di);
TypedArray a = context.obtainStyledAttributes(null,
com.android.internal.R.styleable.AlertDialog,
com.android.internal.R.attr.alertDialogStyle, 0);
mAlertDialogLayout = a.getResourceId(com.android.internal.R.styleable.AlertDialog_layout,
com.android.internal.R.layout.alert_dialog);
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();
}
从构造方法中我们可以看出,主要的工作就是去加载默认的布局文件,当我们没有为AlertDialog设置布局信息时,会显示默认的布局,以下是默认的布局文件
<LinearLayout
22 xmlns:android="http://schemas.android.com/apk/res/android"
23 android:id="@+id/parentPanel"
24 android:layout_width="match_parent"
25 android:layout_height="wrap_content"
26 android:orientation="vertical"
27 android:paddingTop="9dip"
28 android:paddingBottom="3dip"
29 android:paddingStart="3dip"
30 android:paddingEnd="1dip">
31
32 <LinearLayout android:id="@+id/topPanel"
33 android:layout_width="match_parent"
34 android:layout_height="wrap_content"
35 android:minHeight="54dip"
36 android:orientation="vertical">
37 <LinearLayout android:id="@+id/title_template"
38 android:layout_width="match_parent"
39 android:layout_height="wrap_content"
40 android:orientation="horizontal"
41 android:gravity="center_vertical"
42 android:layout_marginTop="6dip"
43 android:layout_marginBottom="9dip"
44 android:layout_marginStart="10dip"
45 android:layout_marginEnd="10dip">
46 <ImageView android:id="@+id/icon"
47 android:layout_width="wrap_content"
48 android:layout_height="wrap_content"
49 android:layout_gravity="top"
50 android:paddingTop="6dip"
51 android:paddingEnd="10dip"
52 android:src="@drawable/ic_dialog_info" />
53 <com.android.internal.widget.DialogTitle android:id="@+id/alertTitle"
54 style="?android:attr/textAppearanceLarge"
55 android:singleLine="true"
56 android:ellipsize="end"
57 android:layout_width="match_parent"
58 android:layout_height="wrap_content"
59 android:textAlignment="viewStart" />
60 </LinearLayout>
61 <ImageView android:id="@+id/titleDivider"
62 android:layout_width="match_parent"
63 android:layout_height="1dip"
64 android:visibility="gone"
65 android:scaleType="fitXY"
66 android:gravity="fill_horizontal"
67 android:src="@android:drawable/divider_horizontal_dark" />
68 <!-- If the client uses a customTitle, it will be added here. -->
69 </LinearLayout>
70
71 <LinearLayout android:id="@+id/contentPanel"
72 android:layout_width="match_parent"
73 android:layout_height="wrap_content"
74 android:layout_weight="1"
75 android:orientation="vertical">
76 <ScrollView android:id="@+id/scrollView"
77 android:layout_width="match_parent"
78 android:layout_height="wrap_content"
79 android:paddingTop="2dip"
80 android:paddingBottom="12dip"
81 android:paddingStart="14dip"
82 android:paddingEnd="10dip"
83 android:overScrollMode="ifContentScrolls">
84 <TextView android:id="@+id/message"
85 style="?android:attr/textAppearanceMedium"
86 android:layout_width="match_parent"
87 android:layout_height="wrap_content"
88 android:padding="5dip" />
89 </ScrollView>
90 </LinearLayout>
91
92 <FrameLayout android:id="@+id/customPanel"
93 android:layout_width="match_parent"
94 android:layout_height="wrap_content"
95 android:layout_weight="1">
96 <FrameLayout android:id="@+android:id/custom"
97 android:layout_width="match_parent"
98 android:layout_height="wrap_content"
99 android:paddingTop="5dip"
100 android:paddingBottom="5dip" />
101 </FrameLayout>
102
103 <LinearLayout android:id="@+id/buttonPanel"
104 android:layout_width="match_parent"
105 android:layout_height="wrap_content"
106 android:minHeight="54dip"
107 android:orientation="vertical" >
108 <LinearLayout
109 style="?android:attr/buttonBarStyle"
110 android:layout_width="match_parent"
111 android:layout_height="wrap_content"
112 android:orientation="horizontal"
113 android:paddingTop="4dip"
114 android:paddingStart="2dip"
115 android:paddingEnd="2dip"
116 android:measureWithLargestChild="true">
117 <LinearLayout android:id="@+id/leftSpacer"
118 android:layout_weight="0.25"
119 android:layout_width="0dip"
120 android:layout_height="wrap_content"
121 android:orientation="horizontal"
122 android:visibility="gone" />
123 <Button android:id="@+id/button1"
124 android:layout_width="0dip"
125 android:layout_gravity="start"
126 android:layout_weight="1"
127 style="?android:attr/buttonBarButtonStyle"
128 android:maxLines="2"
129 android:layout_height="wrap_content" />
130 <Button android:id="@+id/button3"
131 android:layout_width="0dip"
132 android:layout_gravity="center_horizontal"
133 android:layout_weight="1"
134 style="?android:attr/buttonBarButtonStyle"
135 android:maxLines="2"
136 android:layout_height="wrap_content" />
137 <Button android:id="@+id/button2"
138 android:layout_width="0dip"
139 android:layout_gravity="end"
140 android:layout_weight="1"
141 style="?android:attr/buttonBarButtonStyle"
142 android:maxLines="2"
143 android:layout_height="wrap_content" />
144 <LinearLayout android:id="@+id/rightSpacer"
145 android:layout_width="0dip"
146 android:layout_weight="0.25"
147 android:layout_height="wrap_content"
148 android:orientation="horizontal"
149 android:visibility="gone" />
150 </LinearLayout>
151 </LinearLayout>
152</LinearLayout>
2.2 将P中的参数与AlertController对象的属性项绑定
接着我们看apply方法。从代码中我们可以看出,apply方法的主要作用是将AlertParams存放的有关AlertDialog的参数信息传递给AlertController对象
public void apply(AlertController dialog) {
811 if (mCustomTitleView != null) {
812 dialog.setCustomTitle(mCustomTitleView);
813 } else {
814 if (mTitle != null) {
815 dialog.setTitle(mTitle);
816 }
817 if (mIcon != null) {
818 dialog.setIcon(mIcon);
819 }
820 if (mIconId >= 0) {
821 dialog.setIcon(mIconId);
822 }
823 if (mIconAttrId > 0) {
824 dialog.setIcon(dialog.getIconAttributeResId(mIconAttrId));
825 }
826 }
827 if (mMessage != null) {
828 dialog.setMessage(mMessage);
829 }
830 if (mPositiveButtonText != null) {
831 dialog.setButton(DialogInterface.BUTTON_POSITIVE, mPositiveButtonText,
832 mPositiveButtonListener, null);
833 }
834 if (mNegativeButtonText != null) {
835 dialog.setButton(DialogInterface.BUTTON_NEGATIVE, mNegativeButtonText,
836 mNegativeButtonListener, null);
837 }
838 if (mNeutralButtonText != null) {
839 dialog.setButton(DialogInterface.BUTTON_NEUTRAL, mNeutralButtonText,
840 mNeutralButtonListener, null);
841 }
842 if (mForceInverseBackground) {
843 dialog.setInverseBackgroundForced(true);
844 }
845 // For a list, the client can either supply an array of items or an
846 // adapter or a cursor
847 if ((mItems != null) || (mCursor != null) || (mAdapter != null)) {
848 createListView(dialog);
849 }
850 if (mView != null) {
851 if (mViewSpacingSpecified) {
852 dialog.setView(mView, mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight,
853 mViewSpacingBottom);
854 } else {
855 dialog.setView(mView);
856 }
857 }
858
859 /*
860 dialog.setCancelable(mCancelable);
861 dialog.setOnCancelListener(mOnCancelListener);
862 if (mOnKeyListener != null) {
863 dialog.setOnKeyListener(mOnKeyListener);
864 }
865 */
866 }
2.3 设置dialog的监听信息并返回dialog
返回dilaog对象
dialog.setCancelable(P.mCancelable);
934 if (P.mCancelable) {
935 dialog.setCanceledOnTouchOutside(true);
936 }
937 dialog.setOnCancelListener(P.mOnCancelListener);
938 dialog.setOnDismissListener(P.mOnDismissListener);
939 if (P.mOnKeyListener != null) {
940 dialog.setOnKeyListener(P.mOnKeyListener);
941 }
3、显示Dialog
dialog.show();
该方法最终会调用**父类的show()**方法
public void show() {
249 if (mShowing) {
250 if (mDecor != null) {
251 if (mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
252 mWindow.invalidatePanelMenu(Window.FEATURE_ACTION_BAR);
253 }
254 mDecor.setVisibility(View.VISIBLE);
255 }
256 return;
257 }
258
259 mCanceled = false;
260
261 if (!mCreated) {
262 dispatchOnCreate(null);
263 }
264
265 onStart();
266 mDecor = mWindow.getDecorView();
267
268 if (mActionBar == null && mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
269 final ApplicationInfo info = mContext.getApplicationInfo();
270 mWindow.setDefaultIcon(info.icon);
271 mWindow.setDefaultLogo(info.logo);
272 mActionBar = new ActionBarImpl(this);
273 }
274
275 WindowManager.LayoutParams l = mWindow.getAttributes();
276 if ((l.softInputMode
277 & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) {
278 WindowManager.LayoutParams nl = new WindowManager.LayoutParams();
279 nl.copyFrom(l);
280 nl.softInputMode |=
281 WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
282 l = nl;
283 }
284
285 try {
286 mWindowManager.addView(mDecor, l);
287 mShowing = true;
288
289 sendShowMessage();
290 } finally {
291 }
292 }
前面部分的代码主要是判断dialog是否显示,如果dialog目前为显示状态,则直接结束。重要的方法是dispatchOnCreate,该方法会设置相应的布局信息,跟踪源码我们发现最终调用AlertDialog的onCreate方法,而该方法又调用了AlertController的installContent方法,饶了一圈,又回到了AlertController。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//AlertControllre 对象
mAlert.installContent();
}
我们来看看installContent做了哪些事情?
public void installContent() {
233 /* We use a custom title so never request a window title */
234 mWindow.requestFeature(Window.FEATURE_NO_TITLE);
235
236 if (mView == null || !canTextInput(mView)) {
237 mWindow.setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
238 WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
239 }
240 mWindow.setContentView(mAlertDialogLayout);
241 setupView();
242 }
243
从代码中我们不难发现,主要是设置窗体信息,然后设置布局信息,大家可能奇怪,这个mWindow是如何初始化的呢?其实在调用AlertController的构造方法是传递进来的,我们跟踪源码可以发现,mWindow是调用Dialog的getWindow()方法返回的,他本质上是一个PhoneWindow,可以发现,它是在Dialog的构造方法中创建的(调用AlertDialog的构造方法会默认调用父类的构造方法),看到这,我们就差不多明白,原来它其实是个PhoneWindow,我们在看看这个方法 mWindow.setContentView(mAlertDialogLayout),是不是很眼熟,setContentView,创建界面的时候设置布局的方法,通过这个方法,我们就可以将布局设置到PhoneWindow了,然后设置布局的其他信息。
private void setupView() {
394 LinearLayout contentPanel = (LinearLayout) mWindow.findViewById(R.id.contentPanel);
395 setupContent(contentPanel);
396 boolean hasButtons = setupButtons();
397
398 LinearLayout topPanel = (LinearLayout) mWindow.findViewById(R.id.topPanel);
399 TypedArray a = mContext.obtainStyledAttributes(
400 null, com.android.internal.R.styleable.AlertDialog, com.android.internal.R.attr.alertDialogStyle, 0);
401 boolean hasTitle = setupTitle(topPanel);
402
403 View buttonPanel = mWindow.findViewById(R.id.buttonPanel);
404 if (!hasButtons) {
405 buttonPanel.setVisibility(View.GONE);
406 mWindow.setCloseOnTouchOutsideIfNotSet(true);
407 }
408
409 FrameLayout customPanel = null;
410 if (mView != null) {
411 customPanel = (FrameLayout) mWindow.findViewById(R.id.customPanel);
412 FrameLayout custom = (FrameLayout) mWindow.findViewById(R.id.custom);
413 custom.addView(mView, new LayoutParams(MATCH_PARENT, MATCH_PARENT));
414 if (mViewSpacingSpecified) {
415 custom.setPadding(mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight,
416 mViewSpacingBottom);
417 }
418 if (mListView != null) {
419 ((LinearLayout.LayoutParams) customPanel.getLayoutParams()).weight = 0;
420 }
421 } else {
422 mWindow.findViewById(R.id.customPanel).setVisibility(View.GONE);
423 }
424
425 /* Only display the divider if we have a title and a
426 * custom view or a message.
427 */
428 if (hasTitle) {
429 View divider = null;
430 if (mMessage != null || mView != null || mListView != null) {
431 divider = mWindow.findViewById(R.id.titleDivider);
432 } else {
433 divider = mWindow.findViewById(R.id.titleDividerTop);
434 }
435
436 if (divider != null) {
437 divider.setVisibility(View.VISIBLE);
438 }
439 }
440
441 setBackground(topPanel, contentPanel, customPanel, hasButtons, a, hasTitle, buttonPanel);
442 a.recycle();
443 }
可以看出,setupView方法主要用来判断是用默认的布局还是自定义的布局以及设置其他信息,至此,布局的初始化工作就做完了,然后开始显示布局。我们回到show方法,接着分析
可以看到,主要的显示在这段代码。
WindowManager.LayoutParams l = mWindow.getAttributes();
276 if ((l.softInputMode
277 & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) {
278 WindowManager.LayoutParams nl = new WindowManager.LayoutParams();
279 nl.copyFrom(l);
280 nl.softInputMode |=
281 WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
282 l = nl;
283 }
284
285 try {
286 mWindowManager.addView(mDecor, l);
287 mShowing = true;
288
289 sendShowMessage();
290 } finally {
291 }
设置布局参数信息,向WindowManager添加布局。自此,Dialog就显示在屏幕上了。然后通过sendShowMessage发送显示监听调用,前提是你设置了显示监听。
private void sendShowMessage() {
//当mShowMessage 不为空时才调用
if (mShowMessage != null) {
// Obtain a new message so this dialog can be re-used
Message.obtain(mShowMessage).sendToTarget();
}
}
当我们设置了setOnShowListener时,就会初始化mShowMessage
public void setOnShowListener(@Nullable OnShowListener listener) {
if (listener != null) {
mShowMessage = mListenersHandler.obtainMessage(SHOW, listener);
} else {
mShowMessage = null;
}
}
最终消息通过Handler机制,交由我们创建的Hander去处理
private static final class ListenersHandler extends Handler {
private final WeakReference<DialogInterface> mDialog;
public ListenersHandler(Dialog dialog) {
mDialog = new WeakReference<>(dialog);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case DISMISS:
((OnDismissListener) msg.obj).onDismiss(mDialog.get());
break;
case CANCEL:
((OnCancelListener) msg.obj).onCancel(mDialog.get());
break;
case SHOW:
((OnShowListener) msg.obj).onShow(mDialog.get());
break;
}
}
}
dialog.setOnShowListener(new DialogInterface.OnShowListener() {
@Override
public void onShow(DialogInterface dialog) {
//todo 显示后的逻辑
}
});
同理,setOnCancelListener,setOnDismissListener也是如此。
4、Dialog 的dismiss
当我们调用dismiss方法时,会从WinddowManager移除添加的View,这样Dialog就消失了。
@Override
public void dismiss() {
if (Looper.myLooper() == mHandler.getLooper()) {
dismissDialog();
} else {
mHandler.post(mDismissAction);
}
}
void dismissDialog() {
if (mDecor == null || !mShowing) {
return;
}
if (mWindow.isDestroyed()) {
Log.e(TAG, "Tried to dismissDialog() but the Dialog's window was already destroyed!");
return;
}
try {
//真正移除dialog的方法
mWindowManager.removeViewImmediate(mDecor);
} finally {
if (mActionMode != null) {
mActionMode.finish();
}
mDecor = null;
mWindow.closeAllPanels();
onStop();
mShowing = false;
//移除监听回调
sendDismissMessage();
}
}
四、总结
本文代码基于Android4.4,文章中存在错误或不妥之处,请各位看官留言斧正,谢谢!另附上相关源码。
Dialog源码
AlertDialog源码
AlertController源码