android 让导航中的DialogFragment在弹出回栈时不消失

x759pob2  于 2023-03-28  发布在  Android
关注(0)|答案(4)|浏览(321)

我有FragmentA、FragmentB和DialogFragment(BottomDialogFragment)。我将它们缩写为ABD
点击A中的按钮后会显示D,表示A -〉D
点击D中的按钮后,显示B,表示D -〉B
我在navigation.xml中配置它们

<fragment
        android:id="@+id/A"
        android:name="com.example.A">

    <action
        android:id="@+id/A_D"
        app:destination="@id/D" />
</fragment>


<dialog
        android:id="@+id/D"
        android:name="com.example.D">

    <action
        android:id="@+id/D_B"
        app:destination="@id/B" />
</dialog>

<fragment
        android:id="@+id/B"
        android:name="com.example.B">
</fragment>

现在,当我单击A中的按钮时,片段将跳转到D
然后我点击D中的按钮,片段将跳转到B
但是当我弹出B中的导航堆栈时,它将返回到A,并且D不显示。
我该怎么办?我希望D还存在于A的表面。

wa7juj8i

wa7juj8i1#

我该怎么办?我希望D仍然存在于A的表面上。
到目前为止,这是不可能的,因为对话框是在与活动/片段不同的窗口中处理的;这是因为Dialog实现了FloatingWindow interface
检查this answer以了解更多说明。
但要回答你的问题,有两种方法:
1.**方法一:**将片段B更改为DailogFragment,在这种情况下,BD都是对话框,因此当您弹出堆栈从B返回到D时,您仍然会看到D显示。
1.**方法2:**设置一个标志,如果从B返回到D,则重新显示D
实际上,方法2并不是很好,因为当您从D转到B时,它不会将D保留在后台堆栈中;这只是一个变通办法;此外,当对话框从B返回到D时,用户将看到对话框转换/淡入淡出动画;所以这一点也不自然。所以,这里只讨论方法1。

方法一详细:

优点:

  • 这是非常自然的,将保持回栈一样,你想。
    缺点:
  • DialogFragment B具有比正常片段/活动有限的窗口。
  • B不再是一个普通的片段,而是一个DialogFragment,因此您可能会遇到一些其他的限制。

要解决B的有限窗口,您可以使用以下主题:

<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()将其应用于B

class FragmentB : DialogFragment() {

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return layoutInflater.inflate(R.layout.fragment_b, container, false)
    }

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

你还需要将导航图中的B更改为对话框:

<dialog
        android:id="@+id/B"
        android:name="com.example.B">
</dialog>

预览:

iibxawm4

iibxawm42#

请参考link以获得完整的工作示例。
您需要利用NavigationUI全局操作(如here所述),以便能够导航“back”到目的地。将以下代码放入main_graph xml中:

<action android:id="@+id/action_global_fragmentD" app:destination="@id/fragmentD"/>

接下来,在你的活动中添加这些以捕获回按:

class MainActivity: AppCompatActivity {
...
    var backPressedListener: OnBackPressedListener? = null
    override fun onBackPressed() {
        super.onBackPressed()
        backPressedListener?.backHaveBeenPressed()
    }
}
interface OnBackPressedListener {
   fun backHaveBeenPressed()
}

这就像delegatesin swift
接下来在FragmentB中,添加以下内容:

class FragmentB: Fragment(), OnBackPressedListener {
     override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
     ): View? {
        // Inflate the layout for this fragment
        (activity as MainActivity).backPressedListener = this
        return inflater.inflate(R.layout.fragment_b, container, false)
    }

    override fun backHaveBeenPressed() {
        // show Dialog
        findNavController().navigate(R.id.action_global_fragmentD)
    }   
 }

然后您可以根据需要导航回DialogFragment,这种方式不使用popBackStack,因为您的 use case 是一个自定义行为,不是NavigationUI框架处理的(您需要实现它)。

ohfgkhjo

ohfgkhjo3#

不需要使用controller.popBack()弹出堆栈,因为堆栈是由导航库管理的。请注意,堆栈基于LIFO操作,因此这就是为什么片段消失的原因。
您需要添加更多导航图操作:

<?xml version="1.0" encoding="utf-8"?>
 <navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/navigation"
    app:startDestination="@id/A">

    <fragment
        android:id="@+id/A"
        android:name="com.example.A"
        android:label="A" >
        <action
            android:id="@+id/action_A_to_D"
            app:destination="@id/D" />
    </fragment>

    <dialog
        android:id="@+id/D"
        android:name="com.example.D"
        android:label="D" >
        <action
            android:id="@+id/action_D_to_B"
            app:destination="@id/B" />
        <action
            android:id="@+id/action_D_to_A"
            app:destination="@id/A" />
    </dialog>

    <fragment
        android:id="@+id/B"
        android:name="com.example.B"
        android:label="B" >
        <action
            android:id="@+id/action_B_to_D"
            app:destination="@id/D" />
    </fragment>
</navigation>

然后,在“片段”对话框中添加以下内容:
对于OK/Yes:--

private fun doNav() {
        NavHostFragment.findNavController(this).navigate(R.id.action_fragmentD_to_fragmentB)
    }

取消/编号:--

private fun doBackNav() {
        NavHostFragment.findNavController(this).navigate(R.id.action_fragmentD_to_fragmentA)
    }

最后,在B片段中,覆盖back按钮并执行以下操作:

Navigation.findNavController(requireView()).navigate(R.id.action_fragmentB_to_fragmentD)
hkmswyz6

hkmswyz64#

这里是解决这个问题的另一个方法。像大多数方法一样,这个方法也有一些缺点。我在下面的“缺点”一节中概述了缺点。

理论

解决方案涉及一些“较旧”的Android机制,即早于导航控制器的机制(我们并不总是有导航控制器)。解决方案围绕以下几个事实:
1.所有的片段都存在于一些FragmentManager中。导航控制器并不神奇,它仍然在幕后使用FragmentManager。事实上,你可以把导航控制器看作是FragmentManager的 Package 器。
1.所有的fragment都有它自己的小FragmentManager。你可以通过fragment中的childFragmentManager访问它。在childFragmentManager上启动的任何fragment都被认为是该fragment的孩子。
1.当一个片段被移动到“backstack”时,它的所有子元素都会随之移动。
1.当一个片段被恢复时,它的子片段也被恢复。
有了这四个事实,我们可以制定一个解决方案。
我们的想法是,如果我们在一个片段的childFragmentManager上显示所有DialogFragment,那么我们就可以保持导航到其他片段的能力,而不会出现任何对话框相关的问题。这是因为当我们从FragA导航到FragC时,FragA的所有孩子都被移动到后台堆栈。由于我们使用childFragmentManager启动了DialogFragmentDialogFragment也会被自动忽略。
现在,当用户移回我们的片段(FragA)时,我们的DialogFragment再次显示,因为FragA的childFragmentManager也被恢复了。我们的DialogFragment位于childFragmentManager中。

实现

现在我们知道了如何解决这个问题,让我们开始实现它。
为了简单起见,我们假设我们有片段FragAFragC以及对话框DialogB
第一件事是,尽管Navigation组件很好,但如果我们想这样做,我们不能使用它来启动对话框。如果你使用安全参数,你可以继续获得它的好处,因为技术上安全的参数并没有绑定到Navigation组件。下面是一个启动对话框B的例子:

// inside FragA
fun onLaunchBClick() {
  val parentFragment = parentFragment ?: return
  
  DialogB()
    .apply {
        // we can still use safe args
        arguments = DialogBArgs(myArg1, myArg2).toBundle()
    }
    .show(parentFragment?.childFragmentManager, "DialogB")
}

现在我们可以让DialogB启动FragC,但有一个问题。因为我们使用的是childFragmentManager,所以导航控制器实际上看不到DialogB。这意味着对于导航控制器来说,我们正在从FragA启动FragC。如果在导航图中有多个到DialogB的边,这可能会产生问题。解决这个问题的方法是将DialogB的所有方向都设置为全局。这是这个解决方法的最终缺点。在这种情况下,我们可以声明一个全局操作给FragC,并通过

// inside DialogB
fun onLaunchCClick() {
  val direction = NavMainDirections.actionGlobalFragC()
  findNavController().navigate(direction)
}

缺点

因此,这种方法有一些明显的缺点。最大的缺点是对话框可以导航到的所有片段都应该声明为全局操作。唯一的异常值是对话框是否只有一条边。如果对话框只有一条边,并且不太可能添加新的边,那么从技术上讲,您可以将操作添加到它唯一的父片段中。
例如,如果DialogC可以启动FragmentCFragmentD,并且DialogC可以从FragmentAFragmentZ(2个边)启动,则DialogC必须使用全局操作来启动FragmentCFragmentD
另一个缺点是我们不能再使用导航控制器来启动需要启动其他非对话框片段的对话框片段。这个缺点是温和的,因为我们至少仍然可以使用安全参数。

相关问题