
Simplifies navigation with highly extensible components for both mobile and desktop applications. Features include screen lifecycle management, customizable transitions, popups, toasts, and various ViewModel bindings for different contexts.
一个简单并提供高度扩展功能的 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.resultscreenNavigator.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 的扩展方法来方便使用。
// 绑定到当前 group screen 的 ViewModel,当 group screen 出栈的时候,该 ViewModel 被销毁
val viewModel = viewModel { YourViewModel() }// XXX -> YYY,当 YYY 出栈的时候,sharedViewModel 将继续存在,当 XXX 也出栈的时候, sharedViewModel 才被销毁
@Composable
fun XXXScreen(){
val sharedViewModel = sharedViewModel { YourViewModel() }
}
@Composable
fun YYYScreen(){
val sharedViewModel = sharedViewModel { YourViewModel() }
}// 被绑定到 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 横竖屏切换或者配置变化下导致的异常状态恢复
一个简单并提供高度扩展功能的 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.resultscreenNavigator.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 的扩展方法来方便使用。
// 绑定到当前 group screen 的 ViewModel,当 group screen 出栈的时候,该 ViewModel 被销毁
val viewModel = viewModel { YourViewModel() }// XXX -> YYY,当 YYY 出栈的时候,sharedViewModel 将继续存在,当 XXX 也出栈的时候, sharedViewModel 才被销毁
@Composable
fun XXXScreen(){
val sharedViewModel = sharedViewModel { YourViewModel() }
}
@Composable
fun YYYScreen(){
val sharedViewModel = sharedViewModel { YourViewModel() }
}// 被绑定到 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 横竖屏切换或者配置变化下导致的异常状态恢复