DialogFramengt学习
一.我们为什么要使用DialogFragment?
-
生命周期管理:DialogFragment具有自己的生命周期,与Activity的生命周期相互独立。这使得在旋转屏幕或配置更改等情况下,DialogFragment能够更好地处理状态保存和恢复。而Dialog需要手动管理这些生命周期事件。
-
灵活的界面管理:DialogFragment可以在布局文件中定义自己的用户界面,使界面的创建和管理更加灵活。你可以使用XML布局或通过编程方式构建界面。而Dialog通常需要使用代码编程方式创建和配置界面。
-
与Fragment交互:DialogFragment可以与其所属的Activity和其他Fragment进行交互。它可以通过回调接口、观察者模式或使用Fragment的方法来实现与Activity和其他Fragment之间的通信。而Dialog通常无法直接与Activity和其他Fragment进行交互。
-
设备兼容性:DialogFragment提供了更好的设备兼容性。它可以适应不同尺寸的屏幕,并在平板电脑和手机上提供一致的用户体验。而Dialog的样式和大小可能在不同设备上显示不一致。
-
管理和复用:DialogFragment可以由FragmentManager进行管理,使得显示、隐藏、替换和移除更加方便。它也可以通过标签进行识别和查找,便于在需要时重新显示或操作。而Dialog通常需要手动管理其显示和隐藏,并且难以实现复用。
综上所述,DialogFragment相较于Dialog具有更好的生命周期管理、界面灵活性、与Fragment交互、设备兼容性和管理复用等方面的优势。因此,在大多数情况下,推荐使用DialogFragment来实现对话框式的用户界面。
二.简单的使用
DialogFragment的本质是使用Fragmnet来管理Dialog的生命周期。
第一步:创建对话框布局文件ldialig_fragment_custom.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="#ECE1E1"
android:gravity="center_horizontal"
android:orientation="vertical"
android:paddingTop="20dp">
<TextView
android:id="@+id/tv_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="第一个Dialog"
android:textSize="20sp"
android:textStyle="bold" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="20dp"
android:background="#999999" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_close"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_gravity="bottom"
android:layout_weight="1"
android:gravity="center"
android:text="取消"
android:textSize="18sp" />
<View
android:layout_width="1dp"
android:layout_height="match_parent"
android:background="#999999" />
<TextView
android:id="@+id/tv_confirm"
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_gravity="bottom"
android:layout_weight="1"
android:gravity="center"
android:text="确定"
android:textColor="#3085CE"
android:textSize="18sp" />
</LinearLayout>
</LinearLayout>
注意:这里的根布局里设置没有生效,我们需要去代码里面设置。
第二步:自定义DialogFragmnet
public class CustomDialogFragment extends DialogFragment {
private View mView;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
mView = inflater.inflate(R.layout.dialig_fragment_custom, container, false);
return mView;
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
initWindow();
initView();
}
@Override
public void onResume() {
super.onResume();
}
@Override
public void onDismiss(@NonNull DialogInterface dialog) {
super.onDismiss(dialog);
}
private void initWindow() {
if (getDialog() != null) {
//初始化window相关表现
Window window = getDialog().getWindow();
//设置window宽高(单位px)
window.getAttributes().width = 700;
//设置window位置:居中
window.getAttributes().gravity = Gravity.CENTER;
}
}
private void initView() {
TextView button1 = mView.findViewById(R.id.tv_confirm);
button1.setOnClickListener(v -> {
Toast.makeText(getContext(), "mgs", Toast.LENGTH_SHORT).show();
dismiss();
});
}
}
我这里自定义的很简单,应该也不难理解。
第三步:在Activity中展示DialogFragment
CustomDialogFragment customDialogFragment = new CustomDialogFragment();
customDialogFragment.showNow(getSupportFragmentManager(), "customDialogFragment");
以上三步,就可以实现dialog的弹出。
其他效果:
-
点击返回键不消失
DialogFragment
customDialogFragment.getDialog().setOnKeyListener(new DialogInterface.OnKeyListener() { @Override public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) { return keyCode == KeyEvent.KEYCODE_BACK; } });
-
点击弹窗外部,弹窗消失
if (customDialogFragment.getDialog() != null) { customDialogFragment.getDialog().setCancelable(true); }
还有很多…比如弹出动画,可以根据自己需要在网上搜索
三.分析源码
1.方法的作用:用于在 DialogFragment 内部进行 dismiss 操作。
- boolean allowStateLoss :是否允许状态丢失
- boolean fromOnDismiss:是否由onDismiss方法触发
private void dismissInternal(boolean allowStateLoss, boolean fromOnDismiss) {
if (mDismissed) {
return;
}
//mDismissed标志设置为 true,表示已经进行了 dismiss 操作
//mShownByMe 标志设置为 false,表示 DialogFragment 不再处于显示状态。
mDismissed = true;
mShownByMe = false;
if (mDialog != null) {
mDialog.setOnDismissListener(null);
mDialog.dismiss();
if (!fromOnDismiss) {
//判断当前线程是否为主线程
if (Looper.myLooper() == mHandler.getLooper()) {
onDismiss(mDialog);
} else {
mHandler.post(mDismissRunnable);
}
}
}
//mViewDestroyed 标志设置为 true,表示 DialogFragment 的视图已被销毁
mViewDestroyed = true;
//DialogFragment 在回退栈中有对应的 ID,进行弹出栈,否则提交事物
//
if (mBackStackId >= 0) {
getParentFragmentManager().popBackStack(mBackStackId,
FragmentManager.POP_BACK_STACK_INCLUSIVE);
mBackStackId = -1;
} else {
FragmentTransaction ft = getParentFragmentManager().beginTransaction();
ft.remove(this);
if (allowStateLoss) {
ft.commitAllowingStateLoss();
} else {
ft.commit();
}
}
}
1.什么是提交事物?
如果只给出一个提交事物的名词,也行并不能理解这块代码。那么什么是提交事物?
提交事务是将对 Fragment 的操作(添加、移除、替换等)应用到 Fragment 管理器中的一种方式。事务用于将一系列的 Fragment 操作封装在一起,并确保这些操作以原子方式执行,从而保持 Fragment 管理器的一致性。
getSupportFragmentManager().beginTransaction()
.replace(R.id.fragment_container, fragment)
.commit();
上面的代码是不是很眼熟,fragment的动态替换。
通过提交事务,我们可以保证 Fragment 操作的原子性、批量执行、事务回退和状态保存等功能,提供了一种可靠和一致的方式来管理 Fragment 的添加、移除、替换等操作,并确保 Fragment 管理器的正确性和稳定性。
2.commitAllowingStateLoss()和commit()
这两个都是提交事物,但是有一些不同,从我们allowStateLoss的判断就可以看出来,对于commitAllowingStateLoss()
来说,允许在状态丢失的情况下提交事务,并在 Activity 重建后尝试恢复事务状态。举例的话,就是我们屏幕旋转之后,会和我们旋转之前的状态一样。对于commit()
来说,如果在事务提交之前发生 Activity 的状态保存过程(如屏幕旋转)或进程被销毁重建,那么在恢复状态时,如果事务尚未执行完毕,会抛出 IllegalStateException 异常,从而导致事务丢失。
-
commit()
提交事务时,如果在事务提交之前发生状态保存或进程重建,可能会导致事务丢失并抛出异常。
-commitAllowingStateLoss()
提交事务时,在状态丢失的情况下也能够提交事务,但可能会导致一些副作用。如无法通过事务回退来撤销操作
- 如果事务的状态对于应用程序的正确性至关重要,应优先考虑使用commit()
,并通过适当的处理来避免状态丢失。仅在确定状态丢失不会造成严重问题的情况下,才考虑使用commitAllowingStateLoss()
。
3.为什么fragment已经不在回退栈内,为什么还需要提交事物?
- 如果你仔细看代码就会发现上述的问题,fragment不在回退栈,那么当前的实例应该已经销毁了,生命周期已经结束了。我们还提交事物干嘛?
提交事务的目的不是为了维护 DialogFragment
的生命周期,而是为了确保对 FragmentManager
的操作正确执行。
即使 DialogFragment
已经被销毁,调用 FragmentTransaction
的 remove()
方法仍然是必要的。这是因为该操作会通知 FragmentManager
移除相关的 Fragment
,并更新其内部状态,以便在下一次事务提交或回退栈操作时得到正确的处理。
提交事务不仅仅用于处理当前存在的 Fragment
实例,它还用于更新 FragmentManager
的状态和维护其内部数据结构。这对于后续的事务操作、回退栈管理和其他操作是必要的。因此,即使 DialogFragment
已被销毁,提交事务仍然是需要的。
总结起来,提交事务的目的不是为了维护 DialogFragment
的生命周期,而是为了确保对 FragmentManager
的操作能够正确执行和同步。即使 DialogFragment
不在回退栈中并已被销毁,提交事务仍然是必要的,以确保 FragmentManager
的状态和内部数据结构保持一致和可靠。
- 那我们什么时候会执行下面的提交事物的方法呢?
我理解的一种情况:连续进行dismiss操作的情况下可能会发生。因为这个代码块并没有进行加锁操作。所以可能会有同步操作。
2.方法的作用:显示dialogFragmnet
public void showNow(@NonNull FragmentManager manager, @Nullable String tag) {
mDismissed = false;
mShownByMe = true;
FragmentTransaction ft = manager.beginTransaction();
ft.add(this, tag);
ft.commitNow();
}
他还有几个相似的方法:
public void show(@NonNull FragmentManager manager, @Nullable String tag) {
mDismissed = false;
mShownByMe = true;
FragmentTransaction ft = manager.beginTransaction();
ft.add(this, tag);
ft.commit();
}
public int show(@NonNull FragmentTransaction transaction, @Nullable String tag) {
mDismissed = false;
mShownByMe = true;
transaction.add(this, tag);
mViewDestroyed = false;
mBackStackId = transaction.commit();
return mBackStackId;
}
- 实现的效果都是一样的,我们这里优先使用showNow方法,至于为什么?
1.show要比showNow稍微“慢”一点,这导致调用show了后,立刻修改dialog中的view(例如textView修改字符内容)会崩溃,而showNow不会
先在FirstDialogFragment中添加方法:
fun setContent(text: String) {
tv_content.text = text
}
以下代码会崩溃:
val firstDialog = FirstDialogFragment()
firstDialog.show(supportFragmentManager, "First")
firstDialog.setContent("Hello")
以下代码则正常执行(对话框内容被修改为Hello):
val firstDialog = FirstDialogFragment()
firstDialog.showNow(supportFragmentManager, "First")
firstDialog.setContent("Hello")