DialogFramengt学习分享

文章介绍了DialogFragment相对于普通Dialog的优势,包括生命周期管理、界面灵活性、与Fragment交互和设备兼容性,并展示了如何创建和使用DialogFragment的基本步骤。此外,还解析了DialogFragment的源码,特别是提交事务和显示DialogFragment的方法,强调了showNow方法在及时更新内容上的优势。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

DialogFramengt学习

一.我们为什么要使用DialogFragment?

  1. 生命周期管理:DialogFragment具有自己的生命周期,与Activity的生命周期相互独立。这使得在旋转屏幕或配置更改等情况下,DialogFragment能够更好地处理状态保存和恢复。而Dialog需要手动管理这些生命周期事件。

  2. 灵活的界面管理:DialogFragment可以在布局文件中定义自己的用户界面,使界面的创建和管理更加灵活。你可以使用XML布局或通过编程方式构建界面。而Dialog通常需要使用代码编程方式创建和配置界面。

  3. 与Fragment交互:DialogFragment可以与其所属的Activity和其他Fragment进行交互。它可以通过回调接口、观察者模式或使用Fragment的方法来实现与Activity和其他Fragment之间的通信。而Dialog通常无法直接与Activity和其他Fragment进行交互。

  4. 设备兼容性:DialogFragment提供了更好的设备兼容性。它可以适应不同尺寸的屏幕,并在平板电脑和手机上提供一致的用户体验。而Dialog的样式和大小可能在不同设备上显示不一致。

  5. 管理和复用: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的弹出。

其他效果:

  1. 点击返回键不消失DialogFragment

     customDialogFragment.getDialog().setOnKeyListener(new DialogInterface.OnKeyListener() {
                @Override
                public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
                    return keyCode == KeyEvent.KEYCODE_BACK;
                }
            });
    
  2. 点击弹窗外部,弹窗消失

      if (customDialogFragment.getDialog() != null) {
                customDialogFragment.getDialog().setCancelable(true);
            }
    

    还有很多…比如弹出动画,可以根据自己需要在网上搜索

三.分析源码

1.方法的作用:用于在 DialogFragment 内部进行 dismiss 操作。

  1. boolean allowStateLoss :是否允许状态丢失
  2. 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 已经被销毁,调用 FragmentTransactionremove() 方法仍然是必要的。这是因为该操作会通知 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")
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值