天衢路由 (TianQu) - Kotlin Multiplatform 路由框架
天衢(tiān qú)出自《楚辞·九思·遭厄》:“蹑天衢兮长驱,踵九阳兮戏荡。”意为天上的大道,宽阔的通衢。寓意本框架为 Kotlin Multiplatform 跨平台开发提供一条平坦、宽广、畅通无阻的路由大道。
天衢 是一个专为 Kotlin Multiplatform (KMP) 打造的纯 Kotlin + 协程驱动的现代路由框架,支持 Android、iOS 以及鸿蒙。它彻底摆脱了传统 Android 路由框架对 JVM ASM 字节码插桩的依赖与深层嵌套的回调地狱,全面拥抱挂起函数,为您提供无侵入、强解耦、类型安全以及功能极其丰富的跨模块导航与服务发现解决方案。
📱 平台支持
| 平台 | 状态 |
|---|
| Android | ✅ 支持 |
| iOS | ✅ 支持 |
| 鸿蒙 | ✅ 支持 |
🌟 核心特性概览
天衢路由最大的亮点在于对 Kotlin 协程 (Coroutine) 的极致拥抱。我们在核心架构的每一层都去除了传统的回调接口,采用挂起函数重构了复杂的异步流,带来了前所未有的开发体验:
🏗️ 框架架构图

架构说明:
📦 一、引入与配置
目前可通过 Maven Central 引入最新版天衢路由依赖。
⚠️ 注意:需要在 settings.gradle.kts 的 pluginManagement 和 dependencyResolutionManagement 中,将以下仓库添加到第一位(鸿蒙所依赖的 Kotlin/CMP fork 版本仅存于此仓库):
pluginManagement {
repositories {
maven("https://maven.eazytec-cloud.com/nexus/repository/maven-public/")
google()
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositories {
maven("https://maven.eazytec-cloud.com/nexus/repository/maven-public/")
google()
mavenCentral()
}
}
在项目根目录或 gradle/libs.versions.toml 中配置:
1. 业务子模块配置 (Feature Module)
子模块需要依赖 annotations 注解库和运行时库,并应用 KSP 处理器来生成自身的路由表。同时可以通过 KSP 参数 tianqu.moduleName 指定生成的类名前缀。
注意:必须配置 Java 17 并在 commonMain 中添加 KSP 生成的代码目录,否则会导致编译时找不到生成的类。
plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.composeMultiplatform)
alias(libs.plugins.ksp)
}
kotlin {
jvmToolchain()
sourceSets {
commonMain.dependencies {
implementation(libs.tianqu.router.annotations)
implementation(libs.tianqu.router.runtime)
}
}
}
ksp {
arg(, project.name)
}
dependencies {
add(, libs.tianqu.router.processor)
}
kotlin.sourceSets.commonMain {
kotlin.srcDir()
}
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask<*>>().configureEach {
(name != ) {
dependsOn()
}
}
2. 主工程模块配置 (App Module) 【极其重要❗】
主工程除了依赖 runtime 核心库外,必须在 KSP 配置中声明自己为 App 模块,并且同样需要配置 Java 17 和 KSP 代码生成目录关联。
plugins {
alias(libs.plugins.ksp)
}
ksp {
arg(, project.name)
arg(, )
}
kotlin {
jvmToolchain()
sourceSets {
commonMain.dependencies {
implementation(libs.tianqu.router.annotations)
implementation(libs.tianqu.router.runtime)
implementation(project())
implementation(project())
}
}
}
dependencies {
add(, libs.tianqu.router.processor)
}
kotlin.sourceSets.commonMain {
kotlin.srcDir()
}
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask<*>>().configureEach {
(name != ) {
dependsOn()
}
}
🚀 二、基础路由导航与全局初始化
1. 全局初始化 RouterHost
在 Compose Multiplatform 的共享 UI 层 (App.kt) 根节点,调用 rememberAppTianQuState 完成框架装配,并使用 RouterHost 承载。框架会自动完成跨模块服务初始化、路由事件监听和返回键拦截,业务只需声明自己的策略。
2. 获取 Navigator 的多种方式
页面跳转需要先拿到Navigator对象,然后调用navigateTo。获取Navigator对象,除了通过 CompositionLocal 获取,如果您处于协程作用域内,还可以直接从上下文中提取!
3. 页面状态保存(remember vs rememberSaveable)【重要】
天衢底层的 RouterHost 通过 rememberSaveableStateHolder 管理返回栈——当 A 跳转到 B 时,A 会被移出组合树;从 B 返回时,A 重新进入组合树并恢复状态。框架已帮你处理了底层的 SaveableStateHolder,业务侧只需要用 rememberSaveable 声明需要跨页面保留的 UI 状态即可。
@Router("/home")
@Composable
fun HomeScreen(context: RouterContext) {
var testCounter by rememberSaveable { mutableStateOf(0) }
Button(onClick = { testCounter++ }) {
Text("计数值: $testCounter")
}
}
4. 物理返回键/侧滑手势返回拦截 (Android/iOS)
在 Android 上,通常需要拦截系统的物理返回键;在 iOS 上,则是侧滑返回。天衢框架已在 router-runtime 内部内置了跨平台 BackHandler 实现,并由 rememberAppTianQuState 默认自动挂载,无需业务层手动编写任何 expect/actual 桥接代码。
默认行为:当导航栈中多于 1 个页面时,物理返回键/侧滑手势会自动触发出栈。
如需禁用默认的全局返回拦截(例如某些特殊场景需要完全自定义),可通过配置关闭:
val navigator = rememberAppTianQuState {
startRoute = “/main_tab”
enableBackHandler = false
}
⚠️ 注意:对于鸿蒙来说,需要在 harmonyApp/entry/src/main/ets/pages/Index.ets 重写 onBackPress ,不是onBackPressed,方法名别写错。一般来说,Index.ets是在创建项目的时候自动生成的,但自动生成的onBackPress方法可能会出错,可能会生成onBackPressed,多加了ed,一定要检查onBackPress方法是否正确。
// 这是正确的方法
onBackPress(): boolean | void {
return this.controller ? this.controller.onBackPress() : false;
}
// 这是错误的,编译器自动生成的可能会是onBackPressed
onBackPressed(): boolean {
return this.controller ? this.controller.onBackPress() : false;
}
如需在某个深层子页面拦截返回(例如表单页阻止意外返回),直接在该页面调用 BackHandler 即可,它会优先于全局拦截消费返回事件:
@Router(path = “/edit_form”)
@Composable
fun EditFormScreen(context: RouterContext) {
val navigator = LocalNavigator.current
var hasUnsavedChanges by remember { mutableStateOf(true) }
BackHandler(enabled = hasUnsavedChanges) {
println(“⚠️ 有未保存的内容,请先保存再退出”)
}
}
💡 多层嵌套拦截提示:Compose 的 BackHandler 遵循”就近拦截(子优先)”原则。子页面的 BackHandler 会优先消费返回事件,完美覆盖全局的默认退栈逻辑,不会产生冲突。
5. 路由启动模式 (LaunchMode):栈顶复用与栈内复用
天衢 路由全面支持了类似 Android Activity 的高级启动模式 (LaunchMode),并且由于采用纯 Compose 状态驱动,复用页面时只有在参数发生实质性变化时才会触发重组,实现了真正的“零开销复用”。
如何使用:
在 @Router 注解中指定 launchMode 即可。目前支持三种模式:
LaunchMode.STANDARD (默认):每次跳转都创建新页面并入栈。
LaunchMode.SINGLE_TOP:栈顶复用。如果当前栈顶已经是该页面,则复用栈顶页面,不创建新实例,同时将新的参数传递给旧页面。
LaunchMode.SINGLE_TASK:栈内复用。如果导航栈中已经存在该页面,则将其上的所有页面弹出栈,使该页面重新回到栈顶并复用,同时传递新参数。
import shijing.tianqu.router.LaunchMode
import shijing.tianqu.router.Router
@Router(path = "/task", launchMode = LaunchMode.SINGLE_TASK)
@Composable
fun SingleTaskScreen(context: RouterContext) {
Text()
}
💡 最佳实践:得益于 Compose 的 SaveableStateHolder,在 SINGLE_TASK 触发上层页面退栈而使复用页面重新展示时,原页面的滚动位置、输入框状态等均会原样保留且无缝衔接动画,完美还原 Android 的 onNewIntent 体验!
6. 自定义页面切换动画
天衢 路由内置了常见的转场动画(如 Fade, Slide, Scale, None),默认使用的是Slide,这是安卓上经典的页面切换动画。如果您需要极其炫酷的自定义动画,可以直接使用 @Transition 注解和 BaseTransitionStrategy 轻松实现,且完全支持 Compose 官方的动画 API!
实现步骤:
- 继承
BaseTransitionStrategy 并重写入场/出场动画。
- 使用
@Transition 注解标记,并起一个引用名称。
- 在目标页面的
@Router 注解中使用这个名称。
KSP 会自动将您的自定义动画收集到聚合器中,跳转时无缝衔接。
🎯 三、传参大满贯:各种传参场景与代码示例
天衢 路由全面覆盖了不同场景的数据传递需求,不再有传统框架传参类型容易丢失的问题。
场景一:基于 URL 的基础类型传参 (Path & Query)
适用于深层链接 (DeepLink) 直接唤起 App,或者简单的 ID、状态位传递。
发起跳转:
navigator.navigateTo("app://shijing.tianqu/user/1001?source=home_banner&vip=true")
目标页面解析参数:
@Router(path = "/user/{id}")
@Composable
fun UserDetailScreen(context: RouterContext) {
userId = context.pathParams[]
source = context.queryParams[]?.firstOrNull()
isVip = context.queryParams[]?.firstOrNull()?.toBoolean() ?:
Text()
}
场景二:传递内存级别复杂大对象 (Extra)
对于非序列化、直接传递的大对象(例如 Bitmap,或某些体积巨大、不方便格式化的类实例),可以直接通过 extra 字段传递。
发起跳转:
data class UserProfile(val name: String, val age: Int, val isVip: Boolean)
val profile = UserProfile(name = "Kotlin开发者", age = 25, isVip = true)
navigator.navigateTo("/profile", extra = profile)
目标页面安全解包:
@Router(path = "/profile")
@Composable
fun ProfileScreen(context: RouterContext) {
val profileData = context.extra as? UserProfile
if (profileData != null) {
Text("你好,${profileData.name},年龄 ")
} {
Text()
}
}
场景三:基于 Kotlinx.serialization 的强类型安全传参
如果希望享受绝对的类型安全检查,支持序列化并严防类型错误,请使用强类型传参。
前置准备:引入序列化插件与依赖
在项目根目录或 gradle/libs.versions.toml 中配置:
[versions]
kotlinx-serialization = "1.9.1-0.3.0"
[libraries]
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" }
[plugins]
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
kotlinSerialization = { id = , version.ref = }
在需要使用强类型传参的模块的 build.gradle.kts 中,引入 Kotlin 官方的序列化插件及运行库:
plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.androidApplication)
alias(libs.plugins.composeMultiplatform)
alias(libs.plugins.composeCompiler)
alias(libs.plugins.ksp)
alias(libs.plugins.kotlinSerialization)
}
kotlin {
sourceSets {
commonMain.dependencies {
implementation(libs.tianqu.router.annotations)
implementation(libs.tianqu.router.runtime)
implementation(libs.kotlinx.serialization.json)
}
}
}
步骤 1:定义可序列化的数据类
import kotlinx.serialization.Serializable
@Serializable
data class UserDetailArgs(
val userId: Long,
val username: String,
val isVip: Boolean,
val scores: List<Int>
)
步骤 2:发起强类型安全跳转 (navigateArgs)
navigator.navigateArgs(
path = "/typesafe_demo",
args = UserDetailArgs(
userId = 999L,
username = "天衢路由",
isVip = true,
scores = listOf(100, 98)
)
)
步骤 3:目标页面强类型提取 (getTypedArgs<T>())
import shijing.tianqu.runtime.getTypedArgs
@Router(path = "/typesafe_demo")
@Composable
fun TypeSafeScreen(context: RouterContext) {
val args = context.getTypedArgs<UserDetailArgs>()
if (args != null) {
Text("✅ 类型安全解析成功!用户名: ${args.username}")
}
}
⚡️ 四、极致性能:非阻塞并发数据预加载 (Route Pre-fetching)
传统的页面开发模式往往面临两难选择:要么先跳转再请求(进入页面后有一段骨架屏/Loading空白期等待数据),要么先请求再跳转(用户点击按钮后界面卡住无响应,等数据回来了才突然跳转)。
天衢 路由作为一款基于协程驱动的现代框架,彻底解决了这个问题!我们提供了页面跳转与数据加载并发执行的预加载能力:
在执行 navigateTo 的毫秒级瞬间,底层会通过 async 启动预加载协程,并在同时播放页面的 Compose 转场动画。我们完美利用了动画过渡的 300~500 毫秒“空窗期”,等动画播完时,数据往往已经加载完毕,实现真正的**“零秒开启、无缝平滑”**极致体验。完全不阻塞主线程!
步骤 1:定义你的预加载器 (实现 RoutePreloader)
class UserDetailPreloader : RoutePreloader {
override suspend fun preload(context: ): Any? {
userId = context.pathParams[] ?:
println()
delay()
UserDetailArgs(userId = userId.toLong(), username = , isVip = , scores = emptyList())
}
}
步骤 2:注册到 Navigator 初始化中
val preloaders = remember { mapOf("/demo_preload" to UserDetailPreloader()) }
val navigator = rememberAppTianQuState {
startRoute = "/home"
this.preloaders = preloaders
}
请注意,不能通过下面的代码传递预加载器。 直接写 preloaders = mapOf(...),这意味着每次重组时都会创建一个新的 Map 对象。
rememberAppTianQuState 会根据参数(包括 preloaders)来 remember 导航器。
因为传进去的 Map 对象一直在变,导致 Compose 认为参数变了,从而不断地重新创建 Navigator 对象并清空页面栈,最终表现出来的就是不断刷新的“白屏”。
val navigator = rememberAppTianQuState {
startRoute = "/home"
this.preloaders = mapOf("/demo_preload" to UserDetailPreloader())
}
如果不想在启动的时候就传递预加载器,可以在通过下面的方式
navigator.registerPreloader("/demo_preload", UserDetailPreloader())
navigator.navigateTo("/demo_preload")
步骤 3:在目标页面一键提取数据 (rememberPreloadData<T>())
无需关心复杂的协程等待逻辑,框架在底层已经将获取到的 Deferred 无缝传递给了页面,使用即可:
@Router(path = "/user_detail/{id}")
@Composable
fun UserDetailScreen(context: RouterContext) {
val preloadedData = rememberPreloadData<UserDetailArgs>()
if (preloadedData == null) {
CircularProgressIndicator()
} {
Text()
}
}
🔄 五、页面结果回传 (页面间双向通信)
处理“选择联系人”、“修改设置后返回数据”等需求时,天衢 提供了极为优雅的挂起式协程解决方案。
1. 目标页:如何发送返回值并返回?
无论调用方采用哪种方式,目标页面只需要调用 popBackStack(result) 即可!它支持传递任何类型的对象。
@Router(path = "/settings")
@Composable
fun SettingsScreen(context: RouterContext) {
val navigator = LocalNavigator.current
Button(onClick = {
navigator.popBackStack(result = "这里是修改后的最新设置数据")
}) {
Text("保存并返回")
}
}
2. 调用方做法一:协程挂起式 (awaitNavigateForResult) 【⭐ 强烈推荐】
告别嵌套“地狱回调”,像写同步代码一样写异步等待!
(⚠️ 警告:请务必在与页面生命周期绑定的 navigator.coroutineScope 内启动协程,否则可能会因为屏幕重组导致协程取消!)
⚠️ 结果展示状态必须使用 rememberSaveable
如果你要把 awaitNavigateForResult() 得到的返回值展示在当前页面 UI 上,承接结果的状态必须写成 rememberSaveable,不要写成普通 remember。因为从目标页返回后,当前页可能发生状态恢复;此时如果使用 remember,即使已经拿到结果,界面状态也可能被重置,表现为“拿不到返回值”或“返回值一闪而过”。
val navigator = LocalNavigator.current
var returnedResult by rememberSaveable { mutableStateOf<String?>(null) }
Button(onClick = {
navigator.coroutineScope.launch {
result = navigator.awaitNavigateForResult()
(result String) {
returnedResult =
}
}
}) {
Text()
}
(returnedResult != ) {
Text()
}
3. 调用方做法二:经典回调监听 (navigateWithResult)
如果您处于非协程环境(如对接特定的 Swift 闭包),或个人偏爱经典回调:
Button(onClick = {
navigator.navigateWithResult("/settings").onResult { result ->
if (result is String) {
returnedResult = "回调获取: $result"
}
}
}) {
Text("前往设置页 (回调获取结果)")
}
🔌 六、跨模块解耦:服务发现 (Service Discovery)
路由不仅为了页面跳转,也是为了跨模块间的接口调用与反向依赖注入。上层 app 可以调用底层 feature 模块的具体实现,而无需互相强依赖。
1. 定义接口 (放在基础 Common 模块)
interface UserService {
suspend fun getUserName(): String
}
2. 具体实现 (放在具体的 Feature 模块)
添加 @Service 注解,KSP 处理器会自动将其注册到服务表中。
import shijing.tianqu.router.Service
@Service
class UserServiceImpl : UserService {
override suspend fun getUserName(): String {
return "天衢注册用户"
}
}
3. 在任意位置获取服务 (支持挂起与普通获取)
天衢 提供了两种灵活的方式来获取服务实例:
方式一:在 Compose 中基于协程挂起获取 (rememberService)
结合 Compose 最佳实践,无阻塞安全获取。
import shijing.tianqu.runtime.utils.rememberService
@Composable
fun HomeScreen(context: RouterContext) {
val userService = rememberService<UserService>()
val userName = userService?.getUserName() ?: "正在异步加载..."
Text("欢迎您, $userName")
}
方式二:在任意非 Compose 代码中普通获取 (ServiceManager.getService)
如果您在 ViewModel、Repository 或是普通的方法中,希望直接拿到服务实例:
import shijing.tianqu.runtime.service.ServiceManager
fun fetchUserData() {
val userService = ServiceManager.getService<UserService>()
}
🧬 七、ViewModel 与页面生命周期深度绑定
原生的 viewModel() 在纯 Compose Multiplatform 项目中往往缺乏路由弹栈感知能力。天衢 提供了与单次页面路由同生共死的专有 ViewModel,并支持三种不同的获取方式,满足各种复杂场景的需求。
1. 声明标准的 ViewModel
import androidx.lifecycle.ViewModel
import kotlinx.coroutines.flow.MutableStateFlow
class CounterViewModel : ViewModel() {
val count = MutableStateFlow(0)
init { println("ViewModel 初始化分配") }
override fun onCleared {
.onCleared()
println()
}
}
2. 获取 ViewModel 的三种方式
在路由页面中,您可以通过以下三种方式获取与当前页面生命周期绑定的 ViewModel:
方式一:反射无参构造获取 (tianquViewModel<T>())
最简单直接的方式,适用于 ViewModel 只有无参构造函数的情况。在 JVM/Android 平台上通过反射实例化。
import shijing.tianqu.runtime.tianquViewModel
@Router(path = "/demo_viewmodel")
@Composable
fun DemoViewModelScreen(context: RouterContext) {
val viewModel = tianquViewModel<CounterViewModel>()
}
- 优点:使用极其简单,无需额外配置,开箱即用。
- 缺点:依赖反射机制,在 iOS 等 Kotlin/Native 平台上不支持无参反射实例化,会导致 Crash;不支持带参数的构造函数。
方式二:自定义 Factory 获取 (tianquViewModel<T>(factory))
适用于 ViewModel 需要传递参数(如 Repository、UseCase)或在 iOS 平台上运行的情况。
- 优点:灵活性最高,支持带参数的构造函数;完全兼容所有平台(包括 iOS)。
- 缺点:每次使用都需要手动编写和传入 Factory,样板代码较多,略显繁琐。
方式三:基于 KSP 的全自动依赖注入 (tianQuViewModelInject<T>()) 【⭐ 强烈推荐】
结合了前两者的优点,通过 @InjectViewModel 注解在编译期生成工厂代码,实现无反射、跨平台的自动注入。
步骤 A:在 ViewModel 上添加注解
import shijing.tianqu.router.InjectViewModel
@InjectViewModel
class CounterViewModel : ViewModel() {
}
步骤 B:在页面中一键获取
import shijing.tianqu.runtime.tianQuViewModelInject
@Router(path = "/demo_viewmodel")
@Composable
fun DemoViewModelScreen(context: RouterContext) {
val viewModel = tianQuViewModelInject<CounterViewModel>()
}
- 优点:无反射(性能极佳)、完全跨平台(完美支持 iOS)、无样板代码(使用极其简单)。
- 缺点:目前生成的默认工厂仅支持无参构造函数(若需带参构造,请继续使用方式二)。
💡 内存泄漏终结者:无论您使用哪种方式获取,当点击返回,当前页面被 popBackStack 移出栈时,框架将主动触发 ViewModel 的 onCleared(),杜绝任何内存泄漏。
🧩 八、协程驱动的动态按需懒加载 (Dynamic Feature)
在大型项目中,为了减小包体积或加快初始启动速度,某些业务模块可以设计为动态下载与按需加载。在传统路由中,这往往需要复杂的异步回调与占位页面。
而在 天衢 路由中,得益于底层的协程架构与强大的全局守卫 (RouterGuard),您可以像写同步代码一样轻松实现带有等待 UI 的动态模块加载。
核心原理:
- 业务层调用挂起函数
navigator.push("/dynamic_route"),并在协程外层控制 "Loading 转圈" UI 状态。
完整演示代码:
通过这种方式,我们不仅完美隔离了底层模块的动态下载逻辑,同时让上层 UI 得到了优雅的状态管理,一切都得益于 Kotlin 协程 suspend 机制的强大力量。
🎨 九、其他极客能力全景公开
1. 多返回栈嵌套管理与 Tab 状态持久化
App 主页往往包含底部的多个 Tab。天衢 底层深度打通了 SaveableStateHolder,完美解决“切换 Tab 重新渲染导致输入内容与滚动条丢失”的问题。这是一种原生的“单宿主+多挂载点”轻量实现:
2. Compose 原生共享元素动画 (Shared Element)
天衢 基于 Compose 1.7 实现了原生的共享元素能力。您只需用 routerSharedBounds(key) 为发起方和接收方的组件打上相同的 Key 标签,点击跳转时,框架将为您自动呈现惊艳的形变飞跃动画。
import shijing.tianqu.runtime.transition.routerSharedBounds
Box(
modifier = Modifier
.routerSharedBounds(key = "shared_avatar_id")
.size(100.dp)
.clickable { navigator.navigateTo("/detail") }
)
Image(
modifier = Modifier
.routerSharedBounds(key = "shared_avatar_id")
.fillMaxWidth()
)
3. 全局路由 404 降级拦截处理
意外跳转到了未注册的页面怎么办?天衢 不会发生崩溃,而是触发一个 NotFound 事件!您只需在 rememberAppTianQuState 中配置 onRouteEvent,即可实现自由调度或重定向。
val navigator = rememberAppTianQuState {
startRoute = "/home"
onRouteEvent = { event, nav ->
when (event) {
is RouterEvent.NotFound -> {
println("⚠️ [全局降级拦截] 找不到路由: ${event.url}")
nav.navigateTo("/home")
}
is RouterEvent.Navigated -> {
println("ℹ️ 跳转成功: ${event.url}")
}
}
}
}
4. 离线 DeepLink 意图缓存 (Offline DeepLink Caching)
当应用在后台被系统杀死,或者尚未启动时,用户通过外部 DeepLink 唤起 App。此时底层的 UI 引擎和 Navigator 尚未初始化完毕,如果直接执行跳转,意图将会丢失。
天衢 路由内置了 DeepLinkManager,利用协程无界 Channel 实现了离线意图的 FIFO 缓存。
步骤 1:在原生平台入口接收并分发 DeepLink
val url = intent.data?.toString() ?: return
DeepLinkManager.dispatch(url)
步骤 2:Navigator 自动消费
当 Navigator 初始化完成后,它会自动在内部协程中收集并消费这些被缓存的意图,确保用户无缝跳转到目标页面,绝不丢失任何一次外部唤醒!
5. 声明一个弹窗路由 (Dialog)
天衢支持将普通路由节点声明为弹窗类型,弹窗将悬浮展示在现有页面之上。
您可以通过 BackHandler 和 clickable 拦截区域自由控制物理返回键与点击外部的关闭逻辑。
上面的示例对应仓库中的 composeApp/src/commonMain/kotlin/shijing/tianqu/screens/DemoDialogScreen.kt,说明天衢不只支持全屏页面跳转,也支持将路由节点作为弹窗悬浮在当前页面之上。弹窗的遮罩、点击空白关闭、返回键关闭等交互由业务层自己定义。
感谢您选择使用 天衢,希望这条大道能让您的跨平台开发之旅风雨无阻!