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<NamedNavArgument> = emptyList()
    open val deepLinks:List<NavDeepLink> = emptyList()
    //必填参数名
    private val requiredArgs:MutableList<String> = mutableListOf()
    //可选参数名+默认值 map
    private val optionalArgs:MutableMap<String,Any?> = 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<String, Any> key:参数名,value:参数值
     * @return String 调用 navigate 方法所需 route
     */
    fun createRoute(args:Map<String,Any> = emptyMap()):String{
        val sb = StringBuilder(routePath)
        if (args.isEmpty() && requiredArgs.isNotEmpty()){
            throw IllegalArgumentException("param [args:Map<String,Any>] 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<String,Any>]")
                }
                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 地址

猜你喜欢

转载自juejin.im/post/7106348582462029832
5.3