对于一个页面的窗口展示,很多时候都可以理解为是一个activity,这样理解没错,但更确切的说window才是负责页面展示的,那为什么可以理解为一个页面就是一个activity呢?这是由于activity持有window对象,activity负责加载布局并将布局添加到window对象中。这里需要想个问题,dialog的显示并不由activity来控制,所以这里可以推测dialog的显示应该是由window来控制的,那到底是不是呢?那就得去源码中看看了。
先来个简单的使用:
mTextMessage.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
builder.setTitle("titile");
builder.setMessage("message");
AlertDialog dialog = builder.create();
dialog.show();
}
});
使用起来还是挺简单的,就从这段简单的代码开始入手了,先来看下AlertDialog.Builder的构造函数:
public Builder(@NonNull Context context) {
this(context, resolveDialogTheme(context, 0));
}
public Builder(@NonNull Context context, @StyleRes int themeResId) {
P = new AlertController.AlertParams(new ContextThemeWrapper(
context, resolveDialogTheme(context, themeResId)));
mTheme = themeResId;
}
初始化了一个AlertController.AlertParams类型的P对象,可以理解为是一个存放设置dialog属性的容器,再来看下AlertController.AlertParams的构造函数:
public AlertParams(Context context) {
mContext = context;
mCancelable = true;
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
其实就是初始化了一些成员变量。
到这AlertDialog.Builder的初始化就算完了,还是比较简单的,接下来看dialog的参数是怎么设置的:
builder.setTitle("titile")
builder.setMessage("message")
这是设置dialog的显示信息,当然还有好多属性可以设置,不过都是类似的,所以这里就看这两个属性的实现:
public Builder setTitle(@Nullable CharSequence title) {
P.mTitle = title;
return this;
}
public Builder setMessage(@Nullable CharSequence message) {
P.mMessage = message;
return this;
}
可以看到,这些设置进来的属性都赋值给了P对象,到目前为止,已经设置了显示dialog的消息,接下来执行的是:
AlertDialog dialog = builder.create();
看来得去看看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对象,那就去看下AlertDialog的构造方法了:
protected AlertDialog(@NonNull Context context, @StyleRes int themeResId) {
super(context, resolveDialogTheme(context, themeResId));
mAlert = new AlertController(getContext(), this, getWindow());
}
这里创建了一个AlertController类型的mAlert对象,create()方法中有用到,AlertDialog继承自Dialog:
Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
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);
// 设置window显示的位置,默认是居中显示
w.setGravity(Gravity.CENTER);
mListenersHandler = new ListenersHandler(this);
}
这里主要是创建了一个Window对象(Window的唯一实现是PhoneWindow),然后设置了一些回调和Window显示的位置,所以Dialog是持有Window对象的。在回到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对象后,接着调用的是P.apply(dialog.mAlert),P对象是在构建Builder的时候创建的,其类型是AlertController.AlertParams,mAlert对象在AlertDialog构造函数初始化的,那就来看下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);
}
if (mPositiveButtonText != null || mPositiveButtonIcon != null) {
dialog.setButton(DialogInterface.BUTTON_POSITIVE, mPositiveButtonText,
mPositiveButtonListener, null, mPositiveButtonIcon);
}
if (mNegativeButtonText != null || mNegativeButtonIcon != null) {
dialog.setButton(DialogInterface.BUTTON_NEGATIVE, mNegativeButtonText,
mNegativeButtonListener, null, mNegativeButtonIcon);
}
if (mNeutralButtonText != null || mNeutralButtonIcon != null) {
dialog.setButton(DialogInterface.BUTTON_NEUTRAL, mNeutralButtonText,
mNeutralButtonListener, null, mNeutralButtonIcon);
}
// For a list, the client can either supply an array of items or an
// adapter or a cursor
if ((mItems != null) || (mCursor != null) || (mAdapter != null)) {
createListView(dialog);
}
if (mView != null) {
if (mViewSpacingSpecified) {
dialog.setView(mView, mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight,
mViewSpacingBottom);
} else {
dialog.setView(mView);
}
} else if (mViewLayoutResId != 0) {
dialog.setView(mViewLayoutResId);
}
/*
dialog.setCancelable(mCancelable);
dialog.setOnCancelListener(mOnCancelListener);
if (mOnKeyListener != null) {
dialog.setOnKeyListener(mOnKeyListener);
}
*/
}
前面有说到使用Builder设置参数时,实际是将参数设置到P对象中,所以apply()函数的作用就是将P对象中参数又设置到了mAlert对象中。接着回到create()函数中看剩下的方法,基本是对dialog的回调设置,这里就不跟进去了。
分析到这,dialog的配置就算是全部配置完了,接下去要执行的是:
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();
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.copyFrom(l);
nl.softInputMode |=
WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
l = nl;
}
mWindowManager.addView(mDecor, l);
mShowing = true;
sendShowMessage();
}
先是if(mShowing)判断dialog是否处在显示状态,初次显示这里为false,那这里这样做判断有什么用意呢?在Dialog中有个hide()方法,这个方法是隐藏dialog,实际是没用销毁dialog的,且dialog的mShowing还是为true的,这就是这里所作判断的用意,继续往下看,接下来是if(!mCreated),默认是false,所以这里要执行的是dispatchOnCreate(null):
void dispatchOnCreate(Bundle savedInstanceState) {
if (!mCreated) {
onCreate(savedInstanceState);
mCreated = true;
}
}
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mAlert.installContent();
}
在执行完dispatchOncreate(null)后将mCreated置为true,紧接着执行是onCreate(),这个在Dialog中是空实现,在AlertDialog有实现,就执行了mAlert.installContent(),作用是什么呢?看看就知道了:
public void installContent() {
final int contentView = selectContentView();
mDialog.setContentView(contentView);
setupView();
}
这里简单说下这几行代码的作用:
1、selectContentView()是获取显示dialog的布局文件;
2、mDialog.setContentView(),这里是将布局文件设置到window对象中去,还记得activity是如何设置view的么?没错,用的就是setContentView()这个方法,这里和activity中使用是一个意思,其实质都是将布局文件设置到window对象中,之后就可显示了。
3、将布局文件设置到window对象中,会生成view对象,但是这些view对象还没有设置内容和一些事件监听,这也就是setupView()要做的事;
做完这些后就可以回到show()方法中接着往下走了,mWindowManager.adddView()这个方法,这个方法实际起到什么用呢?那就的好好说下了,在这方法中会创建一个ViewRootImpl对象,然后将view对象添加到ViewRootImpl对象中去,ViewRootImpl中会通知下一帧屏幕信号到来时执行view的测量、布局、绘制,这样dialog就显示到界面上了。
最后在说下sendShowMessage()方法,当设置了显示dialog时的回调监听时,这里实际的作用就是去通知这个回调。
接下来看下Dialog的一些不常见设置,也可以说是一些高级点的设置,上面说到dialog实际也是有window来显示,那么就可以对window进行一些设置,已达到想要的效果,这里先说几点需求:
1、指定dialog的宽高;
2、控制dialog的透明度;
3、调整dialog外部的灰度;
4、指定dialog的显示位置;
5、设置dialog的出现和消失时的动画;
如果你对window对象不太明白,那这个实现起来那就会有点头大了,但是当你明白window对象后,你就会发现这实在是太简单了,因为只需要设置一些window的属性就可以了,来看看:
mTextMessage.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
builder.setTitle("titile");
builder.setMessage("message");
AlertDialog dialog = builder.create();
dialog.show();
Window window = dialog.getWindow();
WindowManager.LayoutParams params = window.getAttributes();
params.width = 200;
params.height = 100;
params.alpha = 1f;
params.dimAmount = 0.5f;
params.gravity = Gravity.BOTTOM;
params.windowAnimations = R.style.BaseDialog_Bottom_Anim;
window.setAttributes(params);
dialog.show();
}
});
这里稍微提下这几个属性,还有其他的一些属性,自己可以去测试下,代码还是比较简单的:
params.width = 200;params.height = 100; 设置dialog显示的宽高;
params.alpha = 1f;params.dimAmount = 0.5; 设置弹框的透明度和弹框外部的灰度;
params.gravity = Gravity.BOTTOM; 设置dialog显示的位置在底部,如果想指定在具体位置,可以使用params.x;
params.windowAnimations = R.style.BaseDialog_Botton_Anim; 设置dialog显示隐藏动画;
R.style.BaseDialog_Botton_Anim的配置如下:
<style name="BaseDialog_Bottom_Anim">
<item name="android:windowEnterAnimation">@anim/netlib_bottom_enter_anim</item>
<item name="android:windowExitAnimation">@anim/netlib_bottom_exit_anim</item>
</style>
netlib_bottom_enter_anim:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="300">
<translate
android:fromYDelta="100%p"
android:toYDelta="0%p" />
<alpha
android:fromAlpha="0.0"
android:toAlpha="1.0" />
</set>
netlib_bottom_exit_anim:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="300">
<translate
android:fromYDelta="0%p"
android:toYDelta="100%p" />
<alpha
android:fromAlpha="1.0"
android:toAlpha="0.3" />
</set>
到这就算说完了,有不明白的欢迎提问一起探讨!