Error: Maximum number of items supported by BottomNavigationView is 5, after removing item and adding a new one
Error: Maximum number of items supported by BottomNavigationView is 5, after removing item and adding a new one
目前,我需要在用户注销或登录时动态更改我的 bottomnavigation#menu
。有了这个,bottomnavigation#menu 可以是 R.menu.user_logged_in
或 R.menu.user_logged_out
。我的第一个方法是这样的:
private fun setUpBottomNav(newMenu: Int) {
with(binding.bottomNavigationView) {
menu.clear()
inflateMenu(newMenu)
setupWithNavController(findNavController(R.id.fragment_container))
// fix blinking when re selecting bottom nav item
setOnItemReselectedListener {}
}
}
这种方法的主要问题是,当 activity 被重新创建时(例如当进程被终止或当您通过 Intent 从应用程序打开网络浏览器然后单击后退按钮时) ,菜单将通过 menu#clear
和 inflateMenu
清除并再次膨胀,导致当前底部导航状态丢失(例如,当 配置文件选项卡被选择时 并且 menu#clear
在 activity#oncreate
中被调用,所选择的状态将丢失并且 主页选项卡将被选择 )。
我的下一个想法是将 R.menu_user_logged_out
中的 item_amount 减少到四个项目,而是在运行时通过检查用户是否登录或注销来添加第五个菜单项。这将是我的第二种方法:
// NO CLUE WHAT Menu.NONE, ..., Menu.NONE means!!!!
private fun inflateFifthMenuItem() {
if(user.isLoggedIn) {
binding.bottomNavigationView.menu.add(Menu.NONE, R.id.userLoggedInFragment, Menu.NONE, "Profil").setIcon(R.drawable.child_selector_profil)
} else {
binding.bottomNavigationView.menu.add(Menu.NONE, R.id.userLoggedOutFragment, Menu.NONE, "Profil").setIcon(R.drawable.child_selector_profil)
}
}
现在即使上述解决方案听起来合乎逻辑,android还有另一种观点:
java.lang.IllegalArgumentException: Maximum number of items supported by BottomNavigationView is 5. Limit can be checked with BottomNavigationView#getMaxItemCount()
at com.google.android.material.navigation.NavigationBarMenu.addInternal(NavigationBarMenu.java:67)
at androidx.appcompat.view.menu.MenuBuilder.add(MenuBuilder.java:476)
at com.example.app.presentation.main.MainActivity.signInUserBottomNav(MainActivity.kt:94)
at com.example.app.presentation.main.MainActivity.observeLoginState$lambda-2(MainActivity.kt:75)
at com.example.app.presentation.main.MainActivity.$r8$lambda$-vuA_npkMdEgJfGZeCrh_HfU3LQ(Unknown Source:0)
at com.example.app.presentation.main.MainActivity$$ExternalSyntheticLambda0.onChanged(Unknown Source:4)
at androidx.lifecycle.LiveData.considerNotify(LiveData.java:133)
at androidx.lifecycle.LiveData.dispatchingValue(LiveData.java:151)
at androidx.lifecycle.LiveData.setValue(LiveData.java:309)
at androidx.lifecycle.MutableLiveData.setValue(MutableLiveData.java:50)
at androidx.lifecycle.LiveDataScopeImpl$emit.invokeSuspend(CoroutineLiveData.kt:99)
at androidx.lifecycle.LiveDataScopeImpl$emit.invoke(Unknown Source:10)
at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:89)
at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:165)
at kotlinx.coroutines.BuildersKt.withContext(Unknown Source:1)
at androidx.lifecycle.LiveDataScopeImpl.emit(CoroutineLiveData.kt:97)
at com.example.app.presentation.main.ActivityViewModel$loginState.invokeSuspend(ActivityViewModel.kt:26)
at com.example.app.presentation.main.ActivityViewModel$loginState.invoke(Unknown Source:8)
at com.example.app.presentation.main.ActivityViewModel$loginState.invoke(Unknown Source:4)
at androidx.lifecycle.BlockRunner$maybeRun.invokeSuspend(CoroutineLiveData.kt:176)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.internal.DispatchedContinuationKt.resumeCancellableWith(DispatchedContinuation.kt:367)
at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:30)
at kotlinx.coroutines.BuildersKt__Builders_commonKt.startCoroutineImpl(Builders.common.kt:192)
at kotlinx.coroutines.BuildersKt.startCoroutineImpl(Unknown Source:1)
at kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:134)
at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch(Builders.common.kt:56)
at kotlinx.coroutines.BuildersKt.launch(Unknown Source:1)
at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch$default(Builders.common.kt:47)
at kotlinx.coroutines.BuildersKt.launch$default(Unknown Source:1)
at androidx.lifecycle.BlockRunner.maybeRun(CoroutineLiveData.kt:174)
at androidx.lifecycle.CoroutineLiveData.onActive(CoroutineLiveData.kt:240)
at androidx.lifecycle.LiveData.changeActiveCounter(LiveData.java:390)
at androidx.lifecycle.LiveData$ObserverWrapper.activeStateChanged(LiveData.java:466)
at androidx.lifecycle.LiveData$LifecycleBoundObserver.onStateChanged(LiveData.java:425)
at androidx.lifecycle.LifecycleRegistry$ObserverWithState.dispatchEvent(LifecycleRegistry.java:354)
at androidx.lifecycle.LifecycleRegistry.forwardPass(LifecycleRegistry.java:265)
at androidx.lifecycle.LifecycleRegistry.sync(LifecycleRegistry.java:307)
at androidx.lifecycle.LifecycleRegistry.moveToState(LifecycleRegistry.java:148)
at androidx.lifecycle.LifecycleRegistry.handleLifecycleEvent(LifecycleRegistry.java:134)
at androidx.lifecycle.ReportFragment.dispatch(ReportFragment.java:68)
at androidx.lifecycle.ReportFragment$LifecycleCallbacks.onActivityPostStarted(ReportFragment.java:187)
at android.app.Activity.dispatchActivityPostStarted(Activity.java:1248)
at android.app.Activity.performStart(Activity.java:7865)
at android.app.ActivityThread.handleStartActivity(ActivityThread.java:3294)
at android.app.servertransaction.TransactionExecutor.performLifecycleSequence(TransactionExecutor.java:221)
at android.app.servertransaction.TransactionExecutor.cycleToPath(TransactionExecutor.java:201)
at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:173)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:97)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2016)
at android.os.Handler.dispatchMessage(Handler.java:107)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7356)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
所以我的问题是:
- 如何根据某些条件动态添加第五个项目或
- 我如何根据某些条件动态更改菜单(例如,当用户登录时,按下个人资料按钮应该将您导航到 userLoggedInFragment 而不是 userLoggedOutFragment
一开始,BottomNavigationView 默认以一个空菜单开始。
然后根据这样的条件设置菜单
when (condition) {
one -> bottomNavigationView.inflateMenu(R.menu.menu_bottom_navigation_one)
two -> bottomNavigationView.inflateMenu(R.menu.menu_bottom_navigation_two)
three ->bottomNavigationView.inflateMenu(R.menu.menu_bottom_navigation_three)
else -> bottomNavigationView.inflateMenu(R.menu.menu_bottom_navigation_defaults)
}
好的,我已经设法解决了我的问题并实现了在运行时设置菜单/项目而不会丢失其当前状态。有两种可能的解决方案。解决方案 1 设置不同的 menu_item,解决方案二改变一个特定的 ID。重要提示:对于解决方案 2,您的菜单应仅包含 xml.
处的 x - 1 项
例如,如果您想在底部导航栏中包含 5 个项目,您的 xml 应该只包含 4 个项目。第五项将在运行时设置
解决方案一
private fun setUpBottomProfile(itemId: Int) {
val controller = (supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment).navController
with(binding.bottomNavigationView) {
val currentSelectedItem = selectedItemId
menu.clear()
inflateMenu(menuId)
selectedItemId = currentSelectedItem
setupWithNavController(controller)
// fix blinking when re selecting bottom nav item
setOnItemReselectedListener {}
}
}
方案二
private fun setUpBottomProfile(itemId: Int) {
val controller = (supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment).navController
with(binding.bottomNavigationView) {
val currentSelectedItem = selectedItemId
try {
val lastItem = menu[4]
if (lastItem.itemId != itemId) {
menu.removeItem(lastItem.itemId)
addProfilItem(menu, itemId)
selectedItemId = currentSelectedItem
}
} catch (e: IndexOutOfBoundsException) {
addProfilItem(menu, itemId)
}
setupWithNavController(controller)
// fix blinking when re selecting bottom nav item
setOnItemReselectedListener {}
}
}
private fun addProfilItem(menu: Menu, itemId: Int) {
menu.add(0, itemId, 0, getString(R.string.bottomnav_description_profil))
.setIcon(R.drawable.child_selector_profil)
}
重要
我建议选择第二个选项,因为有以下问题:选择第一个选项时,整个菜单被清除,另一个菜单被重新填充。这样,不仅菜单,而且整个底部导航状态都被清除了。
例如,如果您点击 item4 -> fragment1 -> fragment2 -> acitivity recreated -> item4 (state lost, not started from fragment2
加上第二种解法,就是item4 -> fragment1 -> fragment2 -> acitivity recreated -> fragment2 (state recreated)
目前,我需要在用户注销或登录时动态更改我的 bottomnavigation#menu
。有了这个,bottomnavigation#menu 可以是 R.menu.user_logged_in
或 R.menu.user_logged_out
。我的第一个方法是这样的:
private fun setUpBottomNav(newMenu: Int) {
with(binding.bottomNavigationView) {
menu.clear()
inflateMenu(newMenu)
setupWithNavController(findNavController(R.id.fragment_container))
// fix blinking when re selecting bottom nav item
setOnItemReselectedListener {}
}
}
这种方法的主要问题是,当 activity 被重新创建时(例如当进程被终止或当您通过 Intent 从应用程序打开网络浏览器然后单击后退按钮时) ,菜单将通过 menu#clear
和 inflateMenu
清除并再次膨胀,导致当前底部导航状态丢失(例如,当 配置文件选项卡被选择时 并且 menu#clear
在 activity#oncreate
中被调用,所选择的状态将丢失并且 主页选项卡将被选择 )。
我的下一个想法是将 R.menu_user_logged_out
中的 item_amount 减少到四个项目,而是在运行时通过检查用户是否登录或注销来添加第五个菜单项。这将是我的第二种方法:
// NO CLUE WHAT Menu.NONE, ..., Menu.NONE means!!!!
private fun inflateFifthMenuItem() {
if(user.isLoggedIn) {
binding.bottomNavigationView.menu.add(Menu.NONE, R.id.userLoggedInFragment, Menu.NONE, "Profil").setIcon(R.drawable.child_selector_profil)
} else {
binding.bottomNavigationView.menu.add(Menu.NONE, R.id.userLoggedOutFragment, Menu.NONE, "Profil").setIcon(R.drawable.child_selector_profil)
}
}
现在即使上述解决方案听起来合乎逻辑,android还有另一种观点:
java.lang.IllegalArgumentException: Maximum number of items supported by BottomNavigationView is 5. Limit can be checked with BottomNavigationView#getMaxItemCount()
at com.google.android.material.navigation.NavigationBarMenu.addInternal(NavigationBarMenu.java:67)
at androidx.appcompat.view.menu.MenuBuilder.add(MenuBuilder.java:476)
at com.example.app.presentation.main.MainActivity.signInUserBottomNav(MainActivity.kt:94)
at com.example.app.presentation.main.MainActivity.observeLoginState$lambda-2(MainActivity.kt:75)
at com.example.app.presentation.main.MainActivity.$r8$lambda$-vuA_npkMdEgJfGZeCrh_HfU3LQ(Unknown Source:0)
at com.example.app.presentation.main.MainActivity$$ExternalSyntheticLambda0.onChanged(Unknown Source:4)
at androidx.lifecycle.LiveData.considerNotify(LiveData.java:133)
at androidx.lifecycle.LiveData.dispatchingValue(LiveData.java:151)
at androidx.lifecycle.LiveData.setValue(LiveData.java:309)
at androidx.lifecycle.MutableLiveData.setValue(MutableLiveData.java:50)
at androidx.lifecycle.LiveDataScopeImpl$emit.invokeSuspend(CoroutineLiveData.kt:99)
at androidx.lifecycle.LiveDataScopeImpl$emit.invoke(Unknown Source:10)
at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:89)
at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:165)
at kotlinx.coroutines.BuildersKt.withContext(Unknown Source:1)
at androidx.lifecycle.LiveDataScopeImpl.emit(CoroutineLiveData.kt:97)
at com.example.app.presentation.main.ActivityViewModel$loginState.invokeSuspend(ActivityViewModel.kt:26)
at com.example.app.presentation.main.ActivityViewModel$loginState.invoke(Unknown Source:8)
at com.example.app.presentation.main.ActivityViewModel$loginState.invoke(Unknown Source:4)
at androidx.lifecycle.BlockRunner$maybeRun.invokeSuspend(CoroutineLiveData.kt:176)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.internal.DispatchedContinuationKt.resumeCancellableWith(DispatchedContinuation.kt:367)
at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:30)
at kotlinx.coroutines.BuildersKt__Builders_commonKt.startCoroutineImpl(Builders.common.kt:192)
at kotlinx.coroutines.BuildersKt.startCoroutineImpl(Unknown Source:1)
at kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:134)
at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch(Builders.common.kt:56)
at kotlinx.coroutines.BuildersKt.launch(Unknown Source:1)
at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch$default(Builders.common.kt:47)
at kotlinx.coroutines.BuildersKt.launch$default(Unknown Source:1)
at androidx.lifecycle.BlockRunner.maybeRun(CoroutineLiveData.kt:174)
at androidx.lifecycle.CoroutineLiveData.onActive(CoroutineLiveData.kt:240)
at androidx.lifecycle.LiveData.changeActiveCounter(LiveData.java:390)
at androidx.lifecycle.LiveData$ObserverWrapper.activeStateChanged(LiveData.java:466)
at androidx.lifecycle.LiveData$LifecycleBoundObserver.onStateChanged(LiveData.java:425)
at androidx.lifecycle.LifecycleRegistry$ObserverWithState.dispatchEvent(LifecycleRegistry.java:354)
at androidx.lifecycle.LifecycleRegistry.forwardPass(LifecycleRegistry.java:265)
at androidx.lifecycle.LifecycleRegistry.sync(LifecycleRegistry.java:307)
at androidx.lifecycle.LifecycleRegistry.moveToState(LifecycleRegistry.java:148)
at androidx.lifecycle.LifecycleRegistry.handleLifecycleEvent(LifecycleRegistry.java:134)
at androidx.lifecycle.ReportFragment.dispatch(ReportFragment.java:68)
at androidx.lifecycle.ReportFragment$LifecycleCallbacks.onActivityPostStarted(ReportFragment.java:187)
at android.app.Activity.dispatchActivityPostStarted(Activity.java:1248)
at android.app.Activity.performStart(Activity.java:7865)
at android.app.ActivityThread.handleStartActivity(ActivityThread.java:3294)
at android.app.servertransaction.TransactionExecutor.performLifecycleSequence(TransactionExecutor.java:221)
at android.app.servertransaction.TransactionExecutor.cycleToPath(TransactionExecutor.java:201)
at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:173)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:97)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2016)
at android.os.Handler.dispatchMessage(Handler.java:107)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7356)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
所以我的问题是:
- 如何根据某些条件动态添加第五个项目或
- 我如何根据某些条件动态更改菜单(例如,当用户登录时,按下个人资料按钮应该将您导航到 userLoggedInFragment 而不是 userLoggedOutFragment
一开始,BottomNavigationView 默认以一个空菜单开始。 然后根据这样的条件设置菜单
when (condition) {
one -> bottomNavigationView.inflateMenu(R.menu.menu_bottom_navigation_one)
two -> bottomNavigationView.inflateMenu(R.menu.menu_bottom_navigation_two)
three ->bottomNavigationView.inflateMenu(R.menu.menu_bottom_navigation_three)
else -> bottomNavigationView.inflateMenu(R.menu.menu_bottom_navigation_defaults)
}
好的,我已经设法解决了我的问题并实现了在运行时设置菜单/项目而不会丢失其当前状态。有两种可能的解决方案。解决方案 1 设置不同的 menu_item,解决方案二改变一个特定的 ID。重要提示:对于解决方案 2,您的菜单应仅包含 xml.
处的 x - 1 项例如,如果您想在底部导航栏中包含 5 个项目,您的 xml 应该只包含 4 个项目。第五项将在运行时设置
解决方案一
private fun setUpBottomProfile(itemId: Int) {
val controller = (supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment).navController
with(binding.bottomNavigationView) {
val currentSelectedItem = selectedItemId
menu.clear()
inflateMenu(menuId)
selectedItemId = currentSelectedItem
setupWithNavController(controller)
// fix blinking when re selecting bottom nav item
setOnItemReselectedListener {}
}
}
方案二
private fun setUpBottomProfile(itemId: Int) {
val controller = (supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment).navController
with(binding.bottomNavigationView) {
val currentSelectedItem = selectedItemId
try {
val lastItem = menu[4]
if (lastItem.itemId != itemId) {
menu.removeItem(lastItem.itemId)
addProfilItem(menu, itemId)
selectedItemId = currentSelectedItem
}
} catch (e: IndexOutOfBoundsException) {
addProfilItem(menu, itemId)
}
setupWithNavController(controller)
// fix blinking when re selecting bottom nav item
setOnItemReselectedListener {}
}
}
private fun addProfilItem(menu: Menu, itemId: Int) {
menu.add(0, itemId, 0, getString(R.string.bottomnav_description_profil))
.setIcon(R.drawable.child_selector_profil)
}
重要
我建议选择第二个选项,因为有以下问题:选择第一个选项时,整个菜单被清除,另一个菜单被重新填充。这样,不仅菜单,而且整个底部导航状态都被清除了。
例如,如果您点击 item4 -> fragment1 -> fragment2 -> acitivity recreated -> item4 (state lost, not started from fragment2
加上第二种解法,就是item4 -> fragment1 -> fragment2 -> acitivity recreated -> fragment2 (state recreated)