【问题标题】:Android JetPack navigation with multiple stack具有多个堆栈的 Android JetPack 导航
【发布时间】:2021-06-01 12:01:00
【问题描述】:

我正在使用带有底部导航的 Jetpack Navigation version 1.0.0-alpha04。它可以工作,但导航不正确。例如,如果我有选项卡 A 和选项卡 B,从选项卡 A 转到页面 C,然后从那里转到选项卡 B 并再次返回选项卡 A,我将在选项卡 A 中看到根片段,而不是页面 C不是我所期望的。

我正在寻找一种解决方案,为每个选项卡设置不同的堆栈,因此当我回到它时,每个选项卡的状态都会被保留,而且我不喜欢将所有这些片段保留在内存中,因为它有一个坏的对性能的影响,在喷气背包导航之前,我使用了这个库https://github.com/ncapdevi/FragNav,这正是它的作用,现在我正在寻找与喷气背包导航相同的东西。

【问题讨论】:

  • 您可以发布您的活动的 onCreate 和 OnNavigationItemSelectedListener 代码吗?这将有助于查看您已经尝试过的内容。
  • 在这种情况下什么是页面?另一个活动?还是选项卡a中的视图?也许是寻呼机?
  • @NickCardoso 页面是一个片段,我在每个选项卡中都有根片段,当某些事件发生时,我们会转到该选项卡中的下一个片段。
  • @Reza 你找到解决办法了吗?
  • @Armin 还没有!不过,我会对更好的答案感兴趣。因此,如果您发现任何东西,也请告诉我。

标签: android android-architecture-components android-jetpack android-architecture-navigation


【解决方案1】:

编辑 2: 虽然仍然没有一流的支持(截至撰写本文时),但 Google 现在已经更新了他们的示例,并提供了他们认为目前应该如何解决的示例:https://github.com/googlesamples/android-architecture-components/tree/master/NavigationAdvancedSample


主要原因是您只使用一个NavHostFragment 来保存应用程序的整个后台堆栈。

解决方案是每个选项卡都应该拥有自己的后台堆栈。

  • 在您的主布局中,用FrameLayout 包裹每个选项卡片段。
  • 每个选项卡片段都是一个NavHostFragment,并包含自己的导航图,以使每个选项卡片段都有自己的后退堆栈。
  • BottomNavigationView.OnNavigationItemSelectedListener 添加到BottomNavigtionView 以处理每个FrameLayout 的可见性。

这也可以处理您的“...我不喜欢将所有这些片段保存在内存中...”,因为默认情况下带有NavHostFragment 的导航使用fragmentTransaction.replace(),即您将始终只有与NavHostFragments 一样多的片段。其余的只是在导航图的后台堆栈中。

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


更详细

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

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

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

然后您需要 2 个导航图,例如 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 个NavHostFragments

<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() 所做的只是切换包裹NavHostFragments 的FrameLayouts 的可见性。因此,请确保以某种方式保存它们,例如在onCreateView

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

现在可以轻松切换哪个hostViews 应该可见和不可见。

【讨论】:

  • 它可能会起作用,但如果我这样做,我会同时在内存中有五个片段,而只使用其中一个片段。你有什么建议解决这个问题?
  • 是的,这就是您获得良好用户体验的方式:)。无论哪种方式,如果用户在应用程序中导航,您希望保留每个后退堆栈。这不是开始的问题吗?
  • 好吧,我想要实现的是拥有单独的后退堆栈,而内存中只有一个片段,其他片段仅存在于堆栈中。我知道这很难,但应该有可能,你可以看看这个,例如github.com/ncapdevi/FragNav
  • 哦,是的,现在我明白你的意思了。这是一个值得思考的细微差别!也许ViewPager 是要走的路,但我还没有尝试过使用导航。我得看看那个。
  • @Reza 当我不像第一次那样感到疲倦时,我想到了你的观点,让我震惊的是从来没有问题。我已经用解释更新了答案(参见fragmentTransaction.replace()),并添加了一个完整的代码示例。
【解决方案2】:

Android 团队已在最新版本 2.4.0-alpha01 中解决了该问题,现在可以在没有任何解决方法的情况下使用多个 backstacks 以及底部导航支持。

https://developer.android.com/jetpack/androidx/releases/navigation

【讨论】:

  • 请问如何实施新版本? stackoverflow.com/questions/68042591/…
  • @RichardWilson 只需添加两个依赖项
  • @ArsalanFakhar 但是我们不能在生产中使用 alpha 库,所以您能推荐其他替代方案吗?
【解决方案3】:

首先,我想对@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>

如果您将 main 包装在 LinearLayout 中,将框架设置为不可见仍然会使该框架计数,因此 BottomNavigation 不会出现。

其次,您应该创建一个 NavHostFragment 实例(即:curNavHostFragment)以跟踪单击底部导航中的选项卡时哪个 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();

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-07-22
    • 1970-01-01
    • 2019-10-08
    • 1970-01-01
    • 2021-11-24
    • 2023-04-03
    • 2020-07-22
    相关资源
    最近更新 更多