5.3 Navigation + Compose 整合
開始整合 Navigation,實現單 Activity + Compose 模式的開發。
封裝 Navigation 通用類和拓展方法
新建模組 lib_compose 新增 navigation 包開始封裝 Navigation 的通用類。
Screen.kt
將 NavGraphBuilder.composable() DSL 中的引數收攏到 Screen 。
- route 屬性根據 arguments 生成避免手打 String 時容易產生錯誤。
- createRoute() 方法根據提供的 引數名/引數值 生成 NavController.navigate() 方法使用的 route 引數
``` abstract class Screen(private val path:String){ //根路由,相當於分組標籤 abstract val root:String
open val arguments:List
private val routePath by lazy { if (root.isEmpty()) path else "$root/${path}" }
/* * 自動生成的 route ,配置 DSL 時使用 * route = rootRoute/path/必填引數?可選引數 / val route:String by lazy { val sb = StringBuilder(routePath) //解析引數時將 必選/可選引數 分別儲存起來 for (arg in arguments){ if (arg.argument.isNullable || arg.argument.isDefaultValuePresent){ //可選引數 sb.append("?${arg.name}={${arg.name}}") optionalArgs[arg.name] = arg.argument.defaultValue }else{ sb.append("/{${arg.name}}") requiredArgs.add(arg.name) } } sb.toString() }
/*
* 通用方法
* 根據傳入的 map 生成 navigate 方法所需 route
* map 中必須包括所有的必填引數
* @param args Map
} ```
NavGraphKtx.kt
包裝 NavGraphBuilder.composable(),提供可以直接使用 Screen 配置的拓展。
fun NavGraphBuilder.composableScreen(
screen: Screen,
content: @Composable (NavBackStackEntry) -> Unit
) {
composable(
route = screen.route,
arguments = screen.arguments,
deepLinks = screen.deepLinks,
content = content
)
}
封裝 Screen 分組配置 NavGraph
- 重寫 composeScreens 配置頁面路由
- create() 方法生成 NavGraph ``` abstract class ScreenNavGraph(protected val navController: NavController,private val startScreen:Screen){
protected abstract val composeScreens: NavGraphBuilder.() -> Unit
fun create(builder: NavGraphBuilder){ builder.run { navigation(startDestination = startScreen.route,route = startScreen.root){ composeScreens.invoke(this) } } } } ```
實現首頁路由
ui 模組的路由配置
ui 模組新增 lib_compose 依賴,在每個模組中新建 route 包 ,新增 Screens.kt ,以 ui-home 為例 ``` sealed class HomeScreens(path:String):Screen(path) { override val root: String get() = "home"
object Index:HomeScreens("index") //abstract 是為涉及跨模組路由時定義抽象方法或屬性 abstract class NavGraph(navController: NavController):ScreenNavGraph(navController,Index){
override val composeScreens: NavGraphBuilder.() -> Unit = { composableScreen(Index){ UiHome() } } } } ```
建立 WanNavHost()
將所有路由配置到 WanNavHost() 中 ``` @Composable fun WanNavHost(modifier: Modifier = Modifier,navController: NavHostController){
NavHost(modifier= modifier,navController = navController, startDestination = HomeScreens.Index.root){
HomeGraph(navController).create(this)
FaqGraph(navController).create(this)
ProjectGraph(navController).create(this)
SquareGraph(navController).create(this)
SystemGraph(navController).create(this)
ProfileGraph(navController).create(this)
}
}
private class HomeGraph(navController: NavHostController) : HomeScreens.NavGraph(navController)
private class ProfileGraph(navController: NavHostController) : ProfileScreens.NavGraph(navController)
private class FaqGraph(navController: NavHostController) : FqaScreens.NavGraph(navController)
private class ProjectGraph(navController: NavHostController) : ProjectScreens.NavGraph(navController)
private class SquareGraph(navController: NavHostController) : SquareScreens.NavGraph(navController)
private class SystemGraph(navController: NavHostController) : SystemScreens.NavGraph(navController) ```
修改 WanApp()
``` @OptIn(ExperimentalMaterial3Api::class) @Composable fun WanApp(){ //建立 navController val navController = rememberNavController() var selectedItemIndex by remember { mutableStateOf(0) } val bottomBarItems = remember { BottomBarItem.values() }
WanAndroidTheme { // A surface container using the 'background' color from the theme Surface( modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background ) { Scaffold( topBar = { TopBar(title = bottomBarItems[selectedItemIndex].label) {} }, bottomBar = { BottomBar(bottomBarItems, selectedItemIndex) { // 修改 BottomBarItem 點選事件 if (it != selectedItemIndex) { selectedItemIndex = it navController.navigate(bottomBarItems[selectedItemIndex].route) { popUpTo(navController.graph.findStartDestination().id) { saveState = true } launchSingleTop = true restoreState = true } } } }, containerColor = Color.LightGray ) { //替換成 WanNavHost WanNavHost(modifier = Modifier.padding(it),navController = navController) } } } } ```
navigate 時控制 back 棧
launchSingleTop
防止棧頂頁面重複建立多個例項,僅對當前處在棧頂的頁面生效
popUpTo
導航後將 back 棧彈出到指定路由
saveState、restoreState
- saveState : 當前頁面導航到其他路由後儲存當前頁面的 NavBackStackEntryState
- restoreState: 導航到指定路由後,恢復其儲存的 NavBackStackEntryState
注意 這裡的 state 是指 NavBackStackEntryState ,不是前面我們說的 "state"