Kotlin Compose Kotlin Compose 03 Window
文章目录
在 Compose 中,有几个基本的概念
概念
Application
Application 是一个 Compose 的应用,它通常是一个顶层的 Composable 函数,它的参数是一个 @Composable
的函数,这个函数就是我们的应用的主体。
Tray
Tray 是一个托盘(状态栏的图标),它通常是一个顶层的 Composable 函数,它的参数是一个 @Composable
的函数,这个函数就是我们的托盘的主体。
Window
Window 是一个窗口,对应了 windows/macOS/Linux 的窗口,它通常是一个顶层的 Composable 函数,
它的参数是一个 @Composable
的函数,这个函数就是我们的窗口的主体。
Widget
Widget 是一个组件,也可以包含多个 Widget,包括 Text,Button,Image 等,都是 Widget,通常是一个 @Composable
函数。
架构
学会管理 Window 在 Kotlin Compose 中是非常重要的。
Compose 的结构如下,单Appliction,多 Window,多 Widget
每个窗口都有独立的Menu
Tray则由Application管理
graph TD
APP[Appliction]-->W1[Window]
W1-->V1[Widget]
W1-->V2[Widget]
W1-->V3[Widget]
APP-->Tray[Tray]
APP-->W2[Window]
W2-->V11[Widget]
W2-->V12[Widget]
W2-->V13[Widget]
代码的组织
WindowManager
首先,需要配置一个顶层状态来使窗口和窗口状态可以对应上
1package top.kikt.examples.window
2
3import androidx.compose.runtime.Composable
4import androidx.compose.runtime.mutableStateListOf
5import androidx.compose.ui.window.ApplicationScope
6import androidx.compose.ui.window.MenuBar
7import androidx.compose.ui.window.MenuBarScope
8import androidx.compose.ui.window.Window
9import top.kikt.examples.app.Content
10import top.kikt.examples.example.image.ImageIconPainter
11import top.kikt.examples.menubar.DefaultMenu
12
13/**
14 * 定义一个了一个窗口管理器,用于管理窗口
15 */
16class WindowManager(private val applicationScope: ApplicationScope) {
17
18 /**
19 * 所有窗口对应的状态
20 */
21 val windows = mutableStateListOf<WindowState>()
22
23 init {
24 // 打开一个默认的窗口
25 openDefaultWindow()
26 }
27
28 fun openDefaultWindow() {
29 windows += DefaultWindowState(this)
30 }
31
32 fun openNewWindow(state: WindowState) {
33 // 使用新的状态打开一个窗口
34 windows += state
35 println("Open the new window, the windows count: ${windows.count()}")
36 }
37
38 // 关闭一个指定的窗口
39 fun close(windowState: WindowState) {
40 windows.remove(windowState)
41 }
42
43 // 用于退出应用
44 @Suppress("unused")
45 fun exitApplication() {
46 applicationScope.exitApplication()
47 }
48}
49
50/** 定义一个默认的窗口状态 */
51abstract class WindowState(private val windowManager: WindowManager) {
52
53 /** 每个状态可以打开新窗口 */
54 fun openNewWindow(
55 menu: @Composable ((windowState: WindowState, menuBarScope: MenuBarScope) -> Unit)? = { state, scope ->
56 scope.defaultMenu(state)
57 },
58 content: @Composable (windowState: WindowState) -> Unit,
59 ) {
60 windowManager.openNewWindow(WidgetWindowState(windowManager, content, menu))
61 }
62
63 /** 默认的菜单栏 */
64 @Composable
65 protected fun MenuBarScope.defaultMenu(windowState: WindowState) {
66 // 定义一个菜单项
67 DefaultMenu(windowState)
68 }
69
70 /** 关闭窗口 */
71 fun close() {
72 windowManager.close(this)
73 }
74
75 /** 窗口的内容,抽象化,由子类实现 */
76 @Composable
77 abstract fun createContent(applicationScope: ApplicationScope)
78}
79
80/** 默认的窗口状态 */
81private class DefaultWindowState(windowManager: WindowManager) : WindowState(windowManager) {
82 @Composable
83 override fun createContent(applicationScope: ApplicationScope) {
84 Window(
85 onCloseRequest = this::close,
86 title = "Compose for Desktop example",
87 icon = ImageIconPainter(),
88 ) {
89 Content(this@DefaultWindowState) // 一个默认的窗口内容
90 MenuBar { defaultMenu(this@DefaultWindowState) } // 一个默认的菜单栏
91 }
92 }
93}
94
95/** 用于创建一个窗口,所有内容外部传入 */
96class WidgetWindowState(
97 windowManager: WindowManager,
98 private val widget: @Composable (windowState: WindowState) -> Unit,
99 private val menu: @Composable ((windowState: WindowState, menuBarScope: MenuBarScope) -> Unit)? = null,
100) : WindowState(windowManager) {
101
102 @Composable
103 override fun createContent(applicationScope: ApplicationScope) {
104 Window(
105 onCloseRequest = { close() },
106 title = "Compose for Desktop example",
107 icon = ImageIconPainter(),
108 ) {
109 widget(this@WidgetWindowState)
110 MenuBar {
111 menu?.invoke(this@WidgetWindowState, this)
112 }
113 }
114 }
115}
上面是定义了一个窗口管理器,只需要调用对应的方法就可以管理状态,但是状态有了还需要和应用的窗口对应起来
Create application
1import androidx.compose.runtime.*
2import androidx.compose.ui.window.application
3import top.kikt.examples.example.image.TrayWindowPaint
4import top.kikt.examples.window.WindowManager
5
6fun main() = application {
7 val windowManager = remember { WindowManager(this) }
8
9 println("The windowManager.windows count: ${windowManager.windows.count()}")
10
11 for (window in windowManager.windows) {
12 key(window) { // 使用窗口状态作为 key,保证窗口状态和窗口的一一对应
13 window.createContent(this) // 然后调用窗口状态的 createContent 方法来创建窗口
14 }
15 }
16
17 if (windowManager.windows.isEmpty()) {
18 TrayWindowPaint(windowManager) // 如果没有窗口,就创建一个托盘窗口,用于打开新窗口
19 }
20}
示例
1@Composable
2@Preview
3fun Content(window: WindowState) {
4 ListView(contentPadding = PaddingValues(10.dp)) {
5 ExampleWindowItem("Hello World", window) {
6 HelloWorld()
7 }
8 ExampleWindowItem("Image", window) {
9 ImageExample()
10 }
11 ExampleWindowItem("Mouse Event", window) {
12 MouseEventExample()
13 }
14 }
15}
16
17@Composable
18fun ExampleWindowItem(title: String, window: WindowState, content: @Composable () -> Unit) {
19 Button(onClick = {
20 window.openNewWindow { content() }
21 }) {
22 Text("Open Example: $title")
23 }
24}
预览