第一个KMP应用
This commit is contained in:
+215
-1
@@ -1 +1,215 @@
|
||||
# 第一个KMP应用
|
||||
# 第一个 KMP 应用
|
||||
|
||||
这里,我推荐刚开始上手 KMP 的朋友们先写仅有 JVM 平台的 KMP 应用。
|
||||
|
||||
## 项目结构
|
||||
|
||||
- 项目根目录/
|
||||
- desktopApp/
|
||||
- src/
|
||||
- build.gradle.kts
|
||||
- shared/
|
||||
- src/
|
||||
- commonMain/
|
||||
- composeResources/
|
||||
- kotlin/
|
||||
- commonTest/
|
||||
- jvmMain/
|
||||
- build.gradle.kts
|
||||
- build.gradle.kts
|
||||
- settings.gradle.kts
|
||||
|
||||
## 跑起来看看效果
|
||||
|
||||
Gradle 默认会注册启动项 `hotRun`,它会被 IDEA / Android Studio 识别为 `desktopApp [hot] 🔥`。这是开发阶段的主要的测试方法。点击,即可启动编译,并运行热重载程序,它可以在你修改源代码并编译后,直接在原有程序窗口同步你的修改。
|
||||
|
||||
## commonMain 编写设计
|
||||
|
||||
可以看到 commonMain 的 App.kt 已经包含了一些内容了:
|
||||
|
||||
```kotlin
|
||||
@Composable
|
||||
@Preview
|
||||
fun App() {
|
||||
MaterialTheme {
|
||||
var showContent by remember { mutableStateOf(false) }
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.background(MaterialTheme.colorScheme.primaryContainer)
|
||||
.safeContentPadding()
|
||||
.fillMaxSize(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Button(onClick = { showContent = !showContent }) {
|
||||
Text("Click me!")
|
||||
}
|
||||
AnimatedVisibility(showContent) {
|
||||
val greeting = remember { Greeting().greet() }
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Image(painterResource(Res.drawable.compose_multiplatform), null)
|
||||
Text("Compose: $greeting")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
接下来将对其中的细节进行讲解。
|
||||
|
||||
### 基本的 UI 设计思路
|
||||
|
||||
#### 结构
|
||||
|
||||
KMP 的 UI 是基于一个又一个“盒子”的,在这里,最外层是一个主题 `MaterialTheme`,在主题内部是一个 `Column`,看它的 `modifier` 参数,这里有一个 `fillMaxSize`,表明这个盒子会占据整个窗口。在一个 Column 中,内部的所有组件都会纵向排列。
|
||||
|
||||
Column 内部,有一个 `Button`,还有一个被 `AnimatedVisibility` 框起来的 Column,这里我们先不管 AnimatedVisibility 所实现的动画效果,我们看排版结构。前文提到,在 Column 内部,也就是它的 lambda 里面,所有的组件会纵向排列,也就是说 Button 和内层 Column 会在窗口中一个在上一个在下。
|
||||
|
||||
#### 图片资源
|
||||
|
||||
内层的 Column 又包含了两个组件,一个是图片,一个是文本。这里有一个东西叫图片资源,它们的源文件放在 `commonMain/composeResources/drawable` 中,放在这里的图片会自动被 gradle 注册在一个 object `Res.drawable` 之中,变量名就是它们的文件名。这是 KMP 中最好的调用图片方式。
|
||||
|
||||
### 基本的逻辑处理思路
|
||||
|
||||
我们来看它的 showContent 变量如何被定义,然后在 Button 被点击时被改变
|
||||
|
||||
```kotlin
|
||||
// 将 showContent 委托给一个 Compose state
|
||||
var showContent by remember { mutableStateOf(false) }
|
||||
|
||||
// 一些排版
|
||||
|
||||
// 这里使用一个 lambda 将 showContent 的值反转
|
||||
Button(onClick = { showContent = !showContent })
|
||||
```
|
||||
|
||||
因为变量 showContent 是函数 AnimatedVisibility 的参数之一,因此,当它的值发生变化时,该函数会被重载,随后触发动画效果,其中的 Column 就会出现 / 消失。
|
||||
|
||||
## 更多的排版工具与资源文档
|
||||
|
||||
[排版 / 组件](https://m3.material.io/components)
|
||||
|
||||
[Google 提供的矢量图标](https://fonts.google.com/icons)
|
||||
|
||||
你可以这样使用它们:
|
||||
|
||||
```kotlin
|
||||
Icon(
|
||||
imageVector = Icons.Default.Favorite, // 在网站上可以直接查到
|
||||
contentDescription = null, // 图片的文字标记,一般用于无障碍
|
||||
tint = Color(0xFFE53935) // 注意,alpha 值写在第一位
|
||||
)
|
||||
```
|
||||
|
||||
## main 函数
|
||||
|
||||
刚刚生成的新项目,它的 main 函数一般是这样的:
|
||||
|
||||
```kotlin
|
||||
fun main() = application {
|
||||
Window(
|
||||
onCloseRequest = ::exitApplication,
|
||||
title = "YourProjectName",
|
||||
) {
|
||||
App()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
在这个 `Window` 函数中,可以用 `state = rememberWindowState(width = 900.dp, height = 800.dp)` 来设定窗口的初始大小。用 `icon = painterResource()` 设定窗口图标。例如:
|
||||
|
||||
```kotlin
|
||||
fun main() = application {
|
||||
Window(
|
||||
onCloseRequest = ::exitApplication,
|
||||
title = Const.FULL_APP_NAME, // 自己定义一个 Const 对象,很方便的东西
|
||||
state = rememberWindowState(width = 900.dp, height = 800.dp),
|
||||
icon = painterResource(Res.drawable.kmp)
|
||||
) {
|
||||
App()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 设置 desktop 编译选项
|
||||
|
||||
刚刚生成的新项目,它的 `desktopApp/build.gradle.kts` 一般是这样的:
|
||||
|
||||
```kotlin
|
||||
compose.desktop {
|
||||
application {
|
||||
mainClass = "your.package.name.MainKt"
|
||||
|
||||
nativeDistributions {
|
||||
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
|
||||
packageName = "your.package.name"
|
||||
packageVersion = "1.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
其实这样就可以编译对应三大桌面平台的安装包了。不过如果你想做的更细致点也是可以的:
|
||||
|
||||
```kotlin
|
||||
compose.desktop {
|
||||
application {
|
||||
mainClass = "your.package.name.MainKt"
|
||||
|
||||
nativeDistributions {
|
||||
targetFormats(
|
||||
// Windows
|
||||
TargetFormat.Msi,
|
||||
TargetFormat.Exe,
|
||||
// macOS
|
||||
TargetFormat.Dmg,
|
||||
// Linux
|
||||
TargetFormat.Deb,
|
||||
TargetFormat.Rpm,
|
||||
TargetFormat.AppImage
|
||||
)
|
||||
// 你可以在 gradle 文件中用 Kotlin 一样的方法定义变量,这很方便
|
||||
packageName = appName
|
||||
packageVersion = appVersion
|
||||
|
||||
// 如果可以直接运行,那么就不要写 proguard 这一段
|
||||
buildTypes.release.proguard {
|
||||
configurationFiles.from(files("proguard-rules.pro"))
|
||||
}
|
||||
|
||||
windows {
|
||||
iconFile.set(project.file("icons/icon.ico"))
|
||||
menuGroup = appName
|
||||
shortcut = true
|
||||
dirChooser = true
|
||||
}
|
||||
|
||||
macOS {
|
||||
iconFile.set(project.file("icons/icon.icns"))
|
||||
bundleID = packageName
|
||||
appStore = false
|
||||
signing {
|
||||
sign.set(false)
|
||||
}
|
||||
}
|
||||
|
||||
linux {
|
||||
iconFile.set(project.file("icons/icon.png"))
|
||||
menuGroup = appName
|
||||
shortcut = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 接下来可以看
|
||||
|
||||
[目录](../README.md)
|
||||
|
||||
[我曾经写的一个简单的小应用](https://github.com/SOR2171/Dices)
|
||||
|
||||
[下一篇:复杂逻辑与多平台不同逻辑](./复杂逻辑与多平台不同逻辑.md)
|
||||
Reference in New Issue
Block a user