安卓导航组件:BottomNavigationView的选中标签图标未更新

18

我正在使用Navigation组件的BottomNavigationView。当显示的Fragment不是根Fragment时,选项卡图标没有更新(选中)。

例如:
当我在Tab Home和Fragment A(它是根Fragment)之间切换,并在Tab Star和Fragment B(也是根Fragment)之间切换时,一切正常。但是,当我从Tab Home导航到另一个Fragment,如A2 Fragment,并在Tab Star上点击后再返回Tab Home时,BottomNavigationView仍然选中 Tab Star

Android Navigation Component Bug

版本2.4.0-alpha05时可以正常工作,但更新到2.5.0-alpha01后出现此问题。

build.gradle(app)

implementation "androidx.navigation:navigation-fragment-ktx:2.5.0-alpha01"
implementation "androidx.navigation:navigation-ui-ktx:2.5.0-alpha01"
implementation "androidx.navigation:navigation-dynamic-features-fragment:2.5.0-alpha01"

build.gradle (根目录)

classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.5.0-alpha01"

图表:
安卓导航图表

<navigation 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:id="@+id/graph"
        app:startDestination="@id/fragmentA">
    <fragment
            android:id="@+id/fragmentA"
            android:name="ui.test.FragmentA"
            tools:layout="@layout/fragment_test"
            android:label="FragmentA" >
        <action
                android:id="@+id/action_fragmentA_to_fragmentA2"
                app:destination="@id/fragmentA2" />
    </fragment>
    <fragment
            android:id="@+id/fragmentA2"
            android:name="ui.test.FragmentA2"
            tools:layout="@layout/fragment_test"
            android:label="FragmentA2" />
    <fragment
            android:id="@+id/fragmentB"
            android:name="ui.test.FragmentB"
            tools:layout="@layout/fragment_test"
            android:label="FragmentB" />
</navigation>

菜单:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item
            android:id="@+id/fragmentA"
            android:icon="@drawable/ic_home"
            android:title="" />
    <item
            android:id="@+id/fragmentB"
            android:icon="@drawable/ic_star"
            android:title="" />
</menu>

我是不是做错了什么?还是这是个bug?
如何解决这个问题?


请包含您的导航图。 - ianhanniballake
@ianhanniballake 完成。 - Amir
2
我曾经遇到过同样的问题,你非常清楚地描述了这个问题。 - Mr.A2
3个回答

24

对我起作用的解决方案是ianhanniballake在他的答案中暗示的:使用setOnItemSelectedListener。

// always show selected Bottom Navigation item as selected (return true)
bottomNavigationView.setOnItemSelectedListener { item ->
  // In order to get the expected behavior, you have to call default Navigation method manually
  NavigationUI.onNavDestinationSelected(item, navController)

  return@setOnItemSelectedListener true
}

这将始终选择该项并导航到相关目标,同时保持多个后退堆栈。


1
是的!谢谢!我真的对这个问题感到很烦恼,不想将其分成多个导航图。已经很难了,有多个活动共享一些片段,并且必须在每个图中复制导航路线。根据底部选项卡进一步分离会更加可怕。 - Tyler Turnbull
我也试过了,谢谢。希望它不会破坏其他东西 :) - Anurban
1
哎呀救我!谢谢你这个快速解决方案! - Hoang Nguyen Huu
工作,但会添加默认动画,有没有办法去掉动画或使用自己的动画? - undefined
@ueen: 或许这个链接可以帮到你:https://stackoverflow.com/a/71585108/389582 - undefined

8
考虑到您的导航图,无法将fragmentA2与菜单项fragmentA关联起来,因此当您返回fragmentA2时,fragmentA不会被选中。根据this issue

NavigationUI has always used the current destination and what graph it is part of as the source of truth for what tab should be selected.

This can be seen by calling navigate() to go to your SecondFragment - even though you haven't used the bottom nav button, the selected tab was changed because the current destination has changed to R.id.frag_second.

So when you navigate() to R.id.frag_hint via your button in HomeFragment, NavigationUI receives a callback that the current destination has changed to R.id.frag_hint. It looks at that NavDestination and notes that there's no menu item that matches R.id.frag_hint. It then looks at the destination's parent graph - your R.id.sample <navigation> element. There's no menu item that matches that ID either, so NavigationUI can't associated that destination with any menu item and therefore simply does nothing. That is true on all versions of Navigation.

So what is different when you tap on a bottom navigation item? Well, nothing different from a NavigationUI perspective in fact: the exact same code runs and the current destination and what graph it is part of is the source of truth for what tab should be selected. In the Navigation 2.3.5, there was no state saved for each tab, so it only 'worked' because selecting a tab forced the ID of the current destination to match the destination of the menu item you just tapped.

So what you're seeing in your sample app is that there's no link between R.id.frag_hint and any menu item, which means NavigationUI does nothing. If you want to link R.id.frag_hint to your Home tab, then that's exactly what a nested navigation graph can be used for.

I.e., your navigation graph should instead look like:

<navigation
    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:id="@+id/sample"
    app:startDestination="@id/home">
   
    <navigation
        android:id="@+id/home"
        app:startDestination="@id/frag_home">
        <fragment
            android:id="@+id/frag_home"
            android:name="eu.rekisoft.android.navbug.HomeFragment"
            tools:layout="@layout/fragment_home">
            <action
                android:id="@+id/cause_bug"
                app:destination="@id/frag_hint"/>
        </fragment>

        <fragment
            android:id="@+id/frag_hint"
            android:name="eu.rekisoft.android.navbug.HintFragment"
            android:label="Hint"
            tools:layout="@layout/fragment_hint"/>
    </navigation>

    <fragment
        android:id="@+id/frag_second"
        android:name="eu.rekisoft.android.navbug.SecondFragment"
        android:label="Second Fragment"
        tools:layout="@layout/fragment_second"/>

</navigation>

And your menu XML should be updated to use android:id="@id/home" to match your navigation graph.

Now, when you select the Home bottom nav item, the current destination changes to R.id.frag_hint (as your state was restored due to Navigation 2.4's support for multiple back stacks) and NavigationUI looks at the ID - R.id.frag_hint still doesn't match any menu item, but now the parent graph's ID, R.id.home does match a menu item - your Home menu item, hence, it becomes selected.

The intention that your navigation graph and its structure drives the UI is a key part of how NavigationUI works and is working as intended (there was a bug on earlier versions of Navigation 2.4 that broke this driving principle, but that has since been fixed in beta02). All of NavigationUI is built on public APIs specifically so that if you want to use some different logic for which bottom nav item is selected that is independent from your navigation graph structure, you can absolutely do that.

You'll note from the source code that you can call the public onNavDestinationSelected with your MenuItem to get the exact same navigate() logic which retaining your own ability to return any value from the setOnItemSelectedListener (which is what controls whether the tab becomes selected). Similarly, your own OnDestinationChangedListener can choose to look at the hierarchy of a destination to choose whether to change the selected bottom nav item or not.

所以,您的图表也应该为每个选项卡使用嵌套图表:
<navigation 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:id="@+id/graph"
        app:startDestination="@id/graphA">
    <navigation
        android:id="@+id/graphA"
        app:startDestination="@id/fragmentA">
        <fragment
            android:id="@+id/fragmentA"
            android:name="ui.test.FragmentA"
            tools:layout="@layout/fragment_test"
            android:label="FragmentA" >
            <action
                android:id="@+id/action_fragmentA_to_fragmentA2"
                app:destination="@id/fragmentA2" />
        </fragment>
        <fragment
            android:id="@+id/fragmentA2"
            android:name="ui.test.FragmentA2"
            tools:layout="@layout/fragment_test"
            android:label="FragmentA2" />
    </navigation>
    <navigation
        android:id="@+id/graphB"
        app:startDestination="@id/fragmentB">
        <fragment
            android:id="@+id/fragmentB"
            android:name="ui.test.FragmentB"
            tools:layout="@layout/fragment_test"
            android:label="FragmentB" />
    </navigation>
</navigation>

菜单:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item
            android:id="@+id/graphA"
            android:icon="@drawable/ic_home"
            android:title="" />
    <item
            android:id="@+id/graphB"
            android:icon="@drawable/ic_star"
            android:title="" />
</menu>

1
我还没有测试过,但看起来可以解决这个问题,但代价是什么?在一个复杂的项目中使用提出的结构,其中一个片段可以从多个片段中访问,强制我们复制大量的代码,使图形非常复杂和难以维护。而且每次都需要声明/编辑多个片段/参数。这就是为什么我不喜欢使用这种解决方案。请注意,在导航的版本2.4.0-alpha05中,它运行良好。另外,已经有多种情况的导航存在了,这使得情况与我的不同。 - Amir
根据您提到的问题,底部导航有一种方法可以找出哪个选项卡已被选择。正如它所说:“然后查看目标的父图表”,在我的图表中,如果它查看父级,则可以找到与导航菜单匹配的fragmentA - Amir
在你的图表中,没有任何结构性关系暗示fragmentA2fragmentA有任何联系,与fragmentB或图表中的任何其他随机片段一样。你需要那个匹配菜单项ID的共享父图表来提供NavigationUI所依赖的结构。 - ianhanniballake
5
这意味着任何全局使用的片段都不行了吗?同样,当从两个不同的菜单项使用相同的目标(比如详细视图)时,这也不能按照用户的期望工作,对吧?那么我就得为这两个菜单项都复制一份详细视图了吗?难道不应该通过检查运行时堆栈而不是导航图来更好地完成这个过程吗? - hdort
完美运作。我更喜欢这个解决方案,因为被接受的解决方案感觉像一个权宜之计,在未来的更新中可能会破坏代码。 - hb0

0
我在2.6.0版本遇到了这个问题,如果你也是这种情况,请使用2.5.3版本,它完美解决了这个问题。

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