Kotlin 构建桌面应用程序Hello World

使用 Kotlin 构建桌面应用程序并为桌面编写多平台

介绍

Compose Multiplatform是由 Jetbrains 开发的 UI 框架,用于使用带有反应性和功能性 API 的 Kotlin 简化和加速桌面应用程序开发。它以 JVM 为目标,因此可用于构建跨平台 GUI 应用程序。它还支持其他平台,例如网络。

它基于 Google 为 Android 制作的Jetpack Compose声明式 UI 工具包。使用 Kotlin 设计用户界面可以减少错误、更好的工具支持以及更简洁和健壮的代码。这是使用基于可组合函数的声明性 UI 模型来实现的,该模型接受参数来描述 UI 逻辑而不返回任何内容,并且必须没有副作用。Compose 心智模型文档中提供了有关可组合的更多详细信息。

虽然本教程的主要主题是 Compose Multiplatform for Desktop,但其中的概念和大部分代码片段也适用于 Android 的 Jetpack Compose。

入门

需要 JDK 11 或更高版本。’JAVA_HOME’ 环境变量应设置为 JDK 路径位置。IntelliJ IDEA Community Edition 或 Ultimate Edition 20.2 或更高版本是首选 IDE。如果您需要安装 JDK 11 和 IntelliJ IDEA,请按照本教程进行操作。

  1. 从以下链接下载 Compose for Desktop 项目启动器
  2. 提取文件夹并将其移动到您的工作区
  3. 打开 IntelliJ IDEA 并从您的工作区打开项目
  4. 选择 Gradle Kotlin 作为构建系统
  5. 打开src/main/kotlin/main.kt,使用以下内容覆盖内容:
import androidx.compose.material.Text
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application

fun main() = application {
    Window(onCloseRequest = ::exitApplication) {
        Text("Hello, World!")
    }
}

6.从项目的根目录打开终端,然后启动:gradlew run. 您也可以run直接从 IntelliJ IDEA 启动 gradle 任务。

从官方 GitHub 下载 compose for desktop 项目启动器
在 IntelliJ IDEA 中导入项目
选择 Gradle,然后单击完成
启动“运行”Gradle 任务
为桌面编写 Hello World

从实践中学习:实现一个简单的计算器

为了深入了解 Compose for Desktop 的概念并通过实践来学习,我们将在本节中实现一个计算器 UI。生成的 UI 将如下所示:

入口点

const val DEFAULT_WIDTH = 500
const val DEFAULT_HEIGHT = 500

fun main() = Window(
    title = "Compose Calculator - simply-how.com",
    size = IntSize(DEFAULT_WIDTH, DEFAULT_HEIGHT),
    icon = Assets.WindowIcon
) {
    MaterialTheme(colors = lightThemeColors) {
        val mainOutput = remember { mutableStateOf(TextFieldValue("0")) }
        Column(Modifier.fillMaxHeight()) {
            DisplayPanel(
                Modifier.weight(1f),
                mainOutput
            )
            Keyboard(
                Modifier.weight(4f),
                mainOutput
            )
        }
    }
}
  • 应用程序的窗口具有自定义的标题、大小(尺寸)和图标。此处描述了更多窗口自定义。
  • 应用遵循Material Design原则的应用程序范围的主题。在此处了解有关主题的更多信息。
  • remember用于在可组合项中创建内部状态。在此处了解有关状态管理的更多信息。
  • Column用于在屏幕上垂直放置项目。在此处了解可用的布局。
  • 修饰符用于自定义可组合的外观和行为。例如,Modifier.weight用于通过按比例划分可用空间来调整 Column 子项的大小。更多关于修饰符在这里

显示屏

DisplayPanel是计算器的顶部组件。

@Composable
fun DisplayPanel(
    modifier: Modifier,
    mainOutput: MutableState<TextFieldValue>
) {
    Column(
        modifier = modifier
            .fillMaxWidth()
            .background(MaterialTheme.colors.surface)
            .padding(CALCULATOR_PADDING)
            .background(Color.White)
            .border(color = Color.Gray, width = 1.dp)
            .padding(start = 16.dp, end = 16.dp),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.End
    ) {
        Text(
            text = mainOutput.value.text,
            style = TextStyle(
                fontSize = 48.sp,
                fontFamily = jostFontFamily
            ),
            overflow = TextOverflow.Ellipsis,
            softWrap = false,
            maxLines = 1,
        )
    }
}
  • 我们可以在这里找到更复杂的修饰符用法。
  • 计算器的输出使用Text具有自定义样式和行为的组合来显示。
  • TextFieldValue保存输出文本的编辑状态。它在应用程序的入口点初始化,并通过键盘按键进行修改。

键盘

这是放置在下方的计算器的第二个组件DisplayPanel

@Composable
fun Keyboard(
    modifier: Modifier,
    mainOutput: MutableState<TextFieldValue>
) {
    Surface(modifier) {
        KeyboardKeys(mainOutput)
    }
}

@Composable
fun KeyboardKeys(mainOutput: MutableState<TextFieldValue>) {
    Row(modifier = Modifier.fillMaxSize()) {
        KeyboardLayout.forEach { keyColumn ->
            Column(modifier = Modifier.weight(1f)) {
                keyColumn.forEach { key ->
                    KeyboardKey(Modifier.weight(1f), key, mainOutput)
                }
            }
        }
    }
}
  • Surface组合项将隐式使用应用程序的 Material 主题中定义的表面颜色。
  • Row用于在屏幕上水平放置项目。
@Composable
fun KeyboardKey(modifier: Modifier, key: Key?, mainOutput: MutableState<TextFieldValue>) {
    if (key == null) {
        return EmptyKeyView(modifier)
    }
    KeyView(modifier = modifier.padding(1.dp), onClick = key.onClick?.let {
        { it(mainOutput) }
    } ?: {
        val textValue = mainOutput.value.text.let {
            if (it == "0") key.value else it + key.value
        }
        mainOutput.value = TextFieldValue(text = textValue)
    }) {
        if (key.icon == null) {
            val textStyle = if (key.type == KeyType.COMMAND) {
                TextStyle(
                    color = MaterialTheme.colors.primary,
                    fontSize = 22.sp
                )
            } else {
                TextStyle(
                    fontFamily = jostFontFamily,
                    fontSize = 29.sp
                )
            }
            Text(
                text = key.value,
                style = textStyle
            )
        } else {
            Icon(
                asset = key.icon,
                tint = MaterialTheme.colors.primary
            )
        }
    }
}

val KEY_BORDER_WIDTH = 1.dp
val KEY_BORDER_COLOR = Color.Gray
val KEY_ACTIVE_BACKGROUND = Color.White

@Composable
fun KeyView(
    modifier: Modifier = Modifier,
    onClick: () -> Unit,
    children: @Composable ColumnScope.() -> Unit
) {
    val active = remember { mutableStateOf(false) }
    Column(
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center,
        modifier = modifier.fillMaxWidth()
            .padding(CALCULATOR_PADDING)
            .clickable(onClick = onClick)
            .background(color = if (active.value) KEY_ACTIVE_BACKGROUND else MaterialTheme.colors.background)
            .border(width = KEY_BORDER_WIDTH, color = KEY_BORDER_COLOR)
            .pointerMoveFilter(
                onEnter = {
                    active.value = true
                    false
                },
                onExit = {
                    active.value = false
                    false
                }
            ),
        children = children
    )
}

@Composable
fun EmptyKeyView(modifier: Modifier) = Box(
    modifier = modifier.fillMaxWidth()
        .background(MaterialTheme.colors.background)
        .border(width = KEY_BORDER_WIDTH, color = KEY_BORDER_COLOR)
)
  • Modifier.clickable用于使键接收来自用户的点击交互。
  • Modifier.pointerMoveFilter此处用于在将鼠标悬停在键上时更改键的背景颜色。在此处了解有关鼠标事件的更多信息。

该应用程序的完整源代码可在GitHub上找到。

更多资源和文档

以下是一些涵盖本文未介绍的 Compose 方面的外部资源和教程:

Jetpack Compose