Files
From_Kotlin_to_Kotlin-Multi…/Learning/进入KMP.md
T

5.5 KiB

进入 KMP

Kotlin Multiplatform 是基于 Jetpack Compose 框架的多平台应用编译技术,目前支持 Android,所有 JVM 平台,IOS,JS 网页(已被抛弃),WASM 网页。

选择一个 IDE

这可能感觉上很怪,但是 Android Stuidio 对KMP的支持确实是最好的。 如果你不想做 Android 的部分,用 IDEA 也影响不大,而且 IDEA 有官方中文,而 Android Stuidio 没有。

项目的基本结构

一般来讲,一个 KMP 项目在生成的时候,长这样

  • 项目根目录/
    • androidApp/
    • desktopApp/
    • iosApp/
    • webApp/ (JS 已经死了,现在向我们走来的是 WASM)
    • shared/
      • src/
        • commonMain/
        • commonTest/
        • androidMain/
        • ......
      • build.gradle.kts
    • .gitignore
    • README.md
    • gradlew (脚本文件,一个 bat,一个 sh)
    • gradle.proporties
    • local.proporties (默认被 ignore)
    • build.gradle.kts
    • settings.gradle.kts

如果你的项目还要包含 server 的部分的话,那会稍微复杂一点点,这个后面再说。

各部分用途

所有的共享的部分(如UI,基础逻辑)都写在 shared/commonMain 里面,不同平台如果要做不同的效果,那么,相应的代码写在对应的 Main 里面,App 目录主要控制打包设置和进入软件的窗口设定,虽然可以写针对不同平台的不同效果,但是不推荐。

控制依赖与版本

  • build.gradle.kts 不要动他就好了
  • settings.gradle.kts 可以设置 Maven 镜像之类
  • shared/build.gradle.kts 用于控制各个部分会用到的依赖和基本编译选项
  • androidApp/build.gradle.kts 用于控制 Android 应用编译时的选项
  • desktopApp/build.gradle.kts 用于控制 desktop 应用编译时的选项
  • ......

基本的 UI 结构

KMP 的 UI 以 @Composable 函数为核心,各个平台的 main() 函数入口都会默认调用 shared/commonMain/kotlin 中的 App() 函数。基本的 UI 也都会在 commonMain 中编写。

在 Composable 函数中,通过各种 UI 函数的相互组合,便可以设计出无限可能的 GUI。

@Composable 函数

我们先讲 @Composable,它是一个注解,用于函数或 lambda 变量:

@Composable
fun MyButton(a: Int) {
    TODO() // Composable 函数推荐大写首字母
}

val myCard = @Composable { a: Int ->
    TODO() // lambda 不推荐大写首字母
}          // 变量类型是 @Composable (Int) -> Unit

它有几个重要的特点:

  • Composable 函数只能在 main 函数(描述不严谨)或 Composable 函数中被调用。
  • 当 Composable 函数的参数发生变化时,它会被立刻重组(重新触发),受影响的 UI 立刻重新绘制。
  • 如果 Composable 函数的 lambda 参数的函数体中使用了 Compose state 对象的 value,那么,当它的状态变化时,lambda 所在函数也会被重组。

保护变量值

当 Composable 函数被重新触发时,函数中定义的变量值也会重新被定义,这是很不好的,尤其是当我们需要保留变量值的时候。这里,我们有两种方法:

  1. 使用 remember 定义变量,使其变成状态对象。函数重组时,新的变量会保留重组前的值。
@Composable
fun Counter() {
    // 每次重组时,randomId 不会被重新生成,始终保留第一次的值
    val randomId = remember { UUID.randomUUID().toString() }
    // 如果是可变变量,常使用委托,这里,它是一个枚举类
    var currentScreen by remember { mutableStateOf(Screens.Home) }
}

@Composable
fun UserProfile(userId: String) {
    // 只有当 userId 发生变化时,remember 才会重新计算并更新 userCache
    val userCache = remember(userId) {
        fetchUserCacheFromDb(userId)
    }
}
  1. 在上一层函数中定义变量,然后把变量和用于修改变量的 lambda 变量一并传进下一个函数。
// 1. 父级函数:持有状态并定义修改逻辑
@Composable
fun ParentScreen() {
    var count by remember { mutableStateOf(0) }

    CounterDisplay(
        count = count,            // 将变量和对应的 Lambda 传入子函数
        onIncrement = { count++ } // 用于修改变量的 lambda 变量定义于此
    )
}

// 2. 子级函数:无状态组件(Stateless)
@Composable
fun CounterDisplay(
    count: Int,                   // 传入变量值
    onIncrement: () -> Unit       // 配套的 Lambda
) {
    Button(onClick = onIncrement) {
        Text("当前点击次数: $count")
    }
}

引发重组

一般来讲就是利用改变参数的值和改变 lambda 中的 Compose state 状态来触发。利用参数变化并没有很常见。

@Composable
fun Counter() {
    // 使用 remember + mutableStateOf 保持状态并在值变化时触发重组
    var count by remember { mutableStateOf(0) }

    Button(onClick = { count++ }) {
        Text("点击次数: $count")
    }
}

@Composable
fun App() {
    var seedColor: Color

    // 假设这里有什么东西会使 seedColor 发生变化

    // seedColor 变化时,colorScheme 会被重新定义,
    // 因为 rememberDynamicColorScheme 是 Composable 函数
    val colorScheme = rememberDynamicColorScheme(
        seedColor = seedColor,
        isDark = isSystemInDarkTheme()
    )

    // colorScheme 变化时,MaterialTheme 会重组,
    // 因为 MaterialTheme 是 Composable 函数
    MaterialTheme(
        colorScheme = colorScheme,
    ) {}
}

接下来可以看

目录

下一篇:第一个 KMP 应用