顶级窗口管理
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=true
和 undecorated=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