如何防止 BottomSheetDialogFragment 在导航到另一个片段后消失?

How to prevent BottomSheetDialogFragment from dismissing after a navigation to another fragment?

我正在我的应用程序上使用 NavigationComponent

我有一个特定的流程,在点击 BottomSheetDialogFragment 的按钮后,应用程序应该导航到另一个片段。但是当那个片段被弹出时,我需要导航回之前的 BottomSheetDialogFragment.

由于某种原因,BottomSheetDialogFragment 被自动关闭。

Frag A : click on a button  
Frag A -> Dialog B : click on a button  
Frag A -> Dialog B -> Frag C : pop Frag C from the stack  
Frag A : Dialog B was automatically dismissed =;/  

如何防止这种忽视?


问:为什么我需要BottomSheetDialogFragment不解雇?
A:我通过一个LiveData来监听打开片段的结果。由于 BottomSheetDialogFragment 的关闭,它从未收到结果。

这是不可能的。对话目标实现了 FloatingWindow interface,它指出:

Destinations that implement this interface will automatically be popped off the back stack when you navigate to a new destination.

因此,当您导航到 <fragment> 目的地时,预计对话目的地会自动从返回堆栈中弹出。 不是在多个对话框目标之间导航时的情况(这些目标可以堆叠在一起)。

This issue 进一步解释了这里的限制,即:

  1. Dialogs are separate windows that always sit above your activity's window. This means that the dialog will continue to intercept the system back button no matter what state the underlying FragmentManager is in or what FragmentTransactions you do.

  2. Operations on the fragment container (i.e., your normal destinations) don't affect dialog fragments. Same if you do FragmentTransactions on a nested FragmentManager.

因此,一旦您导航到 <fragment> 目的地,系统后退按钮实际工作的唯一方法是弹出所有浮动 windows(否则它们会在之前拦截后退按钮其他任何东西)因为那些 windows 总是浮动在内容之上。

这不是导航组件强加的限制 - 同样的问题适用于 BottomSheetDialogFragment 关于片段后退堆栈和系统后退按钮的任何用法。

您不希望关闭对话框,因为它会停留在下一个目的地的顶部。

通过“听结果”,如果你的意思是findNavController().currentBackStackEntry.savedStateHandle.getLiveData(MY_KEY)

那么您应该能够将结果设置为 previousBackStackEntry,因为它会在您的对话框之前为您提供目的地。

Frag A : click on a button 
Frag A -> Dialog B : click on a button (automatically popped-off)
Frag A -> Dialog B -> Frag C : pop Frag C from the stack
  

然后

class FragA : Fragment() {
    
    ...

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        
        ...

        findNavController().currentBackStackEntry.savedStateHandle?.getLiveData<MyResult>(MY_KEY).observe(viewLifecycleOwner) {
           // get your result here
           // show Dialog B again if you like ?
        }
    }
}

class FragC : Fragment() {

    ...

    private fun setResultAndFinish(result: MyResult) {
        findNavController().apply { 
            previousBackStackEntry?.savedStateHandle?.set(MY_KEY, result)
            popBackStack()
        }
    }
    
}

@ianhanniballake 指出这是不可能的。

但这可以通过将 fragment C 设为 DailogFragment 而不是普通 Fragment 来实现,但这需要一些努力才能使其表现得像普通片段。

在这种情况下,BC 都是对话框,因此它们将共享相同的返回堆栈。因此,当返回堆栈从 C 返回到 B 时,您仍然会看到 BottomSheetDialgFragment B 显示。

要修复 C 的有限 window,请使用以下主题:

<style name="DialogTheme" parent="Theme.MyApp">
    <item name="android:windowNoTitle">true</item>
    <item name="android:windowFullscreen">false</item>
    <item name="android:windowIsFloating">false</item>
</style>

其中 Theme.MyApp 是您应用的主题。

然后通过覆盖 getTheme():

将其应用于 C
class FragmentC : DialogFragment() {

    //.....

    override fun getTheme(): Int = R.style.DialogTheme
    
}

您还需要将导航图中的 Cfragment 更改为 dialog:

<dialog
        android:id="@+id/fragmentC"
        android:name="....">
</dialog>

预览: