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}

预览