# 进入 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 变量: ```kotlin @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 定义变量,使其变成状态对象。函数重组时,新的变量会保留重组前的值。 ```kotlin @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) } } ``` 2. 在上一层函数中定义变量,然后把`变量`和`用于修改变量的 lambda 变量`一并传进下一个函数。 ```kotlin // 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 状态来触发。利用参数变化并没有很常见。 ```kotlin @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, ) {} } ``` ## 接下来可以看 [目录](../README.md) [下一篇:第一个 KMP 应用](./第一个KMP应用.md)