开始集成 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
防止栈顶页面重复创建多个实例,仅对当前处在栈顶的页面生效
popUpTo
导航后将 back 栈弹出到指定路由
saveState、restoreState
- saveState : 当前页面导航到其他路由后保存当前页面的 NavBackStackEntryState
- restoreState: 导航到指定路由后,恢复其保存的 NavBackStackEntryState
注意 这里的 state 是指 NavBackStackEntryState ,不是前面我们说的 "state"