5.3 Navigation + Compose 整合

語言: CN / TW / HK

開始整合 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 = emptyList()     open val deepLinks:List = emptyList()     //必填引數名     private val requiredArgs:MutableList = mutableListOf()     //可選引數名+預設值 map     private val optionalArgs:MutableMap = mutableMapOf()

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 key:引數名,value:引數值     * @return String 呼叫 navigate 方法所需 route     /     fun createRoute(args:Map = emptyMap()):String{         val sb = StringBuilder(routePath)         if (args.isEmpty() && requiredArgs.isNotEmpty()){             throw IllegalArgumentException("param [args:Map] can't be empty")         }else{             for (requiredArg in requiredArgs){                 if (!args.containsKey(requiredArg)){                     throw IllegalArgumentException("required argument $requiredArg can't find in param [args:Map]")                 }                 sb.append("/${args[requiredArg]}")             }             for (optionalArg in optionalArgs){                 if (args.containsKey(optionalArg.key)){ //引數中有可選引數                     sb.append("?${optionalArg.key}={${args[optionalArg.key]}}")                 }else if (optionalArg.value != null){ // 可選引數有預設值                     sb.append("?${optionalArg.key}={${optionalArg.value}}")                 }             }         }         return sb.toString()     }

} ```

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

防止棧頂頁面重複建立多個例項,僅對當前處在棧頂的頁面生效

7984F149-752C-4720-BF87-9655F9E4103B.png

6DE425F1-1A5C-4ABC-A7CD-E37BA3A624F5.png

BC58DEFE-99A7-4792-B59D-FB64A3450FD8.png

BA5D2B87-DD2B-4C89-880C-4DCF052634CD.png

DC478ED5-6A39-46ED-A9CC-67D5D400DFD5.png

popUpTo

導航後將 back 棧彈出到指定路由

5D657FD0-07B4-43F9-B8EA-E59BA8A9E1C0.png

FC9CB250-4534-4B49-AD35-84235FE2D076.png

saveState、restoreState

  • saveState : 當前頁面導航到其他路由後儲存當前頁面的 NavBackStackEntryState
  • restoreState: 導航到指定路由後,恢復其儲存的 NavBackStackEntryState

注意 這裡的 state 是指 NavBackStackEntryState ,不是前面我們說的 "state"

git 地址