使用DialogFragment代替Dialog
使用DialogFragment代替Dialog
是这样,用了很久的一个Dialog工具类,结果今天发现了一个bug,尝试着搜索发现大家都已经用DialogFragment了,官方也推荐这么做,猛然醒悟原来自己已经过时这么久了。现在就来试试吧。
DialogFragment是什么
DialogFragment从它的源码得知,它继承了Fragment,其实是一个比较特殊的Fragment。那么它相对于普通的Dialog有什么不同,谷歌又为什么推荐我们使用它呢,它相对于普通的Dialog有什么优点呢。
使用过它之后用自己的感受描述:
- 它的生命周期很清晰,方便写复杂的逻辑
- 它于Activity的生命周期是绑定的,Activity消失,DialogFragment也会消失。
- 它可以很简单的控制弹窗的布局。
总结就是dialogfragment能更好的管理dialog的展示与消失,以及在屏幕旋转时的一些状态保存问题。
DialogFragment的踩坑
即使它有很多的优点,但使用不当时,仍然会有很多坑。
我遇到了很多奇奇怪怪的问题。 比如
- Fragment already added 异常
- 快速的显示消失,无法消失的异常
当然除此之外我们还可能有以下需求:
- 设置对话框的大小
- 设置弹出对话框时背景灰色或者透明
下面我们就来一一实现。
如何实现DialogFragment
重点来关注两个方法。
- onCreateDialog 新建一个Dialog即可使用
- onCreateView 自定义一个Dialog界面
onCreateDialog
做一个简单的对话框
public class ConfirmDialog extends DialogFragment {
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return super.onCreateView(inflater, container, savedInstanceState);
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog dialog = new AlertDialog.Builder(getActivity())
.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();
return dialog;
}
}
显示它
ConfirmDialog dialog = new ConfirmDialog();
dialog.show(getSupportFragmentManager(), "dialogTag");
onCreateView
使用自定义视图做一个加载框。
这里有一个非常重要的地方,我出现了 Fragment already added
的问题,意思就是重复添加了,那么为什么会出现重复添加呢,因为我最初的代码是使用isAdded
和isVisibility
进行判断,但是当快速执行的时候,这两个方法并不准确。
正确的做法有两种。
- 添加事务时先进行移除
beginTransaction().remove(this).commit()
- 使用变量进行判断,不能使用
isAdded
private boolean isShowFragment = false;
@Override
public void show(@NonNull FragmentManager manager, @Nullable String tag) {
// 解决bug:Android java.lang.IllegalStateException: Fragment already added
if (this.isShowFragment) {
return;
}
this.isShowFragment = true;
super.show(manager, tag);
}
@Override
public void dismiss() {
super.dismiss();
this.isShowFragment = false;
}
// 避免有些手机兼容性问题,isShowFragment未变成false而导致无法二次打开
@Override
public void onDestroy() {
super.onDestroy();
this.isShowFragment = false;
}
最后直接放代码,封装好的Loading框
LoadingDialog 对话框 可以看到代码中对bug的处理:在每个add事务前增加一个remove事务,防止连续的add。
public class LoadingDialog extends DialogFragment
implements DialogInterface.OnKeyListener {
/**
* 加载框提示信息 设置默认
*/
private final String hintMsg = "加载中...";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setStyle(DialogFragment.STYLE_NO_TITLE, R.style.MyDialog);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Dialog dialog = getDialog();
// 设置背景透明
if (dialog.getWindow() != null)
dialog.getWindow().setBackgroundDrawableResource(android.R.color.transparent);
// 去掉标题
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
dialog.setCanceledOnTouchOutside(false);
View loadingView = inflater.inflate(R.layout.dialog_loading, container);
TextView hintTextView = loadingView.findViewById(R.id.tv_ios_loading_dialog_hint);
hintTextView.setText(hintMsg);
//不响应返回键
dialog.setOnKeyListener(this);
return loadingView;
}
@Override
public void show(FragmentManager manager, String tag) {
try {
//在每个add事务前增加一个remove事务,防止连续的add
manager.beginTransaction().remove(this).commit();
super.show(manager, tag);
} catch (Exception e) {
//同一实例使用不同的tag会异常,这里捕获一下
e.printStackTrace();
}
}
@Override
public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
// return keyCode == KeyEvent.KEYCODE_BACK;
// 允许按back键取消Loading
return false;
}
}
代理管理类
public class GlobalDialogManager {
private LoadingDialog mLoadingDialog;
private GlobalDialogManager() {
init();
}
public static GlobalDialogManager getInstance() {
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder {
private static final GlobalDialogManager INSTANCE = new GlobalDialogManager();
}
public void init() {
if (mLoadingDialog == null) {
mLoadingDialog = new LoadingDialog();
}
}
/**
* 展示加载框
*/
public synchronized void show(FragmentManager manager) {
if (manager != null && mLoadingDialog != null) {
mLoadingDialog.show(manager, "tag");
}
}
/**
* 隐藏加载框
*/
public synchronized void dismiss(FragmentManager manager) {
if (mLoadingDialog != null && !manager.isDestroyed()) {
mLoadingDialog.dismissAllowingStateLoss();
}
}
}
使用它
if (getContext() != null)
GlobalDialogManager.getInstance().show(((Activity) getContext()).getFragmentManager());
if (getContext() != null)
GlobalDialogManager.getInstance().dismiss(((Activity) getContext()).getFragmentManager());
这里判断getContext()很有必要,避免Activity消失了,getContext为空的bug。
背景不变暗设置一个style属性行啦。
<item name="android:backgroundDimEnabled">false</item><!--activity不变暗-->