Kotlin Multiplatform Development Help

顶级窗口管理

Compose Multiplatform 桌面版提供了多种窗口管理功能。您可以将窗口隐藏到系统托盘、使其可拖动、调整尺寸、改变位置等。

打开与关闭窗口

使用 Window() 函数可创建常规窗口。若要在可组合作用域中使用,请在 application 入口点调用:

import androidx.compose.ui.window.Window import androidx.compose.ui.window.application fun main() = application { Window(onCloseRequest = ::exitApplication) { // 窗口内容 } }

作为可组合函数, Window() 允许以声明方式修改属性。例如先打开标题为"Untitled"的窗口,再动态更新标题:

import androidx.compose.material.Button import androidx.compose.material.Text import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.window.Window import androidx.compose.ui.window.application fun main() = application { var fileName by remember { mutableStateOf("Untitled") } Window(onCloseRequest = ::exitApplication, title = "$fileName - Editor") { Button(onClick = { fileName = "note.txt" }) { Text("保存") } } }
窗口属性:修改标题

条件控制

通过简单的 if 条件即可控制窗口开关。以下示例在任务完成后自动关闭窗口:

import androidx.compose.material.Text import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.window.Window import androidx.compose.ui.window.application import kotlinx.coroutines.delay fun main() = application { var isPerformingTask by remember { mutableStateOf(true) } LaunchedEffect(Unit) { // 执行耗时操作 delay(2000) isPerformingTask = false } if (isPerformingTask) { Window( onCloseRequest = ::exitApplication, title = "窗口1" ) { Text("正在执行任务,请稍候!") } } else { Window( onCloseRequest = ::exitApplication, title = "窗口2" ) { Text("你好,世界!") } } }
条件窗口

如需自定义关闭逻辑(如显示确认对话框),可通过 onCloseRequest 回调覆盖默认行为。以下示例采用声明式方式(isOpen = false )而非命令式(window.close() )关闭窗口:

import androidx.compose.material.Button import androidx.compose.material.Text import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.window.DialogWindow import androidx.compose.ui.window.Window import androidx.compose.ui.window.application fun main() = application { var isOpen by remember { mutableStateOf(true) } var isAskingToClose by remember { mutableStateOf(false) } if (isOpen) { Window( onCloseRequest = { isAskingToClose = true }, title = "重要文档" ) { if (isAskingToClose) { DialogWindow( onCloseRequest = { isAskingToClose = false }, title = "放弃未保存内容?" ) { Button( onClick = { isOpen = false } ) { Text("确认") } } } } } }
关闭确认

多窗口管理

对于多窗口应用,可创建独立的状态类并通过 mutableStateListOf 控制窗口:

import androidx.compose.runtime.Composable import androidx.compose.runtime.key import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.remember import androidx.compose.ui.window.MenuBar import androidx.compose.ui.window.Window import androidx.compose.ui.window.application fun main() = application { val applicationState = remember { MyApplicationState() } for (window in applicationState.windows) { key(window) { MyWindow(window) } } } @Composable private fun MyWindow( state: MyWindowState ) = Window(onCloseRequest = state::close, title = state.title) { MenuBar { Menu("文件") { Item("新建窗口", onClick = state.openNewWindow) Item("退出", onClick = state.exit) } } } private class MyApplicationState { val windows = mutableStateListOf<MyWindowState>() init { windows += MyWindowState("初始窗口") } fun openNewWindow() { windows += MyWindowState("窗口 ${windows.size}") } fun exit() { windows.clear() } private fun MyWindowState( title: String ) = MyWindowState( title, openNewWindow = ::openNewWindow, exit = ::exit, windows::remove ) } private class MyWindowState( val title: String, val openNewWindow: () -> Unit, val exit: () -> Unit, private val close: (MyWindowState) -> Unit ) { fun close() = close(this) }
多窗口示例

更复杂示例可参考 记事本示例

最小化到系统托盘

通过修改 windowState.isVisible 状态可实现窗口隐藏而非关闭:

import androidx.compose.material.Text import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.drawscope.DrawScope import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.window.Tray import androidx.compose.ui.window.Window import androidx.compose.ui.window.application import kotlinx.coroutines.delay fun main() = application { var isVisible by remember { mutableStateOf(true) } Window( onCloseRequest = { isVisible = false }, visible = isVisible, title = "计数器", ) { var counter by remember { mutableStateOf(0) } LaunchedEffect(Unit) { while (true) { counter++ delay(1000) } } Text(counter.toString()) } if (!isVisible) { Tray( TrayIcon, tooltip = "计数器", onAction = { isVisible = true }, menu = { Item("退出", onClick = ::exitApplication) }, ) } } object TrayIcon : Painter() { override val intrinsicSize = Size(256f, 256f) override fun DrawScope.onDraw() { drawOval(Color(0xFFFFA500)) } }
隐藏窗口到托盘

singleWindowApplication() 函数

通过 singleWindowApplication() 可快速创建单窗口应用,但有以下限制:

  • 仅支持单窗口

  • 无法自定义关闭逻辑

  • 运行时不可修改窗口属性

import androidx.compose.ui.window.singleWindowApplication fun main() = singleWindowApplication { // 窗口内容 }

替代方案是在 application 入口点使用 Window() 可组合函数

自适应窗口尺寸

当内容尺寸未知时,可将 WindowSize 的一个或两个维度设为 Dp.Unspecified ,系统会自动调整窗口尺寸以适应内容:

import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material.Text import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Window import androidx.compose.ui.window.application import androidx.compose.ui.window.rememberWindowState fun main() = application { Window( onCloseRequest = ::exitApplication, state = rememberWindowState(width = Dp.Unspecified, height = Dp.Unspecified), title = "自适应尺寸", resizable = false ) { Column(Modifier.background(Color(0xFFEEEEEE))) { Row { Text("标签1", Modifier.size(100.dp, 100.dp).padding(10.dp).background(Color.White)) Text("标签2", Modifier.size(150.dp, 200.dp).padding(5.dp).background(Color.White)) Text("标签3", Modifier.size(200.dp, 300.dp).padding(25.dp).background(Color.White)) } } } }
自适应窗口尺寸

窗口状态管理

WindowState 类管理窗口的布局方式(浮动/最大化/最小化/全屏)、位置和尺寸。状态变更会自动触发重组:

import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.material.Checkbox import androidx.compose.material.Text import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Window import androidx.compose.ui.window.WindowPlacement import androidx.compose.ui.window.WindowPosition import androidx.compose.ui.window.application import androidx.compose.ui.window.rememberWindowState fun main() = application { val state = rememberWindowState(placement = WindowPlacement.Maximized) Window(onCloseRequest = ::exitApplication, state, title = "窗口状态") { Column { Row(verticalAlignment = Alignment.CenterVertically) { Checkbox( state.placement == WindowPlacement.Fullscreen, { state.placement = if (it) { WindowPlacement.Fullscreen } else { WindowPlacement.Floating } } ) Text("全屏模式") } Row(verticalAlignment = Alignment.CenterVertically) { Checkbox( state.placement == WindowPlacement.Maximized, { state.placement = if (it) { WindowPlacement.Maximized } else { WindowPlacement.Floating } } ) Text("最大化") } Row(verticalAlignment = Alignment.CenterVertically) { Checkbox(state.isMinimized, { state.isMinimized = !state.isMinimized }) Text("最小化") } Text( "位置 ${state.position}", Modifier.clickable { val position = state.position if (position is WindowPosition.Absolute) { state.position = position.copy(x = state.position.x + 10.dp) } } ) Text( "尺寸 ${state.size}", Modifier.clickable { state.size = state.size.copy(width = state.size.width + 10.dp) } ) } } }
状态变更示例

监听窗口状态

通过 snapshotFlow() 可将状态变化传递到非组合式代码层(如数据库写入):

import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.unit.DpSize import androidx.compose.ui.window.Window import androidx.compose.ui.window.WindowPosition import androidx.compose.ui.window.application import androidx.compose.ui.window.rememberWindowState import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach fun main() = application { val state = rememberWindowState() Window(onCloseRequest = ::exitApplication, state) { LaunchedEffect(state) { snapshotFlow { state.size } .onEach(::onWindowResize) .launchIn(this) snapshotFlow { state.position } .filter { it.isSpecified } .onEach(::onWindowRelocate) .launchIn(this) } } } private fun onWindowResize(size: DpSize) { println("窗口尺寸变更 $size") } private fun onWindowRelocate(position: WindowPosition) { println("窗口位置变更 $position") }

对话框

使用 Window() 创建常规窗口, DialogWindow() 创建模态窗口(锁定父窗口直至关闭):

import androidx.compose.material.Button import androidx.compose.material.Text import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.window.DialogWindow import androidx.compose.ui.window.Window import androidx.compose.ui.window.WindowPosition import androidx.compose.ui.window.application import androidx.compose.ui.window.rememberDialogState fun main() = application { Window( onCloseRequest = ::exitApplication, ) { var isDialogOpen by remember { mutableStateOf(false) } Button(onClick = { isDialogOpen = true }) { Text(text = "打开对话框") } if (isDialogOpen) { DialogWindow( onCloseRequest = { isDialogOpen = false }, state = rememberDialogState(position = WindowPosition(Alignment.Center)) ) { // 对话框内容 } } } }

可拖动窗口区域

通过 WindowDraggableArea() 可为无边框窗口添加自定义标题栏或实现全窗口拖动:

import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.window.WindowDraggableArea import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Window import androidx.compose.ui.window.application fun main() = application { Window(onCloseRequest = ::exitApplication, undecorated = true) { WindowDraggableArea { Box(Modifier.fillMaxWidth().height(48.dp).background(Color.DarkGray)) } } }

该组件仅适用于 singleWindowApplication()Window()DialogWindow() 。在其他可组合函数中使用需添加 WindowScope 接收器:

import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.window.WindowDraggableArea import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Window import androidx.compose.ui.window.WindowScope import androidx.compose.ui.window.application fun main() = application { Window(onCloseRequest = ::exitApplication, undecorated = true) { AppWindowTitleBar() } } @Composable private fun WindowScope.AppWindowTitleBar() = WindowDraggableArea { Box(Modifier.fillMaxWidth().height(48.dp).background(Color.DarkGray)) }
可拖动区域示例

透明窗口与其他定制

创建透明窗口需设置 transparent=trueundecorated=true (透明窗口无法带边框)。以下示例实现圆角透明窗口:

import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Surface import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.draw.shadow import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Window import androidx.compose.ui.window.application import androidx.compose.material.Text import androidx.compose.runtime.* fun main() = application { var isOpen by remember { mutableStateOf(true) } if (isOpen) { Window( onCloseRequest = { isOpen = false }, title = "透明窗口示例", transparent = true, // 透明窗口必须无边框 undecorated = true, ) { Surface( modifier = Modifier.fillMaxSize().padding(5.dp).shadow(3.dp, RoundedCornerShape(20.dp)), color = Color.Transparent, // 圆角窗口 shape = RoundedCornerShape(20.dp) ) { Text("你好世界!", color = Color.White) } } } }

Swing 互操作性

由于 Compose Multiplatform 桌面版基于 Swing,可直接使用 Swing 创建窗口:

import androidx.compose.ui.awt.ComposeWindow import java.awt.Dimension import javax.swing.JFrame import javax.swing.SwingUtilities fun main() = SwingUtilities.invokeLater { ComposeWindow().apply { size = Dimension(300, 300) defaultCloseOperation = JFrame.DISPOSE_ON_CLOSE setContent { // 窗口内容 } isVisible = true } }

也可在 Window() 作用域内访问底层 ComposeWindow

import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.window.singleWindowApplication import java.awt.datatransfer.DataFlavor import java.awt.dnd.DnDConstants import java.awt.dnd.DropTarget import java.awt.dnd.DropTargetAdapter import java.awt.dnd.DropTargetDropEvent fun main() = singleWindowApplication { LaunchedEffect(Unit) { window.dropTarget = DropTarget().apply { addDropTargetListener(object : DropTargetAdapter() { override fun drop(event: DropTargetDropEvent) { event.acceptDrop(DnDConstants.ACTION_COPY) val fileName = event.transferable.getTransferData(DataFlavor.javaFileListFlavor) println(fileName) } }) } } }

如需使用 Swing 实现的对话框,可封装为可组合函数:

import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.window.AwtWindow import androidx.compose.ui.window.application import java.awt.FileDialog import java.awt.Frame fun main() = application { var isOpen by remember { mutableStateOf(true) } if (isOpen) { FileDialog( onCloseRequest = { isOpen = false println("结果 $it") } ) } } @Composable private fun FileDialog( parent: Frame? = null, onCloseRequest: (result: String?) -> Unit ) = AwtWindow( create = { object : FileDialog(parent, "选择文件", LOAD) { override fun setVisible(value: Boolean) { super.setVisible(value) if (value) { onCloseRequest(file) } } } }, dispose = FileDialog::dispose )

后续步骤

探索更多 桌面组件教程

22 四月 2025