我试图使用新的导航组件。我使用带有navController的BottomNavigationView:NavigationUI.setupWithNavController(bottomNavigation,navController)
但是当我切换片段时,即使以前使用它们,它们也会每次都被破坏/创建。
有没有办法让我们的主要片段链接到我们的BottomNavigationView?
答案 0 :(得分:31)
尝试一下。
创建自定义导航器。
@Navigator.Name("custom_fragment") // Use as custom tag at navigation.xml
class CustomNavigator(
private val context: Context,
private val manager: FragmentManager,
private val containerId: Int
) : FragmentNavigator(context, manager, containerId) {
override fun navigate(destination: Destination, args: Bundle?, navOptions: NavOptions?) {
val tag = destination.id.toString()
val transaction = manager.beginTransaction()
val currentFragment = manager.primaryNavigationFragment
if (currentFragment != null) {
transaction.detach(currentFragment)
}
var fragment = manager.findFragmentByTag(tag)
if (fragment == null) {
fragment = destination.createFragment(args)
transaction.add(containerId, fragment, tag)
} else {
transaction.attach(fragment)
}
transaction.setPrimaryNavigationFragment(fragment)
transaction.setReorderingAllowed(true)
transaction.commit()
dispatchOnNavigatorNavigated(destination.id, BACK_STACK_DESTINATION_ADDED)
}
}
创建自定义NavHostFragment。
class CustomNavHostFragment: NavHostFragment() {
override fun createFragmentNavigator(): Navigator<out FragmentNavigator.Destination> {
return CustomNavigator(requireContext(), childFragmentManager, id)
}
}
使用自定义标签而不是片段标签。
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/navigation"
app:startDestination="@id/navigation_first">
<custom_fragment
android:id="@+id/navigation_first"
android:name="com.example.sample.FirstFragment"
android:label="FirstFragment" />
<custom_fragment
android:id="@+id/navigation_second"
android:name="com.example.sample.SecondFragment"
android:label="SecondFragment" />
</navigation>
使用CustomNavHostFragment而不是NavHostFragment。
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:id="@+id/nav_host_fragment"
android:name="com.example.sample.CustomNavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@+id/bottom_navigation"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/navigation" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottom_navigation"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:menu="@menu/navigation" />
</androidx.constraintlayout.widget.ConstraintLayout>
我创建了示例项目。 link
我不创建自定义NavHostFragment。我使用navController.navigatorProvider += navigator
。
答案 1 :(得分:3)
经过数小时的研究,我找到了解决方案。一直在我们面前:)有一个功能:popBackStack(destination, inclusive)
可以导航到给定的目的地(如果在backStack中找到的话)。它返回布尔值,因此如果控制器找不到片段,我们可以手动导航到那里。
if(findNavController().popBackStack(R.id.settingsFragment, false)) {
Log.d(TAG, "SettingsFragment found in backStack")
} else {
Log.d(TAG, "SettingsFragment not found in backStack, navigate manually")
findNavController().navigate(R.id.settingsFragment)
}
答案 2 :(得分:0)
目前不可用。
作为一种解决方法,您可以将所有提取的数据存储到视图模型中,并在重新创建片段时使这些数据可供使用。确保使用活动上下文获取视图。
您可以使用LiveData使您对数据生命周期的感知成为可观察的
答案 3 :(得分:0)
Google samples link 只需将NavigationExtensions复制到您的应用程序并通过示例进行配置。效果很好。
答案 4 :(得分:0)
我使用了@STAR_ZERO提供的链接,它可以正常工作。对于那些对“后退”按钮有疑问的人,您可以像这样在活动/导航主机中处理它。
override fun onBackPressed() {
if(navController.currentDestination!!.id!=R.id.homeFragment){
navController.navigate(R.id.homeFragment)
}else{
super.onBackPressed()
}
}
只需检查当前目的地是否是您的根目录/主目录片段(通常是底部导航视图中的第一个),否则,只需导航回该片段,如果是,则仅退出应用程序或执行您想做的任何事情。
顺便说一句,此解决方案需要使用 keep_state_fragment 与STAR_ZERO提供的上述解决方案链接一起使用。
答案 5 :(得分:0)
@ piotr-prus提供的解决方案对我有所帮助,但是我不得不添加一些当前的目的地检查:
if (navController.currentDestination?.id == resId) {
return //do not navigate
}
如果没有进行此项检查,则如果您错误地导航到当前目的地,则会重新创建它,因为不会在后堆栈中找到它。
答案 6 :(得分:0)
如果您在传递参数时遇到麻烦,请添加:
fragment.arguments = args
在KeepStateNavigator
类中的
答案 7 :(得分:0)
根据Android Documentation,
使用FragmentContainerView创建NavHostFragment时,或者通过FragmentTransaction手动将NavHostFragment添加到您的活动中时,尝试通过Navigation.findNavController(Activity,@IdRes int)在活动的onCreate()中检索NavController将会失败。您应该直接从NavHostFragment检索NavController。
将您的nav_host_fragment更改为-> FragmentContainerView 并获取navController
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
val navController = navHostFragment.navController
您可以使用此方法通过导航组件保留片段状态
答案 8 :(得分:0)
如果您只是为了在使用 RecyclerView
和 BottomNavigationView
在片段之间导航时保持 精确 NavController
滚动状态,那么有一个简单的方法是在 layoutManager
中存储 onDestroyView
状态并在 onCreateView
我使用 ActivityViewModel 来存储状态。如果您使用不同的方法,请确保将状态存储在父 Activity 或任何比片段本身存活时间更长的东西中。
片段
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
recyclerview.adapter = MyAdapter()
activityViewModel.listStateParcel?.let { parcelable ->
recyclerview.layoutManager?.onRestoreInstanceState(parcelable)
activityViewModel.listStateParcel = null
}
}
override fun onDestroyView() {
val listState = planet_list?.layoutManager?.onSaveInstanceState()
listState?.let { activityViewModel.saveListState(it) }
super.onDestroyView()
}
视图模型
var plantListStateParcel: Parcelable? = null
fun savePlanetListState(parcel: Parcelable) {
plantListStateParcel = parcel
}