一、背景
Google推出了FragmentContainerView用來(lái)替代FragmentManager進(jìn)行Fragment管理。
--
二、使用步驟
- 在項(xiàng)目根目錄build.gradle文件的中,添加如下代碼
buildscript {
...
dependencies {
...
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.5.0"
}
}
在新版的AndroidStudio中,Gradle升級(jí)到7.2+后,可以將如下代碼添加到根目錄的build.gradle文件的頂頂部
buildscript {
dependencies {
classpath 'androidx.navigation:navigation-safe-args-gradle-plugin:2.5.0'
}
}
在module/build.gradle文件中,添加
apply plugin: "androidx.navigation.safeargs.kotlin",否則不能生成XXXDirections類(lèi);在用到FragmentContainerView的Module中,引用navigation庫(kù),代碼如下:
implementation 'androidx.navigation:navigation-fragment-ktx:2.4.1'
implementation 'androidx.navigation:navigation-ui-ktx:2.4.1'
在Module的res/navigation目錄下創(chuàng)建對(duì)應(yīng)的導(dǎo)航圖表文件,如
nav_main_graph.xml創(chuàng)建Activity及對(duì)應(yīng)布局文件,并在布局文件中使用FragmentContainerView組件
<androidx.fragment.app.FragmentContainerView
android:id="@+id/fragment_container_view"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:defaultNavHost="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/main_nav_graph" />
- 創(chuàng)建對(duì)應(yīng)的Fragment及布局,如:LoginFragment、HomeFragment,并在導(dǎo)航圖表文件
nav_main_graph.xml中添加對(duì)應(yīng)Fragment
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/nav_main_graph"
app:startDestination="@id/loginFragment">
<fragment
android:id="@+id/loginFragment"
android:name="com.bytb.login.LoginFragment"
android:label="LoginFragment">
<action
android:id="@+id/action_goto_home"
app:destination="@id/homeFragment" />
</fragment>
<fragment
android:id="@+id/homeFragment"
android:name="com.bytb.login.HomeFragment"
android:label="HomeFragment" />
</navigation>
注:這里可以通過(guò)拖拽指針來(lái)指定各View之間的關(guān)系
視圖添加、拖拽演示
導(dǎo)航到指定視圖,如從
LoginFragment轉(zhuǎn)入到HomeFragment
findNavController().navigate(LoginFragmentDirections.actionGotoHome())
- 返回到上一級(jí)視圖
findNavController().popBackStack()
- 返回到指定的頁(yè)面
當(dāng)我們?cè)跇I(yè)務(wù)中進(jìn)行如下頁(yè)面導(dǎo)航順序時(shí)FragmentA->DialogFragmentA->DialogFragmentB->DialogFragmentC,當(dāng)前是在DialogFragmentC頁(yè)面,在此頁(yè)面調(diào)用dismiss方法,會(huì)發(fā)現(xiàn)只返回到了DialogFragmentB,而正常情況下,我們是希望返回到彈出彈窗的FragmentA頁(yè)面,那么我們可以使用NavController.kt類(lèi)提供的如下方法達(dá)到目的,源碼如下:
/**
* Attempts to pop the controller's back stack back to a specific destination.
*
* @param destinationId The topmost destination to retain
* @param inclusive Whether the given destination should also be popped.
*
* @return true if the stack was popped at least once and the user has been navigated to
* another destination, false otherwise
*/
@MainThread
// 參數(shù)一:回退到指定的destinationId
// 參數(shù)二:表示是否把指定的destinationId也清退出回退棧
public open fun popBackStack(@IdRes destinationId: Int, inclusive: Boolean): Boolean {
return popBackStack(destinationId, inclusive, false)
}
我們可以在DialogFragmentC中調(diào)用以下方法,直接返回到FragmentA頁(yè)面并把之前的DialogFragmentA->DialogFragmentB->DialogFragmentC都清退出回退棧:
findNavController().popBackStack(R.id.FragmentA_destinationId, false)
如果我們想要返回到FragmentA頁(yè)面的上一級(jí),那么只需要修改參數(shù)二為true即可:
findNavController().popBackStack(R.id.FragmentA_destinationId, true)
11.回傳參數(shù)給上一層的Fragment
當(dāng)我們從FragmentA導(dǎo)航到FragmentB進(jìn)行操作時(shí),如果我們?cè)诮Y(jié)束當(dāng)前FragmentB時(shí)想要返回指定的參數(shù)給FragmentA,那么可以通過(guò)SavedStateHandle+LiveData進(jìn)行對(duì)應(yīng)的參數(shù)值傳遞:
FragmentA.kt
findNavController().currentBackStackEntry?.savedStateHandle?.getLiveData<String>("PARAMETER_KEY")?.observe(viewLifecycleOwner) {
}
FragmentB.kt
val navController = findNavController()
navController.previousBackStackEntry?.savedStateHandle?.set("PARAMETER_KEY", "testValue")
navController.popBackStack()
Android 使用 navigation 時(shí)傳遞參數(shù)的兩種方式
SavedStateHandle組件解析
Android Navigation 如何回傳參數(shù)
三、Navigation結(jié)合BottomNavigationView的使用
在Activity布局文件中,添加Navigation和BottomNavigationView:
<?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=".home.MainActivity">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_container"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:defaultNavHost="true"
app:layout_constraintBottom_toTopOf="@+id/bottom_navigation_view"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0"
app:navGraph="@navigation/nav_main" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottom_navigation_view"
android:layout_width="0dp"
android:layout_height="@dimen/common_measure_56dp"
android:background="@color/white"
app:itemIconTint="@drawable/selector_bottom_tab"
app:itemTextAppearanceActive="@style/bottom_tab_title_active"
app:itemTextAppearanceInactive="@style/bottom_tab_title_inactive"
app:itemTextColor="@drawable/selector_bottom_tab"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:menu="@menu/bottom_nav_menu" />
</androidx.constraintlayout.widget.ConstraintLayout>
導(dǎo)航對(duì)應(yīng)的graph文件nav_main.xml內(nèi)容如下:
<?xml version="1.0" encoding="utf-8"?>
<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/nav_main"
app:startDestination="@id/navigation_home">
<fragment
android:id="@+id/navigation_home"
android:name="com.xxx.HomeFragment"
android:label="HomeFragment"
tools:layout="@layout/fragment_home"/>
<fragment
android:id="@+id/navigation_mine"
android:name="com.xxx.MineFragment"
android:label="MineFragment"
tools:layout="@layout/fragment_mine"/>
</navigation>
BottomNavigationView對(duì)應(yīng)的菜單文件bottom_nav_menu.xml內(nèi)容如下:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/navigation_home"
android:icon="@drawable/ic_bn_home_n"
android:title="@string/tab_home" />
<item
android:id="@+id/navigation_mine"
android:icon="@drawable/ic_bn_mine_n"
android:title="@string/tab_mine" />
</menu>
注意:
graph文件、menu文件中的id要保持一致,即<fragment>與<item>標(biāo)簽的id必須一致。
在Activity將BottomNavigationView與Navigation關(guān)聯(lián)即可:
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_container) as NavHostFragment
mViewBinding.bottomNavigationView.setupWithNavController(navHostFragment.navController)
四、使用過(guò)程中的異常情況
1、Navigation action/destination com.xxx:id/action_x cannot be found from the current destination
// 調(diào)用navigate跳轉(zhuǎn)之前先判斷
// https://blog.csdn.net/qq_26658969/article/details/125642527
if (navController.currentDestination?.id == R.id.Afragment) {
navController.navigate(R.id.action_Afragment_to_Bfragment)
}
2、在http請(qǐng)求回調(diào)中使用Navigation導(dǎo)航報(bào)異常錯(cuò)誤:Method addObserver must be called on the main thread
剛開(kāi)始以為是http請(qǐng)求出了問(wèn)題,各種調(diào)試。。。后來(lái)發(fā)現(xiàn)。。。
又是一個(gè)android BUG NavController在調(diào)用時(shí)Method addObserver must be called on the main thread
參考自
- Android Navigation 使用總結(jié)
- Android-ViewModel-入門(mén)實(shí)踐、FragmentContainerView等
- FragmentContainerView布局
- Android下使用Navigation和FragmentContainerView實(shí)現(xiàn)Fragment的加載和傳值
- Android Navigation + Fragment 制作APP主頁(yè)面導(dǎo)航
- Navigation組件化模式下的應(yīng)用
- Navigation組件化模式下的應(yīng)用-嵌套導(dǎo)航圖和 <include>
- 最全面的Navigation的使用指南