Dialog
的基本使用,可以看之前写的一片文章 Android 弹出式布局之Dialog的使用
官方不是不建议直接使用Dialog的,而我们工作中一般是使用AlertDialog或者DialogFragment实现弹框功能
这里简单的给出一个用AlertDialog实现的弹框
AlertDialog dialog = new AlertDialog.Builder(MainActivity.this)
.setTitle("这是标题")
.setMessage("这是具体的消息内容")
.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();
dialog.show();
从上面的代码中可以看到,是使用了构建者模式(Builder模式)
,巧妙的将操作抽离出来方便自定义操作。
首先看一下new AlertDialog,Builder
做了什么操作。
public Builder(Context context, int themeResId) {
P = new AlertController.AlertParams(new ContextThemeWrapper(
context, resolveDialogTheme(context, themeResId)));
}
可以看到创建了一个AlertController.AlertParams
对象,点进去看看这个AlertController
和AlertController.AlertParams
到底是何方神圣。
public CharSequence mTitle;
public View mCustomTitleView;
public CharSequence mMessage;
...
public interface OnPrepareListViewListener
public void apply(AlertController dialog)
private void createListView(final AlertController dialog)
AlertParams
是AlertController
的内部类,只要定义了一些参数和三个方法,暂时先了解这些,后面在详细看有什么用。
public Builder setTitle(CharSequence title) {
P.mTitle = title;
return this;
}
public Builder setMessage(@StringRes int messageId) {
P.mMessage = P.mContext.getText(messageId);
return this;
}
这里AlertDialog
设置的信息实际上是传递给了AlertController.AlertParams
。
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;
}
首先通过new AlertDialog(P.mContext, 0, false)
创建了一个Dialog
对象。
AlertDialog(Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
super(context, createContextThemeWrapper ? resolveDialogTheme(context, themeResId) : 0,
createContextThemeWrapper);
mWindow.alwaysReadCloseOnTouchAttr();
mAlert = AlertController.create(getContext(), this, getWindow());
}
在看AlertController.create
方法,return new AlertController(context, di, window);
创建出来一个AlertController
对象。我们再看AlertDialog.Builder#create()
中的apply
方法。
public void apply(AlertController dialog) {
if (mCustomTitleView != null) {
dialog.setCustomTitle(mCustomTitleView);
} else {
if (mTitle != null) {
dialog.setTitle(mTitle);
}
....
}
这个方法主要是将通过AlertController.AlertParams转贮的参数又设置给了Dialog对象。
接着我们看AlertDialog
对象的创建,可以看到Dialog
的创建主要由父类完成的,这里在翻一翻Dialog
构造方法。
Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
// 由于createContextThemeWrapper在AlertDialog中传递进来是false,
//所以mContext最终是使用的创建AlertDialog传递过来的context,而
//我们知道创建dialog对象是不能使用系统的ApplicationContext的,
//所以这里应该是Activity的Context对象
if (createContextThemeWrapper) {
if (themeResId == ResourceId.ID_NULL) {
final TypedValue outValue = new TypedValue();
context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true);
themeResId = outValue.resourceId;
}
mContext = new ContextThemeWrapper(context, themeResId);
} else {
mContext = context;
}
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
final Window w = new PhoneWindow(mContext);
mWindow = w;
w.setCallback(this);
w.setOnWindowDismissedCallback(this);
w.setOnWindowSwipeDismissedCallback(() -> {
if (mCancelable) {
cancel();
}
});
w.setWindowManager(mWindowManager, null, null);
w.setGravity(Gravity.CENTER);
mListenersHandler = new ListenersHandler(this);
}
之后创建出来了一个PhoneWindow
对象,它是Window
的唯一子类,具体可以看另一篇文章PhoneWindow源码分析
这里重点关注w.setWindowManager(mWindowManager, null, null);
可以看到调用将windowManager对象设置到了window中。
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
boolean hardwareAccelerated) {
mAppToken = appToken;
mAppName = appName;
mHardwareAccelerated = hardwareAccelerated
|| SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);
if (wm == null) {
wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
}
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
继续看((WindowManagerImpl)wm).createLocalWindowManager(this);
实现。
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
return new WindowManagerImpl(mContext, parentWindow);
}
可以看到实际上是实例话出来了一个WindowManagerImpl
对象,而WindowManagerImpl
是WindowManager
的子类实现。WindowManagerImpl
内部很简单,有这么几个主要的方法。
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
@Override
public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.updateViewLayout(view, params);
}
@Override
public void removeView(View view) {
mGlobal.removeView(view, false);
}
可以看到所有的操作都是使用了mGlobal
, 而mGlobal
是WindowManagerGlobal
的实例(见private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
)。
而WindowManagerGlobal
负责管理ViewRoot
的创建、销毁操作,由于文章篇幅问题,这里只需要知道到这里就把View添加到window上去了。
接下来看一看show()
方法。这个方法是在Dialog
中。
public void show() {
...
mWindowManager.addView(mDecor, l);
mShowing = true;
sendShowMessage();
}
主要是调用WindowManager
将布局添加到了Window中。然后设置显示标记为true。sendShowMessage
用于出发添加相应回调处理。
dismiss
方法和show方法类似,主要还是调用了mWindowManager.removeViewImmediate(mDecor);
将布局移除掉。