上一篇文章介绍了AlertDialog对话框的一般运用并留下了几个问题。这篇文将更详细的介绍对话框的的基类Dialog。我们都知道AlertDialog的界面划分成了三个区域,那Dialog的区域呢?其实Dialog的界面要比AlertDialog简单得多,它只划分为两个区域:标题区域和内容区域(其实一般的窗口都是这样划分的,包括Acivity),并且标题局域不能设置自定义布局而内容区域只能添加自定义布局,Dialog的setContentView()方法可用于向内容区域添加自定义布局,但本文不会用它来做自定义对话框。本文的重点是搞明白Dialog实质的显示原理和用更好的方法自定义对话框。
1.源码详解
首先先介绍一下跟对话框相关的几个重要的知识点
Window:window是一个抽象类,很多方法都是由他的子类PhoneWindow实现。他代表一个窗口,对话框就是一个窗口,activity也是一个窗口(dialog和activity都持有各自的Widnow对象),用dialog的getWindow方法可获取该对话框的Window对象。
DecorView:该类继承自FrameLayout,window内部有一个DecorView对象,该DecorView即是window的根视图,用Window对象的getDecorView方法可得到该view。
WindowManager:这个类特别重要,我们所能看到的view就是由它添加到手机屏幕上的,该类中有三个重要的方法:
//向屏幕中添加view
public void addView(View view, ViewGroup.LayoutParams params);
//更新屏幕中的view
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
//删除屏幕中的view
public void removeView(View view);
WindowManager.LayoutParams:WindowManager的内部类,继承自ViewGroup.LayoutParams,是专门用于Window的,用Window的setAttributes()方法即可设置Window的LayoutParams(布局参数)。这个类中用很多常量标记(如:FLAG_FULLSCREEN窗口全屏)用于设置Window的类型。用Window类的addFlags()可以添加这些标记
看下下面的两句代码
Dialog d = new Dialog(this);//this为activity实例
d.show();
它可以显示下图的对话框
可以看到阴暗背景和一条白色区域,其实白色区域就是Window的DecorView的所在区域,而且这块白色view就是DecorView的子view。这个子view就包含了上文中提到标题区域和内容标题,由于我们没有用setContentView设置内容,所以看不到内容区域。接下来看下dialog的构造函数和show()函数源码中分别做了什么
public Dialog(@NonNull Context context) {
this(context, 0, true);
}
public Dialog(@NonNull Context context, @StyleRes int themeResId) {
this(context, themeResId, true);
}
Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
if (createContextThemeWrapper) {
if (themeResId == 0) {
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.setWindowManager(mWindowManager, null, null);
w.setGravity(Gravity.CENTER);
mListenersHandler = new ListenersHandler(this);
}
可以发现构造函数最终是在第三个构造函数实现的,9到18行对传进来的就是对context用新的theme(theme就是属性的的批量设置)进行包装,让对话框窗口有“对话框”的样子(如非全屏,窗口外部会有阴暗效果),theme中的属性设置有些是和WindowManager.LayoutParams中的常量标记相对应的。关于theme和Window属性的详细内容可看我后续文章。20行到29行是对话框Window和WindowManager的初始化并设置一些回调,构造函数没有看到显示DecorView的相关代码。看下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);
}
onStart();
mDecor = mWindow.getDecorView();
//ActionBar相关设置
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