版权声明:本文为博主原创文章,未经博主允许不得转载
前言
DialogFragment
是继承于 Fragment
的子类,因而其具备了 Fragment
的特性,如生命周期。而其内部实际控制了一个 Dialog
对象,并通过当前 DialogFragment
所依附的 Activity
的 FragmentManager
来对其进行管理与控制,也即将其当做一个普通的 Fragment
那样来管理。
注:
dialog
拥有自己的窗体window
,意味着它可以自己接收输入事件,处理事件,并且决定自己何时消失。
浅谈生命周期
DialogFragment
与 Dialog
同生同死,因为 DialogFragment
需要监听 Dialog
的显示与关闭,并做出反映。而这两个操作关联到了 DialogFragment
加入到 FragmentManager
中和从其中移除。这样一来 Dialog
的创建与销毁也随着 DialogFragment
的生命周期而走了。
源码分析
public void setStyle(@DialogStyle int style, @StyleRes int theme) {
mStyle = style;
if (mStyle == STYLE_NO_FRAME || mStyle == STYLE_NO_INPUT) {
mTheme = android.R.style.Theme_Panel;
}
if (theme != 0) {
mTheme = theme;
}
}
此方法为对话框设置样式与主题。其中样式有 4 个可选,一般用得比较多的是STYLE_NO_TITLE
(指定对话框无标题模式),而主题默认的话可填0,若是自定义主题,可在style
文件下新增一个样式,然后继承Theme.Dialog
修改你期望的主题即可。
注意: 此方法若要生效一定要在对话框未创建之前设置,否则失效。下面我们还会谈到。
public void show(FragmentManager manager, String tag) {
mDismissed = false;
mShownByMe = true;
FragmentTransaction ft = manager.beginTransaction();
ft.add(this, tag);
ft.commit();
}
此方法就是控制对话框的显示,调用即可。其实就是类似普通的 Fragment
的显示操作逻辑,获取FragmentManager
来管理当前的对话框。是不是似曾相识?
public void dismiss() {
dismissInternal(false);
}
void dismissInternal(boolean allowStateLoss) {
if (mDismissed) {
return;
}
mDismissed = true;
mShownByMe = false;
if (mDialog != null) {
mDialog.dismiss();
mDialog = null;
}
mViewDestroyed = true;
if (mBackStackId >= 0) {
getFragmentManager().popBackStack(mBackStackId,
FragmentManager.POP_BACK_STACK_INCLUSIVE);
mBackStackId = -1;
} else {
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.remove(this);
if (allowStateLoss) {
ft.commitAllowingStateLoss();
} else {
ft.commit();
}
}
此方法控制DialogFragment
的关闭。我们看到在void dismissInternal()
方法内会判断当前的Dialog
对象 mDialog
是否为空,否则就调用Dialog
的 dismiss()
关闭对话框。而剩下的代码都是DialogFragment
为了管理自己的后续操作。比如退栈操作,移除 Fragment
。
public Dialog getDialog() {
return mDialog;
}
此方法用于获取 DialogFragment
内的 Dialog
对象。我们之后要对对话框的尺寸改变、去除标题、设置背景、都需要获取该实例。
public void setCancelable(boolean cancelable) {
mCancelable = cancelable;
if (mDialog != null) mDialog.setCancelable(cancelable);
}
望名知义,就是设置对话框是否可以取消。内部还是调用 Dialog
的方法
public void setShowsDialog(boolean showsDialog) {
mShowsDialog = showsDialog;
}
此方法控制当前 DialogFragment
是否作为对话框而存在,否则视作普通的 Fragment
来使用。而如果你把它当前普通的 Fragment
加入到关联的 Activity
中,那么mShowsDialog
会被初始化为 true。
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (!mShownByMe) {
mDismissed = false;
}
}
@Override
public void onDetach() {
super.onDetach();
if (!mShownByMe && !mDismissed) {
mDismissed = true;
}
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mShowsDialog = mContainerId == 0;
if (savedInstanceState != null) {
mStyle = savedInstanceState.getInt(SAVED_STYLE, STYLE_NORMAL);
mTheme = savedInstanceState.getInt(SAVED_THEME, 0);
mCancelable = savedInstanceState.getBoolean(SAVED_CANCELABLE, true);
mShowsDialog = savedInstanceState.getBoolean(SAVED_SHOWS_DIALOG, mShowsDialog);
mBackStackId = savedInstanceState.getInt(SAVED_BACK_STACK_ID, -1);
}
}
以上的三个方法都是 重写 Fragment
中的生命周期方法。前两个主要是设置相应的标志位。
而在 onCreate()
中主要是从保存的实例状态中获取相应信息(之前在 onSaveInstanceState()
中设置的)。
/** @hide */
@RestrictTo(LIBRARY_GROUP)
@Override
public LayoutInflater getLayoutInflater(Bundle savedInstanceState) {
if (!mShowsDialog) {
return super.getLayoutInflater(savedInstanceState);
}
mDialog = onCreateDialog(savedInstanceState);
if (mDialog != null) {
setupDialog(mDialog, mStyle);
return (LayoutInflater) mDialog.getContext().getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
}
return (LayoutInflater) mHost.getContext().getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
}
/** @hide */
@RestrictTo(LIBRARY_GROUP)
public void setupDialog(Dialog dialog, int style) {
switch (style) {
case STYLE_NO_INPUT:
dialog.getWindow().addFlags(
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
// fall through...
case STYLE_NO_FRAME:
case STYLE_NO_TITLE:
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
}
}
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new Dialog(getActivity(), getTheme());
}
最上两个方法是被隐藏的。getLayoutInflater()
中首先判断是否显示为一个对话框,若是才来到下面的逻辑。在这里我们发现,方法内调用了onCreateDialog()
来获得一个对话框实例。如果实例存在,那么才开始配置对话框(注意:我们发现此处获取前述的 setStyle()
传进来的值,因而该方法的调用必须早于此,也就是说你至少可以在 onCreateDialog()
中进行设置),此时,方法返回与对话框上下文关联的布局加载器。
接下来我们看setupDialog()
。方法内根据存入的样式,对对话框实例进行一系列的设置,比如禁止获取焦点、不可触摸、设置无标题。
另外,在 onCreateDialog()
返回了一个新的对话框实例。在此,我们注意到,如果你想实现一些Dialog子类的对话框,比如 AlertDialog
,那么你就可以重写该方法,返回你要创建的Dialog
子类实例。
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (!mShowsDialog) {
return;
}
View view = getView();
if (view != null) {
if (view.getParent() != null) {
throw new IllegalStateException(
"DialogFragment can not be attached to a container view");
}
mDialog.setContentView(view);
}
final Activity activity = getActivity();
if (activity != null) {
mDialog.setOwnerActivity(activity);
}
mDialog.setCancelable(mCancelable);
mDialog.setOnCancelListener(this);
mDialog.setOnDismissListener(this);
if (savedInstanceState != null) {
Bundle dialogState = savedInstanceState.getBundle(SAVED_DIALOG_STATE_TAG);
if (dialogState != null) {
mDialog.onRestoreInstanceState(dialogState);
}
}
}
当Activity
创建完成后回调。若mShowsDialog
为false,那么以下逻辑不再执行。
否则获取当前 DialogFragment
的根布局实例 view
,并将其作为对话框的内容,加入其中。接下来把对话框与 Activity
进行绑定。接下来就是对话框的配置,包括注册取消监听,关闭监听。最后保存相关状态到 savedInstanceState
,以便重建时利用。
@Override
public void onStart() {
super.onStart();
if (mDialog != null) {
mViewDestroyed = false;
mDialog.show();
}
}
此方法很重要。对话框真正显示就在此后。内容调用了 mDialog
的show()
方法,将对话框显示出来(依附于 Fragment
)
@Nullable
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
return null;
}
此方法为 Fragment
中的方法,为空实现,它交给子类去加载一个 contentView,再由Fragment
来处理。而我们对话框的内容布局就是在此处进行加载,以便返回一个对话框的布局给 Fragment
去处理。
注意:
onCreateDialog()
比onCreateView()
先一步执行。因为在Fragment
中调用onCreateView()
时需要拿到一个布局加载器LayoutInflater
,而DialogFragmet
中是通过上述的一个方法—getLayoutInflater()
来返回一个与对话框有关的上下文布局加载器,onCreateDialog()
就在其中被调用,因而我们很容易就可以知道两者的执行次序。
几类常见应用
普通对话框
public class CommonDialogFragment extends DialogFragment {
private static final String TAG = "CommonDialogFragment";
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
Dialog dialog = getDialog();
DisplayMetrics dm = DensityUtil.getDisplayMetrics(getActivity());
int width = dm.widthPixels;
if (dialog != null) {
dialog.getWindow().setLayout(width * 2 / 3, width / 2);
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
dialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));//去除多余的padding
}
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
View view = inflater.inflate(R.layout.time, container, false);
ListView mListView = (ListView) view.findViewById(R.id.listview);
mListView.setAdapter(new MyAdapter(getActivity()));
return view;
}
}
这类很简单,一般只要重写 onCreateView()
,和Fragment
中的做法一样。只是需要额外配置对话框的一些设置,我们下面再统一叙述。
警告对话框
public class MyAlerDialog extends DialogFragment {
private static final String TAG = "RoundCornerDialog";
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new AlertDialog.Builder(getActivity()).setMessage("我是对话框").setTitle("警告").setIcon(R.mipmap.ic_launcher).create();
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
Dialog dialog = getDialog();
DisplayMetrics dm = DensityUtil.getDisplayMetrics(getActivity());
int width = dm.widthPixels;
if (dialog != null) {
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
dialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));//去除多余的padding
dialog.getWindow().setLayout(width * 2 / 3, width / 2);
}
return super.onCreateView(inflater, container, savedInstanceState);
}
}
圆角对话框
圆角对话框与上述两张对话框创建流程无异,只需对布局额外加上一层drawable绘制的圆角背景层即可
注意: 若加了圆角背景层仍没有圆角化,那么你需要对圆角背景层再 加上padding属性即可解决。
对话框的配置
去标题化
- 调用
setStyle()
传入STYLE_NO_TITLE
,必须在Dialog
创建之前调用方可生效。 - 定义自己的主题,设置
windowNoTitle
属性为true
背景透明化
getDialog().getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)。
我们一般都会加上,没有设置有可能出现多余的padding,它可让整个Window
透明化。
设置Dialog的尺寸
由于 Dialog
属于 Window
级别,属于独立窗体,因而要改变 Dialog
的尺寸实际上就上改变窗体 Window
的尺寸,而如果你对布局修改尺寸是毫无效果的。
我们通过getDialog().getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
来设置对话框具体宽高或全屏就够了。
设置对话框方位
实际还是控制window
这个对象,调用它的方法setGravity()
即可。
注意: 但凡涉及到获取对话框所在的
window
对象时,都必须在对话框已经创建了之后调用。
配置style样式定制主题
我们查看源码可知道对话框的默认主题基础自Theme.Dialog
,那么我们便可以继承整个主题,修改默认主题中的属性值来达到我们想要的效果,下面贴出对话框默认的主题。
<style name="Theme.Dialog">
<!--配置window有无边框-->
<item name="windowFrame">@null</item>
<!--配置window标题样式-->
<item name="windowTitleStyle">@style/DialogWindowTitle</item>
<!--配置window背景层-->常用
<item name="windowBackground">@drawable/panel_background</item>
<!--配置window是否浮在上面-->
<item name="windowIsFloating">true</item>
<!--配置window是否浮在上面-->
<!--配置window有无标题 --> 常用
<item name="windowNoTitle">true</item>
<!--配置window进入退出动画 --> 常用
<item name="windowAnimationStyle">@style/Animation.Dialog</item>
<!--配置window外部是否可触摸-->
<item name="windowCloseOnTouchOutside">@bool/config_closeDialogWhenTouchOutside</item>
....此处省略部分属性
</style>
总结
首先,介绍了使用DialogFragment
的缘由以及好处,再来谈到了它的生命周期,这也是之所以使用这种方式来呈现对话框的关键所在。再接下来,我们从源码的角度,详细剖析了各个方法的作用、联系,以及调用的时机以及注意事项。再来介绍了DialogFragment
的几种常见的应用,也可以说是常用的代码模版了。最后,介绍了配置对话框的一些常用的手段以及注意事项。
感谢
感谢您看到这里,期望留下您的印迹。