我已经以最基本的方式使用nav graph
设置了底部导航-
NavigationUI.setupWithNavController(bottomNavigationView, navHostFragment.navController)
声明为startDestination
的片段从其导航时不会被破坏(仅暂停),而所有其他片段在导航时都被破坏。
(我需要销毁它,以便在与其关联的viewModel中调用onCleared()
。)
知道为什么吗?或如何更改此行为?
导航
<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/navigation"
app:startDestination="@id/drawingFragment">
<fragment
android:id="@+id/controllerFragment"
android:name="com.example.android.myApp.ControllerFragment"
android:label="fragment_controller"
tools:layout="@layout/fragment_controller" >
<action
android:id="@+id/action_controllerFragment_to_drawingFragment"
app:destination="@id/drawingFragment" />
</fragment>
<fragment
android:id="@+id/drawingFragment"
android:name="com.example.android.myApp.DrawingFragment"
android:label="fragment_drawing"
tools:layout="@layout/fragment_drawing" >
<action
android:id="@+id/action_drawingFragment_to_clippingFragment"
app:destination="@id/clippingFragment"
app:launchSingleTop="true"
app:popUpTo="@+id/drawingFragment"
app:popUpToInclusive="true" />
</fragment>
<fragment
android:id="@+id/clippingFragment"
android:name="com.example.android.myApp.ClippingFragment"
android:label="fragment_clipping"
tools:layout="@layout/fragment_clipping" />
MainActivity:
class MainActivity : AppCompatActivity() {
private lateinit var navHostFragment: NavHostFragment
private lateinit var bottomNavigationView: BottomNavigationView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setUpNavigation()
}
fun setUpNavigation(){
bottomNavigationView = findViewById(R.id.bttm_nav)
navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
NavigationUI.setupWithNavController(bottomNavigationView, navHostFragment.navController)}
activity_main / xml:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
tools:context=".MainActivity">
<fragment
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="@navigation/navigation" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bttm_nav"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:itemTextAppearanceActive="@style/bottomNaActive"
app:itemTextAppearanceInactive="@style/bottomNavInactive"
app:layout_constraintBottom_toBottomOf="@+id/nav_host_fragment"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:menu="@menu/bottom_menu_nav" />
</androidx.constraintlayout.widget.ConstraintLayout>
答案 0 :(得分:0)
您要描述的是导航组件的默认行为。 导航向下时,仅当导航上时不会破坏您导航的片段。
我个人不明白为什么要通知viewModel该片段已被破坏,但是如果要在导航到另一个目标时运行某些代码,则可以在主目录中使用NavController.OnDestinationChangedListener活动(或在您的片段中,但不要忘了在监听器销毁后将其删除),并根据您的起始和结束目标执行一些操作。
如果您想破坏片段,可以尝试在导航图的导航操作中更改“ pop to”参数。
答案 1 :(得分:0)
这实际上不是答案,而是对@isrzanza答案的评论(对不起,声誉不足)。
我假设在使用BottomNavigationView时没有向上或向下。对我来说,我可以从此处浏览的每个片段分别更像邻居和同等重要,因此我认为不仅清除了这些片段的ViewModel,而且我认为片段本身也会在离开时通过 onDestroy 销毁。
我不知道为什么起始片段应该保留在内存中,而其他片段却不保留,因为它们同等重要,不是吗?
编辑:
我还要提到我注意到,如果我再次导航到startDestination-fragment,则会创建一个相同类型的新片段(onCreate将再次执行),而旧片段将被销毁(onDestroy将被执行)。对我来说,这是另一种资源浪费。将这种情况的片段保存在内存中,然后在以后重新创建它对我来说毫无意义。希望我在这里误解了:)
答案 2 :(得分:0)
@chrgue 正确描述了问题
在“顶级”导航的情况下,没有 up
或 down
的概念。但是 startDestination
片段保存在内存中,同时切换到它时会重新创建。
如果应用程序只包含“顶级”片段,有什么特别不愉快的地方。
我不知道如何正确解决这个问题。 为了我自己,我写了下一个代码。
代码变得很复杂,因为我必须解决问题:后退按钮无法正常工作并且在 OOM 后没有恢复(还有带有“pop to”的动作导航),这是一个可怕的黑客攻击
扩展方法
fun FragmentActivity.enableDestroyStartDestination(
navController: NavController,
appBarConfiguration: AppBarConfiguration
) {
val startDestinationId = navController.graph.startDestination
var firstStart = true
var preventMainRecursionFlag = false
var latestDestionationIsMain = false
navController.addOnDestinationChangedListener { controller, destination, args ->
if (appBarConfiguration.topLevelDestinations.contains(destination.id)) {
if (destination.id == startDestinationId) {
latestDestionationIsMain = true
if (firstStart) {
firstStart = false
return@addOnDestinationChangedListener
}
if (preventMainRecursionFlag) {
preventMainRecursionFlag = false
return@addOnDestinationChangedListener
}
preventMainRecursionFlag = true
val options = NavOptions.Builder().setLaunchSingleTop(true).build()
controller.navigate(startDestinationId, args, options)
} else {
val navHostFragment =
supportFragmentManager.primaryNavigationFragment as NavHostFragment
if (navHostFragment.childFragmentManager.fragments.size > 0) {
if (latestDestionationIsMain) {
val fragment = navHostFragment.childFragmentManager.fragments[0]
navHostFragment.childFragmentManager.beginTransaction().remove(fragment)
.commitNowAllowingStateLoss()
}
}
latestDestionationIsMain = false
}
} else {
latestDestionationIsMain = false
}
}
}
用法(Navigation Drawer 或 BottomNavigationView)
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
...
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...
appBarConfiguration = AppBarConfiguration(
setOf(... ), drawerLayout
)
setupActionBarWithNavController(navController, appBarConfiguration)
navView.setupWithNavController(navController)
this.enableDestroyStartDestination(navController, appBarConfiguration)
}
我不能保证它可以在任何地方使用 - 我的应用程序太简单了。