Kotlin Multiplatform 多平台路由框架实现
- 从开始到实现
- 为什么要自己实现
- 框架实现思路
- 移动端
- 桌面端
- 代码实现【移动端,桌面端】实现,单窗口页面路由
- 文件介绍
- PageFrame.kt
- PageConfig.kt
- PageRoute.kt
- PageOperator.kt
- PageFrame.kt
- PageConfig.kt
- PageRoute.kt
- PageOperator.kt
- 代码实现【移动端,桌面端】实现,单窗口页面路由
- 文件介绍
- WindowFrame.kt
- WindowConfig.kt
- WindowRoute.kt
- WindowOperator.kt
- WindowFrame.kt
- WindowConfig.kt
- WindowRoute.kt
- WindowOperator.kt
- 目前的不足以及待完善的功能
- 桌面端打开关闭窗口数据传递,这个功能目前我的项目没有使用到,所以没有做出对应实现
- 目前还不支持标题栏自定义,默认情况下会有一个标题拦,但是如果想去掉或者做美化处理,就还不支持,得自己实现了
从开始到实现
为什么要自己实现
目前由于Kotlin Multiplatform 的 web端处于实现阶段,而ios端处于beta端,相较于其他多平台解决方案并不完善,多平台库的缺失,导致目前我还未发现市面上开源可用的多平台路由框架的实现,而这个之前的做法是在每端独立实现,而Kotlin Multiplatform的野心不止于此,未来肯定会有众多优秀的多平台框架的出现。今天介绍的是自己因为多平台路由框架的缺失而自己实现的解决方案,如有问题,请及时指出,会及时纠正。
(图片来源网络,侵删)框架实现思路
不同平台对路由的解决方案有所不同,但借助于Kotlin Multiplatform可以共享公共代码,介绍了许多公共代码实现
移动端
桌面端
- 能够打开多个窗口
- 窗口内部有单独的页面路
- 其余同移动端相同
代码实现【移动端,桌面端】实现,单窗口页面路由
文件介绍
PageFrame.kt
这个文件是给使用者使用的,由于适配桌面端,多了个windowRoute,但是本质上移动端是一个只有一个窗口的应用,所以移动端使用可以不指定windowRoute,因为设置了一个Unit作为默认值使用,用户只需要配置启动路由,所有路由的配置以及windowRoute,为什么windowRoute放在最后,还移动端可以不需要指定参数。
(图片来源网络,侵删)理论上支持Compose级别的路由切换,移动端不明显,在桌面端可以做局部Compose的切换,只要给不同的windowRoute就可以了,这个变量还是用于适配桌面端的。
PageConfig.kt
每个页面的配置文件,配置路由和对应的Compsable页面,看到content是一个PageOperator的@Composable扩展方法,目的是可以使用PageOperator类里面的一些方法
PageRoute.kt
用于管理页面路由的文件,如:初始化页面路由,获取当前页面路由,设置页面路由状态等,具体见代码
PageOperator.kt
这个文件用于一些提供给使用者的方法,比如:routeTo 路由页面,back 返回,并且支持传递数据,如何接收数据?可以使用onCreate()和onBack() 这两个方法,里面有get可以获取到对应的数据,同时可以获取到 from,从哪个页面进入或返回,由于Compose的特性,想要在单个PageOperator使用多个onCreate或者onBack,必须指定不同的flag,不然后者可能会调用不到
PageFrame.kt
@Composable fun PageFrame( launch: PageRoute, vararg configs: PageConfig, windowRoute: Any = Unit, ) { initPageRoute(windowRoute, launch) configs.forEach { val status = getPageRouteStatus(windowRoute) val animation = when (status) { PageRouteStatus.OnCreate -> RouteAnimationHorizontalOnCreate PageRouteStatus.OnBack -> RouteAnimationHorizontalOnBack } val currentPageRoute = getCurrentPageRoute(windowRoute) AnimatedVisibility( visible = it.pageRoute == currentPageRoute, enter = animation.enter, exit = animation.exit, label = it.pageRoute.title ) { val pageOperator = PageOperator.getInstance(windowRoute, it.pageRoute) it.content(pageOperator) } } }
PageConfig.kt
data class PageConfig( val pageRoute: PageRoute, val content: @Composable PageOperator.() -> Unit )
PageRoute.kt
/** * 页面路由 */ interface PageRoute { val title: String } /** * 路由状态 */ enum class PageRouteStatus { OnCreate, OnBack } private var pageRouteStateMap: SnapshotStateMap? = null private val pageRouteStatusMap = mutableMapOf() private val pageRouteStackMap = mutableMapOf>() private val fromPageRouteMap = mutableMapOf() /** * 设置页面路由 */ internal fun setCurrentPageRoute(windowRoute: Any, pageRoute: PageRoute) { pageRouteStateMap!![windowRoute] = pageRoute } /** * 获取页面路由 */ fun getCurrentPageRoute(windowRoute: Any): PageRoute { return pageRouteStateMap!![windowRoute]!! } /** * 设置页面路由状态 */ internal fun setPageRouteStatus(windowRoute: Any, pageRouteStatus: PageRouteStatus) { pageRouteStatusMap[windowRoute] = pageRouteStatus } /** * 获取页面路由状态 */ internal fun getPageRouteStatus(windowRoute: Any): PageRouteStatus { return pageRouteStatusMap[windowRoute] ?: PageRouteStatus.OnCreate.also { pageRouteStatusMap[windowRoute] = it } } internal fun getPageRouteStackSize(windowRoute: Any): Int { return pageRouteStackMap[windowRoute]?.size ?: 0 } /** * 页面路由进栈 */ internal fun pushToPageRouteStack(windowRoute: Any, pageRoute: PageRoute) { val stack = pageRouteStackMap[windowRoute] ?: stackOf().also { pageRouteStackMap[windowRoute] = it } stack.push(pageRoute) } internal fun popFromPageRouteStack(windowRoute: Any): PageRoute { return pageRouteStackMap[windowRoute]!!.pop() } internal fun getPageRouteStackTop(windowRoute: Any): PageRoute { return pageRouteStackMap[windowRoute]!!.top() } internal fun removeFromPageRouteStack(windowRoute: Any, pageRoute: PageRoute) { pageRouteStackMap[windowRoute]?.remove(pageRoute) } internal fun removeAtFromPageRouteStack(windowRoute: Any, index: Int): PageRoute { return pageRouteStackMap[windowRoute]!!.removeAt(index) } /** * 设置上个页面路由 */ internal fun setFromPageRoute(windowRoute: Any, pageRoute: PageRoute) { fromPageRouteMap[windowRoute] = pageRoute } /** * 获取上个页面路由 */ internal fun getFromPageRoute(windowRoute: Any): PageRoute { return fromPageRouteMap[windowRoute]!! } /** * 初始化页面路由 */ @Composable fun initPageRoute(windowRoute: Any, pageRoute: PageRoute) { if (pageRouteStateMap == null) { pageRouteStateMap = remember { pageRouteStackMap[windowRoute] = stackOf() mutableStateMapOf() } } pushToPageRouteStack(windowRoute, pageRoute) pageRouteStateMap!![windowRoute] = pageRoute }
PageOperator.kt
class PageOperator private constructor( private val windowRoute: Any, val pageRoute: PageRoute, ) { private val onCreate = OnCreate() private val onBack = OnBack() companion object { private val cacheMap by lazy { mutableMapOf>() } /** * 获取页面操作实例 */ fun getInstance(windowRoute: Any, pageRoute: PageRoute): PageOperator { val cache = cacheMap[windowRoute] ?: mutableMapOf().also { cacheMap[windowRoute] = it } return cache[pageRoute] ?: PageOperator(windowRoute, pageRoute).also { cache[pageRoute] = it } } } /** * 页面之间传递的数据 */ private object PageLocalData { private val createDataMap by lazy { mutableMapOf>() } private val backDataMap by lazy { mutableMapOf>() } fun setCreateData(windowRoute: Any, pageRoute: PageRoute, data: Any?) { val createData = createDataMap[windowRoute] ?: mutableMapOf().also { createDataMap[windowRoute] = it } createData[pageRoute] = data } fun getCreateData(windowRoute: Any, pageRoute: PageRoute): Any? { return createDataMap[windowRoute]?.get(pageRoute) } fun setBackData(windowRoute: Any, pageRoute: PageRoute, data: Any?) { val backData = backDataMap[windowRoute] ?: mutableMapOf().also { backDataMap[windowRoute] = it } backData[pageRoute] = data } fun getBackData(windowRoute: Any, pageRoute: PageRoute): Any? { return backDataMap[windowRoute]?.get(pageRoute) } fun clear(windowRoute: Any, pageRoute: PageRoute) { createDataMap[windowRoute]?.let { if (it.containsKey(pageRoute)) { it -= pageRoute } } backDataMap[windowRoute]?.let { if (it.containsKey(pageRoute)) { it -= pageRoute } } } } /** * 回调记录 */ private object CallbackRecord { private val recordOnCreateMap by lazy { mutableMapOf>>() } private val recordOnBackMap by lazy { mutableMapOf>>() } fun recordOnCreate(windowRoute: Any, pageRoute: PageRoute, flag: Any) { val recordMap = recordOnCreateMap[windowRoute] ?: mutableMapOf().also { recordOnCreateMap[windowRoute] = it } val records = recordMap[pageRoute] ?: mutableSetOf().also { recordMap[pageRoute] = it } records += flag } fun isRecordOnCreate(windowRoute: Any, pageRoute: PageRoute, flag: Any): Boolean { val recordMap = recordOnCreateMap[windowRoute] ?: return false val records = recordMap[pageRoute] ?: return false return records.contains(flag) } fun recordOnBack(windowRoute: Any, pageRoute: PageRoute, flag: Any) { val recordMap = recordOnBackMap[windowRoute] ?: mutableMapOf().also { recordOnBackMap[windowRoute] = it } val records = recordMap[pageRoute] ?: mutableSetOf().also { recordMap[pageRoute] = it } records += flag } fun isRecordOnBack(windowRoute: Any, pageRoute: PageRoute, flag: Any): Boolean { val recordMap = recordOnBackMap[windowRoute] ?: return false val records = recordMap[pageRoute] ?: return false return records.contains(flag) } /** * 清理回调记录 */ fun clear(windowRoute: Any, pageRoute: PageRoute) { recordOnCreateMap[windowRoute]?.let { if (it.containsKey(pageRoute)) { it -= pageRoute } } recordOnBackMap[windowRoute]?.let { if (it.containsKey(pageRoute)) { it -= pageRoute } } } } /** * 页面创建的时候执行回调 */ fun onCreate(flag: Any = Unit, callback: OnCreate.(from: PageRoute) -> Unit) { if (getPageRouteStatus(windowRoute) == PageRouteStatus.OnCreate) { if (!CallbackRecord.isRecordOnCreate(windowRoute, pageRoute, flag)) { CallbackRecord.recordOnCreate(windowRoute, pageRoute, flag) callback(onCreate, getFromPageRoute(windowRoute)) } } } /** * 页面返回的时候执行回调 */ fun onBack(flag: Any = Unit, callback: OnBack.(from: PageRoute) -> Unit) { if (getPageRouteStatus(windowRoute) == PageRouteStatus.OnBack) { if (!CallbackRecord.isRecordOnBack(windowRoute, pageRoute, flag)) { CallbackRecord.recordOnBack(windowRoute, pageRoute, flag) callback(onBack, getFromPageRoute(windowRoute)) } } } /** * 路由 */ fun routeTo(nextPageRoute: PageRoute, finish: Boolean = false) = routeTo(nextPageRoute, null, finish) /** * 路由 */ fun routeTo(nextPageRoute: PageRoute, data: T, finish: Boolean = false) { val topRoute = getPageRouteStackTop(windowRoute) if (topRoute != nextPageRoute) { setFromPageRoute(windowRoute, topRoute) setPageRouteStatus(windowRoute, PageRouteStatus.OnCreate) if (finish) { val route = getPageRouteStackTop(windowRoute) clear(route) } getInstance(windowRoute, nextPageRoute).onCreate.set(data) setCurrentPageRoute(windowRoute, nextPageRoute) pushToPageRouteStack(windowRoute, nextPageRoute) } } /** * 返回 */ fun back(depth: Int = 1) = back(null, depth) /** * 返回 */ fun back(data: T, depth: Int = 1) { if (getPageRouteStackSize(windowRoute) > 1) { repeat(min(getPageRouteStackSize(windowRoute) - 1, depth)) { setFromPageRoute(windowRoute, popFromPageRouteStack(windowRoute)) clear(getFromPageRoute(windowRoute)) } setPageRouteStatus(windowRoute, PageRouteStatus.OnBack) val route = getPageRouteStackTop(windowRoute) getInstance(windowRoute, route).onBack.set(data) setCurrentPageRoute(windowRoute, route) } } /** * 移除路由 */ fun removeRoute(pageRoute: PageRoute) { removeFromPageRouteStack(windowRoute, pageRoute) clear(pageRoute) } /** * 移除路由 0: 为栈顶 */ fun removeRouteAt(index: Int) { val route = removeAtFromPageRouteStack(windowRoute, index) clear(route) } /** * 清理数据 */ private fun clear(pageRoute: PageRoute) { PageLocalData.clear(windowRoute, pageRoute) CallbackRecord.clear(windowRoute, pageRoute) } inner class OnCreate internal constructor() { internal fun set(data: T) { PageLocalData.setCreateData(windowRoute, pageRoute, data) } @Suppress("UNCHECKED_CAST") fun get(): T { return PageLocalData.getCreateData(windowRoute, pageRoute) as T } } inner class OnBack internal constructor() { internal fun set(data: T) { PageLocalData.setBackData(windowRoute, pageRoute, data) } @Suppress("UNCHECKED_CAST") fun get(): T { return PageLocalData.getBackData(windowRoute, pageRoute) as T } } }
代码实现【移动端,桌面端】实现,单窗口页面路由
文件介绍
WindowFrame.kt
这个文件给客户端使用者使用,配置启动窗口路由,窗口配置等信息
WindowConfig.kt
这个文件用来配置窗口路由,窗口状态如:大小位置等信息,窗口图标以及对应的窗口布局
WindowRoute.kt
这个文件用于管理窗口的打开关闭,初始化窗口路由,程序销毁事件
WindowOperator.kt
这个文件用于给对应窗口提供 open其他窗口,close当签窗口的事件。并且管理整个app的关闭事件
WindowFrame.kt
@Composable fun ApplicationScope.WindowFrame( launch: WindowRoute, vararg configs: WindowConfig ) { initWindowRoute(launch, this::exitApplication) configs.forEach { val windowOperator = WindowOperator.getInstance(it.windowRoute) Window( onCloseRequest = windowOperator::close, state = it.Windowstate, visible = windowVisible(it.windowRoute), title = it.windowRoute.title, icon = it.windowIcon ) { it.content(windowOperator) } } }
WindowConfig.kt
data class WindowConfig( val windowRoute: WindowRoute, val windowState: WindowState = WindowState(), val windowIcon: Painter? = null, val content: @Composable WindowOperator.() -> Unit )
WindowRoute.kt
interface WindowRoute { val title: String } internal lateinit var windowRouteState: SnapshotStateMap fun windowVisible(windowRoute: WindowRoute): Boolean = windowRouteState[windowRoute] ?: false.also { windowRouteState[windowRoute] = false } val needExit: Boolean get() = windowRouteState.values.all { !it } internal lateinit var appExitApplication: () -> Unit @Composable fun initWindowRoute(windowRoute: WindowRoute, exitApplication: () -> Unit) { if (!::windowRouteState.isInitialized) { windowRouteState = remember { mutableStateMapOf(windowRoute to true) } appExitApplication = exitApplication } }
WindowOperator.kt
class WindowOperator private constructor( val windowRoute: WindowRoute ) { companion object { private val cache = mutableMapOf() /** * 获取窗口操作实例 */ fun getInstance(windowRoute: WindowRoute): WindowOperator = cache[windowRoute] ?: WindowOperator(windowRoute).also { cache[windowRoute] = it } } fun open(windowRoute: WindowRoute, finish: Boolean = false) { windowRouteState[windowRoute] = true if (finish) { windowRouteState[this.windowRoute] = true } } fun close() { windowRouteState[this.windowRoute] = false if (needExit) appExitApplication() } }
目前的不足以及待完善的功能
桌面端打开关闭窗口数据传递,这个功能目前我的项目没有使用到,所以没有做出对应实现
目前还不支持标题栏自定义,默认情况下会有一个标题拦,但是如果想去掉或者做美化处理,就还不支持,得自己实现了