Kotlin Multiplatform 多平台路由框架实现

从开始到实现

为什么要自己实现

目前由于Kotlin Multiplatform 的 web端处于实现阶段,而ios端处于beta端,相较于其他多平台解决方案并不完善,多平台库的缺失,导致目前我还未发现市面上开源可用的多平台路由框架的实现,而这个之前的做法是在每端独立实现,而Kotlin Multiplatform的野心不止于此,未来肯定会有众多优秀的多平台框架的出现。今天介绍的是自己因为多平台路由框架的缺失而自己实现的解决方案,如有问题,请及时指出,会及时纠正。

框架实现思路

不同平台对路由的解决方案有所不同,但借助于Kotlin Multiplatform可以共享公共代码,介绍了许多公共代码实现

移动端

  1. 页面能够的进入和退出
  2. 在页面进入或者退出的时候能够给下个展示的页面传递数据
  3. 可以一次性返回多个页面
  4. 可以在开启下一个页面的同时关闭当前页面
  5. 知道是哪个页面跳转到当前页面的

桌面端

  1. 能够打开多个窗口
  2. 窗口内部有单独的页面路
  3. 其余同移动端相同

代码实现【移动端,桌面端】实现,单窗口页面路由

文件介绍

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<Any, PageRoute>? = null

private val pageRouteStatusMap = mutableMapOf<Any, PageRouteStatus>()

private val pageRouteStackMap = mutableMapOf<Any, Stack<PageRoute>>()

private val fromPageRouteMap = mutableMapOf<Any, PageRoute>()

/**
 * 设置页面路由
 */
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<PageRoute>().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<Any, MutableMap<PageRoute, PageOperator>>()
		}
		
		/**
		 * 获取页面操作实例
		 */
		fun getInstance(windowRoute: Any, pageRoute: PageRoute): PageOperator {
			val cache = cacheMap[windowRoute] ?: mutableMapOf<PageRoute, PageOperator>().also {
				cacheMap[windowRoute] = it
			}
			return cache[pageRoute] ?: PageOperator(windowRoute, pageRoute).also {
				cache[pageRoute] = it
			}
		}
	}
	
	/**
	 * 页面之间传递的数据
	 */
	private object PageLocalData {
		
		private val createDataMap by lazy {
			mutableMapOf<Any, MutableMap<PageRoute, Any?>>()
		}
		
		private val backDataMap by lazy {
			mutableMapOf<Any, MutableMap<PageRoute, Any?>>()
		}
		
		fun setCreateData(windowRoute: Any, pageRoute: PageRoute, data: Any?) {
			val createData = createDataMap[windowRoute] ?: mutableMapOf<PageRoute, Any?>().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<PageRoute, Any?>().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<Any, MutableMap<PageRoute, MutableSet<Any>>>()
		}
		
		private val recordOnBackMap by lazy {
			mutableMapOf<Any, MutableMap<PageRoute, MutableSet<Any>>>()
		}
		
		fun recordOnCreate(windowRoute: Any, pageRoute: PageRoute, flag: Any) {
			val recordMap = recordOnCreateMap[windowRoute] ?: mutableMapOf<PageRoute, MutableSet<Any>>().also {
				recordOnCreateMap[windowRoute] = it
			}
			val records = recordMap[pageRoute] ?: mutableSetOf<Any>().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<PageRoute, MutableSet<Any>>().also {
				recordOnBackMap[windowRoute] = it
			}
			val records = recordMap[pageRoute] ?: mutableSetOf<Any>().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 <T> 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 <T> 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 <T> set(data: T) {
			PageLocalData.setCreateData(windowRoute, pageRoute, data)
		}
		
		@Suppress("UNCHECKED_CAST")
		fun <T> get(): T {
			return PageLocalData.getCreateData(windowRoute, pageRoute) as T
		}
	}
	
	inner class OnBack internal constructor() {
		
		internal fun <T> set(data: T) {
			PageLocalData.setBackData(windowRoute, pageRoute, data)
		}
		
		@Suppress("UNCHECKED_CAST")
		fun <T> 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<WindowRoute, Boolean>

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<WindowRoute, WindowOperator>()
		
		/**
		 * 获取窗口操作实例
		 */
		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()
	}
}

目前的不足以及待完善的功能

桌面端打开关闭窗口数据传递,这个功能目前我的项目没有使用到,所以没有做出对应实现

目前还不支持标题栏自定义,默认情况下会有一个标题拦,但是如果想去掉或者做美化处理,就还不支持,得自己实现了

  • 35
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Kotlin Multiplatform 是一种由 JetBrains 开发的跨平台开发框架。它允许开发人员使用 Kotlin 语言编写代码,然后在多个平台上运行,包括 Android、iOS、Web 等。与传统的跨平台解决方案相比,Kotlin Multiplatform 提供了更高的灵活性和性能。 Kotlin Multiplatform 的核心思想是共享代码。开发人员可以编写一个通用的 Kotlin 模块,其中包含与平台无关的业务逻辑和算法。然后,他们可以根据不同的目标平台,编写平台特定的代码。这样,开发人员可以在不同平台之间共享核心逻辑,减少了重复代码的编写,并且保持了一致性。 Kotlin Multiplatform 目前已经应用于许多项目中。对于 Android 开发人员来说,它提供了更好的性能和开发体验。它允许开发人员在 Android 和 iOS 上使用相同的 Kotlin 代码库,从而加快了开发速度和代码复用。对于 iOS 开发人员来说,Kotlin Multiplatform 可以通过共享核心业务逻辑来简化跨平台开发,并且可以与现有的 Objective-C 或 Swift 代码无缝集成。 总之,Kotlin Multiplatform 是一个强大的跨平台开发框架,可以大大简化和提高开发人员的工作效率。它同时适用于 Android 和 iOS 开发,并且允许开发人员在不同平台之间共享核心逻辑。在未来,我们可以预见 Kotlin Multiplatform 将会在跨平台开发领域发挥更大的作用,并且有望成为开发人员的首选解决方案。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值