Android JetPack多堆栈导航

15
我正在使用Jetpack Navigation 版本1.0.0-alpha04 与底部导航。它能够工作,但是导航不正确。例如,如果我有选项卡A和选项卡B,从选项卡A进入页面C,然后从那里转到选项卡B并再次返回到选项卡A,我将在选项卡A中看到根片段而不是页面C,这不是我想要的结果。
我正在寻找一个解决方案,为每个选项卡设置不同的堆栈,以便当我回到选项卡时保留每个选项卡的状态。此外,我不想将所有这些片段保存在内存中,因为这会对性能产生不良影响。在Jetpack Navigation之前,我使用了这个库https://github.com/ncapdevi/FragNav,它完全符合我的需求。现在我正在寻找与Jetpack Navigation相同的东西。

你能否发布你的activity的onCreate和OnNavigationItemSelectedListener代码?这将有助于了解你已经尝试过什么。 - J. Jefferson
在这个上下文中,“page”是什么?另一个活动吗?还是选项卡内的视图?也许是分页器? - Nick Cardoso
@NickCardoso 的页面是一个片段,在每个选项卡中都有根片段,当发生某些事件时,我们会进入该选项卡的下一个片段。 - Alireza Ahmadi
@Reza,你找到任何解决方案了吗? - Armin
@Armin 还没有!不过我对更好的答案很感兴趣。如果你找到了什么,请也告诉我一声。 - Alireza Ahmadi
你找到任何解决方案了吗? - hosseinAmini
3个回答

16

编辑2: 虽然目前仍然没有一流的支持,但是 Google 现在已经更新了他们的示例,展示了他们认为现在应该如何解决这个问题: https://github.com/googlesamples/android-architecture-components/tree/master/NavigationAdvancedSample


主要原因是您只使用了一个 NavHostFragment 来保存整个应用程序的返回栈。

解决方案是每个选项卡应该持有自己的返回栈。

  • 在主布局中,使用一个 FrameLayout 包装每个选项卡片段。
  • 每个选项卡片段都是一个 NavHostFragment 并包含自己的导航图,以使每个选项卡片段具有自己的返回栈。
  • BottomNavigationView 添加一个 BottomNavigationView.OnNavigationItemSelectedListener 来处理每个 FrameLayout 的可见性。

这也解决了您关于“我不喜欢将所有这些片段保留在内存中”的问题,因为默认情况下,使用 NavHostFragment 进行导航时,它使用 fragmentTransaction.replace(),即您将始终只有与 NavHostFragment 相同数量的片段。其余部分就在你的导航图的返回栈中。

编辑: Google 正在开发本地实现https://issuetracker.google.com/issues/80029773#comment25


更详细的内容

假设您有一个带有 2 个菜单选项的 BottomNavigationView,即 DogsCats

<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:id="@+id/dogMenu"
        .../>

    <item android:id="@+id/catMenu"
        .../>
</menu>

接下来你需要两个导航图,例如 dog_navigation_graph.xmlcat_navigation_graph.xml

dog_navigation_graph 可能长这样:

<navigation
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/dog_navigation_graph"
    app:startDestination="@id/dogMenu">
</navigation>

对于cat_navigation_graph也做相应的处理。

在你的activity_main.xml中,添加2个NavHostFragment

<FrameLayout
    android:id="@+id/frame_dog"
    ...>

    <fragment
        android:id="@+id/dog_navigation_host_fragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:name="androidx.navigation.fragment.NavHostFragment"
        app:navGraph="@navigation/dog_navigation_graph"
        app:defaultNavHost="true"/>
</FrameLayout>

在下面添加相应的猫的NavHostFragment。在你的猫的帧布局上,设置android:visibility="invisible"

现在,在你的MainActivityonCreateView中,你可以:

bottom_navigation_view.setOnNavigationItemSelectedListener { item ->
    when (item.itemId) {
        R.id.dogMenu -> showHostView(host = 0)
        R.id.catMenu -> showHostView(host = 1)
    }
    return@setOnNavigationItemSelectedListener true
}

showHostView()所做的只是切换包装NavHostFragmentFrameLayout的可见性。因此,请确保以某种方式保存它们,例如在onCreateView中。

val hostViews = arrayListOf<FrameLayout>()  // Member variable of MainActivity
hostViews.apply {
    add(findViewById(R.id.frame_dog))
    add(findViewById(R.id.frame_cat))
}

现在很容易切换哪些hostViews应该是可见和不可见的。


2
可能会起作用,但如果我这样做,就会在内存中同时有五个片段,而只有其中一个被使用。你建议如何解决这个问题? - Alireza Ahmadi
是的,这就是如何获得良好的用户体验。无论如何,如果用户在应用程序中导航,您都希望保留每个后退堆栈。这不就是一开始问的问题吗? - Algar
我想实现的是,在内存中只有一个fragment的同时,拥有单独的后退栈,其他fragment仅存在于栈中。我知道这很难,但应该是可能的,例如,您可以查看https://github.com/ncapdevi/FragNav。 - Alireza Ahmadi
哦,没错,现在我明白你的观点了。这是一个值得思考的细微差别!也许使用 ViewPager 是正确的方法,但我还没有尝试过与导航一起使用。我需要去看一下。 - Algar
@Reza,当我不像第一次那样疲惫时,我考虑了你的观点,我意识到从来没有问题。我已经更新了答案并添加了解释(请参见fragmentTransaction.replace()),还添加了完整的代码示例。 - Algar
@Algar 这种方法有任何更新吗? - gromyk

2

请问我该如何实现新版本?https://stackoverflow.com/questions/68042591/problem-upgrading-my-fragments-navigation-versionfrom-2-3-5-to-2-4-0-alpha03 - Richard Wilson
@RichardWilson 只需添加这两个依赖项。 - Arsalan Fakhar Siddiqui
@ArsalanFakhar 但是我们不能在生产中使用alpha库,你能推荐其他的替代库吗? - Mohammed Faiyyaz Khatri

0
首先,我想对 @Algar 的回答进行编辑。您要隐藏的框架应该使用 android:visibility="gone" 而不是 invisible。原因是在您的主布局中将会有这样的代码:
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context=".ui.activity.MainActivity">
    
        <include
            android:id="@+id/toolbar"
            layout="@layout/toolbar_base" />
    
        <FrameLayout
            android:id="@+id/frame_home"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="2"
            >
            <fragment
                android:id="@+id/home_navigation_host_fragment"
                android:name="androidx.navigation.fragment.NavHostFragment"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                app:defaultNavHost="true"
                app:navGraph="@navigation/home_nav" />
        </FrameLayout>
        <FrameLayout
            android:id="@+id/frame_find"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="2"
            android:visibility="gone">
    
            <fragment
                android:id="@+id/find_navigation_host_fragment"
                android:name="androidx.navigation.fragment.NavHostFragment"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                app:defaultNavHost="true"
                app:navGraph="@navigation/find_nav" />
        </FrameLayout>
        ...
    
   </LinearLayout>

如果您将主要内容包装在LinearLayout中,即使将框架设置为不可见,该框架仍然会计数,因此BottomNavigation不会出现。
其次,您应该创建一个NavHostFragment实例(即curNavHostFragment),以跟踪单击BottomNavigation选项卡时哪个NavHostFragment正在显示。注意:您可能希望在活动由于配置更改而被销毁时恢复此curNavHostFragment。这是一个示例:
@Override
protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
    super.onRestoreInstanceState(savedInstanceState);
    //if this activity is restored from previous state,
    //we will have the ItemId of botnav the has been selected
    //so that we can set up nav controller accordingly
    switch (bottomNav.getSelectedItemId()) {
        case R.id.home_fragment:
            curNavHostFragment = homeNavHostFragment;
            ...
            break;
        case R.id.find_products_fragment:
            curNavHostFragment = findNavHostFragment;
            ...
            break;
    
    }
    curNavController = curNavHostFragment.getNavController();

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接