浅谈 Android 中的 DialogFragment

文章目录
  1. 1. 简介 DialogFragment
  2. 2. Demo 用法
  3. 3. 深入源码
  4. 4. 参考文献

If you don’t like where you are, change it. You’re not a tree.

Android 开发中,Dialog 十分常见。如今,在官方不推荐直接使用 Dialog 类创建对话框的前提下,且来浅谈一谈 Android 中创建 Dialog 的 DialogFragment。

简介 DialogFragment

DialogFragment 自 Android 3.0 引入,是一种特殊的 Fragment,参见之前的两篇博客:重温 Fragment 分析一:生命周期重温 Fragment 分析二:使用及注意事项。DialogFragment 能够在 Activity 的内容之上展示一个特定的对话框,此 Fragment 包含一个 Dialog 对象,其基于 Fragment 的状态作展示。此外,控制 dialog 如决定展示、隐藏和去除时机,应调用这里的 API,而不是直接调用 Dialog。

用 DialogFragment 来管理对话框,当旋转屏幕和按下后退键时,能很好地管理其生命周期,因为它和普通的 Fragment 有着基本一致的生命周期。并且,DialogFragment 中,Dialog 可作为内嵌的组件重用。

Demo 用法

Demo 地址:DialogFragmentDemo

I. 先看一个简单的 OK Dialog 布局,只有一个 TextView 标题、一个 EditText 编辑框和一个 Button 按钮,fragment_dialog_ok.xml 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="66dp"
android:paddingRight="66dp">


<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="Title"
android:textSize="16sp"/>


<EditText
android:id="@+id/et_input"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:ems="10"/>


<Button
android:id="@+id/btn_key"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="10dp"
android:text="OK"/>

</LinearLayout>

TestDialogFragment 如下:

1
2
3
4
5
6
7
8
9
public class TestDialogFragment extends DialogFragment {

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
getDialog().requestWindowFeature(Window.FEATURE_NO_TITLE);
return inflater.inflate(R.layout.fragment_dialog, null);
}

}

如下图所示:

II. 再看继承自 DialogFragment 的自定义 LoginDialogFragment 中,重写 onCreateDialog() 方法创建 Dialog,使用 AlertDialog 创建一个登录对话框。先看布局文件 fragment_dialog_login.xml 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">


<EditText
android:id="@+id/et_username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Username"
android:inputType="textEmailAddress"/>


<EditText
android:id="@+id/et_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Password"
android:inputType="textPassword"/>

</LinearLayout>

LoginDialogFragment 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class LoginDialogFragment extends DialogFragment {

@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
View view = getActivity().getLayoutInflater().inflate(R.layout.fragment_dialog_login, null);
builder.setView(view)
.setPositiveButton("Sign in"
, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {

}
})
.setNegativeButton("Cancel", null);
return builder.create();
}

}

如下图所示:

除此之外,针对 DialogFragment,我们可以实现:

通过重写 onCreateView() 方法,Dialog 在大屏幕上以对话框的形式显示,在小屏幕上直接嵌入当前的 Activity 里;

Dialog 中,编辑框中的内容在移动设备旋转时,仍然存在不消失。而传统的以 AlertDialog 创建的 Dialog,在屏幕旋转时,对话框不保存用户输入的值且消失,同时还会报异常,Activity 销毁前不允许 Dialog 没有关闭。Log 信息:

1
Activity com.iamasoldier6.dialogfragmentdemo.MainActivity has leaked window [email protected] that was originally added here

主要代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private void showPureAlert() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
View view = getLayoutInflater().inflate(R.layout.fragment_dialog_login, null);
builder.setView(view)
.setPositiveButton("Sign in"
, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {

}
})
.setNegativeButton("Cancel", null)
.show();
}

深入源码

重点关注 DialogFragment 中常用的几个方法,首先看

show()

1
2
3
4
5
6
7
public void show(FragmentManager manager, String tag) {
mDismissed = false;
mShownByMe = true;
FragmentTransaction ft = manager.beginTransaction();
ft.add(this, tag);
ft.commit();
}
1
2
3
4
5
6
7
8
public int show(FragmentTransaction transaction, String tag) {
mDismissed = false;
mShownByMe = true;
transaction.add(this, tag);
mViewDestroyed = false;
mBackStackId = transaction.commit();
return mBackStackId;
}

两种显示方式都是通过 tag 的方式将 DialogFragment 以事务的形式提交。其中,第二种方式采用创建过的 transaction,同时返回了一个 int 类型的 mBackStackId 值,它作为将 DialogFragment 压入回退栈的编号,初始值为 -1。再看

dismiss()

1
2
3
public void dismiss() {
dismissInternal(false);
}
1
2
3
public void dismissAllowingStateLoss() {
dismissInternal(true);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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 关闭的时候,会检查堆栈里是否有其他对象,若有就 pop 出来,否则就直接 remove 和 commit,即用到了上面提到的一个回退栈编号 mBackStackId,用 show(FragmentTransaction transaction, String tag) 方法进行压栈,故取消对话框要在这里作判断。若已压栈的,则要退出由 Activity 管理的回退栈;若是用 show(FragmentTransaction transaction, String tag),则无需出栈,只需要在 FragmentTransaction 中 remove 掉。注意,关于 commitAllowingStateLoss() 方法,其是在 Activity 保存状态即调用 onSaveInstanceState() 之后调用的。

此外,DialogFragment 可以设置自己的 style,看

setStyle()

1
2
3
4
5
6
7
8
9
public void setStyle(int style, int theme) {
mStyle = style;
if (mStyle == STYLE_NO_FRAME || mStyle == STYLE_NO_INPUT) {
mTheme = com.android.internal.R.style.Theme_DeviceDefault_Dialog_NoFrame;
}
if (theme != 0) {
mTheme = theme;
}
}

调用该方法为对话框设置样式和主题。要注意的是,该方法的调用要在 onCreateView() 之前,否则设置的 style 和 theme 将不起作用。

至此,浅谈 Android 中的 DialogFragment 到此结束。

本人才疏学浅,如有疏漏错误之处,望读者中有识之士不吝赐教,谢谢。

1
Email: [email protected] / WeChat: Wolverine623

您也可以关注我个人的微信公众号码农六哥第一时间获得博客的更新通知,或后台留言与我交流

参考文献

1.https://developer.android.com/reference/android/app/DialogFragment.html

2.http://blog.csdn.net/lmj623565791/article/details/37815413