一个简单并提供高度扩展功能的 Compose 导航组件,同时支持 Android 和 Desktop 平台。
// Android
implementation("io.github.succlz123:compose-screen-android:0.0.2")
// Desktop
implementation("io.github.succlz123:compose-screen-desktop:0.0.2")
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
// 生命周期,按键返回等都由 activity 接管
ScreenContainer(this) {
// Screen Host
}
// 或者分别设置相应所有者
ScreenContainer(this, this, this, this) {
// Screen Host
}
}
}
}
fun main() = application {
val size = DpSize(480.dp, 720.dp)
val windowState = rememberWindowState(size = size)
ScreenContainer(
title = "Compose Screen",
state = windowState,
onCloseRequest = {
exitApplication()
},
) {
// Screen Host
}
}
// 创建 screen navigator
val screenNavigator = rememberScreenNavigator()
// 初始化应用页面,确定根页面
ScreenHost(screenNavigator = screenNavigator, rootScreenName = "your root screen") {
// 注册添加你所需要展示的页面
groupScreen(screenName = ("your root screen")) {
XXXScreen()
}
groupScreen(screenName = "your YYY screen") {
YYYScreen()
}
itemScreen(screenName = "your popup screen") {
ZZZPopupScreen()
}
...
}
// 获取 screenNavigator
val screenNavigator = LocalScreenNavigator.current
// 普通导航
screenNavigator.push(screenName = "your screen")
// 导航携带参数
val count = 1
screenNavigator.push(
screenName = "your screen",
arguments = ScreenArgs.putValue("KEY_COUNT", count)
)
// singleTop 栈顶复用
screenNavigator.push(
screenName = "your screen",
pushOptions = PushOptions().apply {
removePredicate = PushOptions.SingleTopPredicate("your screen")
}
)
// singleTask 栈内复用
screenNavigator.push(
screenName = "your screen",
pushOptions = PushOptions().apply {
removePredicate = PushOptions.SingleTaskPredicate("your screen")
}
)
// 导航同时移除当前页面
screenNavigator.push(
screenName = "your screen",
pushOptions = PushOptions().apply {
removePredicate = PushOptions.PopItselfPredicate()
}
)
// 移除任意页面
screenNavigator.remove(screenName = "your screen")
screenNavigator.pop()
screenNavigator.popTo(screenName = "ZZZScreen")
val onResult = LocalScreenRecord.current.result
screenNavigator.pop(popOptions = PopOptions(popStackFinalInterceptor = { backstackList, _, _ ->
backstackList.size > 10
}))
PopupScreen 层级高于 Screen,附属于当前绑定的 Group Screen。退出 Group Screen 时,PopupScreen 同时消失。
ScreenHost(screenNavigator = screenNavigator, rootScreenName = Manifest.MainScreen) {
// register your popup screen
popup(screenName = Manifest.DialogPopupScreen) {
DialogPopupScreen(screenNavigator)
}
}
// show th dialog
screenNavigator.push(Manifest.DialogPopupScreen)
全局只有一个吐司,吐司永远在所有页面层级之上,切换页面不会导致吐司消失。
// 默认的吐司
screenNavigator.toast("Your toast.")
// 更多配置的默认的吐司
screenNavigator.toast(
arguments = ScreenArgs.putValue(KEY_TOAST_TIME, ARGS_TOAST_TIME_SHORT)
.putValue(KEY_TOAST_TIME_LOCATION, ARGS_TOAST_TIME_LOCATION_BOTTOM_CENTER)
.putValue(KEY_TOAST_MSG, "Your toast."))
// 显示自定义的吐司
screenNavigator.toast {
BoxWithConstraints(modifier = Modifier.fillMaxSize()) {
...
}
}
同一个 Group Screen 中只能存在一个 PopupWindow。退出 Screen PopupWindow 同时消失。
Column(modifier = Modifier.fillMaxSize().background(Color.White)) {
...
PopupWindowLayout(displayContent = {
// the content which you want to display
...
}, clickableContent = {
// such as a button layout
...
})
...
}
针对 Compose 为了更好的处理在某些特殊场景下的需要,ScreenLifecycleCallback 提供了 screenLifecycleState 和 hostLifecycleState 生命周期回调。
宿主 Android/Desktop 生命周期回调
Android 下目前 Compose 收不到 onCreate onDestroy 相应回调
Compose Screen - Host Lifecyle | Android | Desktop |
---|---|---|
onCreate | ||
onStart | ||
RESUMED | onResume | active |
PAUSED | onPasuse | inActive - isMinimized |
onStop | ||
onDestroy | ||
onDestroy | onCloseRequest |
Screen 抽象后的生命周期,通常来讲业务逻辑不应该依赖与此,切换页面会导致在此触发的 OnCreate 和 OnResume。
Compose Screen - Screen Lifecycle | Screen |
---|---|
CREATED | Push the new screen record into the stack |
RESUMED | Check the host lifecycle - onResume |
PAUSED | Check the host lifecycle - onPause |
PAUSED | A new screen was dispalyed |
DESTROYED | The screen record was out of the stack |
ScreenLifecycleCallback(screenRecord, screenLifecycleState = {
println("Screen lifecycle - $countValue: ${it.name}")
if (it == ComposeLifecycle.State.DESTROYED) {
screenNavigator.toast("Screen lifecycle - $countValue: is destroyed")
}
}, hostLifecycleState = {
println(">>> Host lifecycle: ${getPlatformName()} - ${it.name} <<<")
})
针对不同场景下的 ViewModel 使用, Compose Screen 提供了 viewModel,sharedViewModel,globalViewModel,androidViewModel 的扩展方法来方便使用。
- viewModel
// 绑定到当前 group screen 的 ViewModel,当 group screen 出栈的时候,该 ViewModel 被销毁
val viewModel = viewModel { YourViewModel() }
- sharedViewModel
// XXX -> YYY,当 YYY 出栈的时候,sharedViewModel 将继续存在,当 XXX 也出栈的时候, sharedViewModel 才被销毁
@Composable
fun XXXScreen(){
val sharedViewModel = sharedViewModel { YourViewModel() }
}
@Composable
fun YYYScreen(){
val sharedViewModel = sharedViewModel { YourViewModel() }
}
- globalViewModel
// 被绑定到 Screen Mananger 上的 ViewModel,全局唯一,只有 Screen Mananger 不在后才被销毁
val globalViewModel = globalViewModel { YourViewModel() }
// Android 平台独有,通过初始化时传入的 ViewModelStoreOwner,来获取或者生成 AndroidX 的 ViewModel.
val androidViewModel = androidViewModel { YourAndroidViewModel() }
框架内已经封装了几个常见转场动画,如下是右边进左边出的转场动画
screenNavigator.push(
"your screen",
pushOptions = PushOptions(
pushTransition = ScreenTransitionRightInLeftOutPush(),
popTransition = ScreenTransitionRightInLeftOutPop()
)
)
在 Compose Screen 初始化时,传入 OnBackPressedDispatcherOwner, 就可以响应系统返回按键。
可以通过 enableEscBack 来开启关闭 Compose Screen 响应 ESC 按键返回
val windowSizeOwner = LocalScreenWindowSizeOwner.current
val windowWidth = remember {
windowSizeOwner.getWindowHolder().size.value.width.toInt()
}
val windowHeight = remember {
windowSizeOwner.getWindowHolder().size.value.height.toInt()
}
val windowSizeClass = remember {
windowSizeOwner.getWindowHolder().sizeClass.value
}
// 具体参考 https://developer.android.com/jetpack/compose/layouts/adaptive
when (windowSizeClass) {
ScreenWindowSizeClass.Compact -> {}
ScreenWindowSizeClass.Medium -> {}
ScreenWindowSizeClass.Expanded -> {}
}
目前已自动处理了 Android 横竖屏切换或者配置变化下导致的异常状态恢复