Android 导航组件:BottomNavigationView 的选定选项卡图标未更新
Android Navigation Component : BottomNavigationView's selected tab icon is not updated
我正在使用 BottomNavigationView
和导航组件。当显示片段不是根片段时,选项卡图标未更新(选中)。
示例:
当我在 Tab Home 与片段 A(这是根片段)和 Tab Star 与片段 B(也是根片段)之间切换时,它是工作正常。
但是当我从 Tab Home 导航到另一个片段时,比如片段 A2,然后点击 Tab Star 并再次 return 到 Tab Home,仍然在BottomNavigationView
中选择了Tab Star。
它在版本 2.4.0-alpha05
上工作正常,当我将它更新到 2.5.0-alpha01
时会发生这种情况。
build.gradle(应用程序)
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>
我做错了什么吗?或者这是错误?
我该如何解决这个问题?
鉴于您的导航图,无法将 fragmentA2
与您的菜单项 fragmentA
相关联,因此当您 return 到 fragmentA2
。根据 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>
所以对我有用的是 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
}
这将始终 select 项目并导航到关联的目的地,同时保持多个返回堆栈。
我正在使用 BottomNavigationView
和导航组件。当显示片段不是根片段时,选项卡图标未更新(选中)。
示例:
当我在 Tab Home 与片段 A(这是根片段)和 Tab Star 与片段 B(也是根片段)之间切换时,它是工作正常。
但是当我从 Tab Home 导航到另一个片段时,比如片段 A2,然后点击 Tab Star 并再次 return 到 Tab Home,仍然在BottomNavigationView
中选择了Tab Star。
它在版本 2.4.0-alpha05
上工作正常,当我将它更新到 2.5.0-alpha01
时会发生这种情况。
build.gradle(应用程序)
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>
我做错了什么吗?或者这是错误?
我该如何解决这个问题?
鉴于您的导航图,无法将 fragmentA2
与您的菜单项 fragmentA
相关联,因此当您 return 到 fragmentA2
。根据 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 yourSecondFragment
- even though you haven't used the bottom nav button, the selected tab was changed because the current destination has changed toR.id.frag_second
.So when you
navigate()
toR.id.frag_hint
via your button inHomeFragment
,NavigationUI
receives a callback that the current destination has changed toR.id.frag_hint
. It looks at thatNavDestination
and notes that there's no menu item that matchesR.id.frag_hint
. It then looks at the destination's parent graph - yourR.id.sample
<navigation>
element. There's no menu item that matches that ID either, soNavigationUI
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 meansNavigationUI
does nothing. If you want to linkR.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) andNavigationUI
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 ofNavigationUI
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 yourMenuItem
to get the exact samenavigate()
logic which retaining your own ability to return any value from thesetOnItemSelectedListener
(which is what controls whether the tab becomes selected). Similarly, your ownOnDestinationChangedListener
can choose to look at thehierarchy
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>
所以对我有用的是 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
}
这将始终 select 项目并导航到关联的目的地,同时保持多个返回堆栈。