使用 KMP 构建 Compose Multiplatform 共享 UI

社区移动开发Android
使用 KMP 构建 Compose Multiplatform 共享 UI

picture.image

最近, Android 开发已经相当现代化, 大量的 XML 布局已经被关闭, 这正是 Jetpack Compose(Android 的现代化, 完全声明式 UI 工具包)的时代. 凭借其强大而直观的基于 Kotlin 的语法, Compose 简化了UI开发, 同时也为未来更简洁, 更反应灵敏, 更动态的移动应用打开了大门.

Jetpack Compose 得到了开发人员的广泛采用和认可. 与此同时, 跨平台解决方案的需求也在不断增长. 因此, 作为 Kotlin 背后的强大力量, JetBrains 与谷歌携手将 Compose 体验扩展到更跨平台, Compose Multiplatform 应运而生.

有了这个UI框架, 你就可以将 Kotlin Multiplatform 的代码共享功能扩展到应用逻辑之外. 你可以一次性实现UI, 然后将其用于所有目标平台(iOS, Android, 桌面).

在本教程中, 我创建了一个带有自定义数据的懒列示例应用, 可在 Android, iOS 和桌面(对我来说是 Mac)上运行. 为创建UI, 你将使用 Compose Multiplatform 框架并学习其基础知识: Composable, 主题, 布局, 事件和Modifier.

支持的平台有:

picture.image

现在的问题是, Kotlin MultiplatformCompose Multiplatform 之间有什么区别?

Kotlin Multiplatform 是用于在平台间共享业务逻辑的工具, 但它有一个很大的缺点 -- 开发人员仍需使用**Jetpack Compose(Android)SwiftUI(iOS)**等本地工具来实现UI.

在此, Compose Multiplatform登场, 填补了KMP的空白, 完善了 Kotlin 体验. 通过将ComposeKMP结合起来, 你可以根据项目的复杂程度, 使代码库中 80%-95% 的内容由 Kotlin 构成. 对于我这个 Android 开发人员来说, 这听起来相当令人兴奋.

简而言之, 两者的区别在于
Kotlin Multiplatform - 仅共享业务逻辑 + 单独的 Android 和 iOS UI

Compose Multiplatform - 共享业务逻辑 + Android 和 iOS 共享UI
那么哪个更好呢? 很明显, Compose 跨平台:

picture.image

深度实现

首先, 你需要一台用于跨平台开发的工作机. 不久之后, 你将需要安装了KMP插件, XcodeCocoaPods依赖管理器的Android Studio.

好消息是, 你甚至不需要花时间进行适当的KMP + Compose项目设置, JetBrains提供了一个便捷模板, 其中包含对该模板中发生的事情的逐步解释, 你可以在该模板中选择平台并下载, 它会为你创建一个示例项目设置.

让我解释一下项目结构是怎样的:

picture.image

  • composeApp- 它包含平台特定文件夹, 其中包含入口点, 共享UI和业务逻辑
  • commonMain - 在这个模块中, 我们将保留所有共享的 Compose UI 和业务逻辑
  • androidMain - Kotlin 模块, Android 应用的入口点, 包含平台特定的类, 如 Application 和 Activity, 以及所需的其他依赖项.
  • desktopMain - Kotlin 模块, 桌面应用的入口点, 包含平台特定的类, 如 Window 类.
  • iosMain - 包含特定平台配置和类的 Xcode 项目. 在构建过程中, commonMain 模块会作为 CocoaPod 依赖项捆绑到 iOS 项目中.

让我们看看如何定义每个平台的依赖关系和平台间的共同依赖关系.

在 Gradle 文件夹中, 我们有版本目录文件:

[versions]
agp = "8.1.4"
android-compileSdk = "34"
android-minSdk = "24"
android-targetSdk = "34"
androidx-activityCompose = "1.8.2"
androidx-appcompat = "1.6.1"
androidx-constraintlayout = "2.1.4"
androidx-core-ktx = "1.12.0"
androidx-espresso-core = "3.5.1"
androidx-material = "1.10.0"
androidx-test-junit = "1.1.5"
compose = "1.5.4"
compose-compiler = "1.5.6"
compose-plugin = "1.5.11"
junit = "4.13.2"
kotlin = "1.9.21"
ktor = "2.3.7"
coroutines = "1.7.3"

[libraries]
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activityCompose" }
compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "compose" }
compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview", version.ref = "compose" }
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" }
ktor-client-darwin = { module = "io.ktor:ktor-client-darwin", version.ref = "ktor" }
ktor-client-cio= {module ="io.ktor:ktor-client-cio", version.ref = "ktor"}
kotlin-serialization = {module = "io.ktor:ktor-serialization-kotlinx-json", version.ref="ktor"}
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" }
kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" }

[plugins]
androidApplication = { id = "com.android.application", version.ref = "agp" }
androidLibrary = { id = "com.android.library", version.ref = "agp" }
jetbrainsCompose = { id = "org.jetbrains.compose", version.ref = "compose-plugin" }
kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
kotlinSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
  • [versions]- 带编号的版本名称
  • [libraries]- 带有模块和版本号的库名[版本
  • [plugins]- 带有 ID 和版本号的插件名称[版本

现在我们可以像这样在 build.gradle 中使用库:

picture.image

项目级 build.gradle 的插件:

picture.image

现在让我们快速回顾一下如何定义依赖关系:

 sourceSets {
        val desktopMain by getting
        
        androidMain.dependencies {
            implementation(libs.compose.ui.tooling.preview)
            implementation(libs.androidx.activity.compose)
            implementation(libs.ktor.client.okhttp)
            implementation(libs.kotlinx.coroutines.android)
        }
        commonMain.dependencies {
            implementation(compose.runtime)
            implementation(compose.foundation)
            implementation(compose.material)
            implementation(compose.ui)
            @OptIn(ExperimentalComposeLibrary::class)
            implementation(compose.components.resources)
            implementation(libs.ktor.client.core)
            implementation(libs.kotlin.serialization)
            implementation(libs.kotlinx.coroutines.core)
        }
        desktopMain.dependencies {
            implementation(compose.desktop.currentOs)
            implementation(libs.ktor.client.cio)
        }
        iosMain.dependencies {
            implementation(libs.ktor.client.darwin)
        }
    }
  • androidMain.dependencies -- 用于 Android 特定的依赖关系
  • iOSMain.dependencies -- 用于 iOS 特定的依赖关系
  • desktopMain.dependencies -- 用于桌面特定的依赖关系
  • commonMain.dependencies -- 用于平台间的通用依赖关系

所有平台都有一个入口 App.kt:

@Composable
fun App() {
    MaterialTheme {
        Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
            ListScreen(Repository.getDataList())
        }
    }
}
@Composable
fun ListScreen(list: List<Products>)
{
    LazyColumn(modifier = Modifier.fillMaxWidth(), contentPadding = PaddingValues(16.dp)) {
        items(list) { data ->
            ColumnItem(data.title, data.description, data.price.toString(), data.image)
        }
    }
}

    @OptIn(ExperimentalResourceApi::class)
    @Composable
    fun ColumnItem(name: String, description: String, price:String, image: String) {
        Card(
            modifier = Modifier.padding(8.dp)
                .fillMaxWidth()
                .wrapContentHeight(),
            shape = MaterialTheme.shapes.medium,
            elevation = 5.dp,
            backgroundColor = MaterialTheme.colors.surface
        ) {
            Row(
                verticalAlignment = Alignment.CenterVertically,
            ) {
                Image(
                    painter = painterResource(res = "compose-multiplatform.xml"),
                    contentDescription = null,
                    modifier = Modifier.size(100.dp)
                        .padding(8.dp),
                    contentScale = ContentScale.Fit,
                )
                Column(Modifier.padding(4.dp)) {
                    Text(
                        modifier = Modifier.padding(2.dp),
                        text = name,
                        maxLines = 3,
                        style = MaterialTheme.typography.h6,
                        color = MaterialTheme.colors.onSurface,
                    )
                    Text(
                        modifier = Modifier.padding(2.dp),
                        text = description,
                        maxLines = 3,
                        style = MaterialTheme.typography.body2,
                    )
                    Text(
                        modifier = Modifier.padding(2.dp),
                        text = "Price USD $price",
                        style = MaterialTheme.typography.button,
                    )
                }
            }
        }
    }

让我们从顶部选择平台并运行应用, 就这样:

picture.image

picture.image

总结一下
  • 我们学习了跨平台环境设置
  • 我们学习了如何使用项目结构
  • 我们学习了如何声明通用依赖和特定平台依赖
  • 我们了解了如何在平台间共享UI
  • 我将在接下来的博客中介绍 Ktor 如何处理跨平台网络请求!

今天的内容就分享到这里啦!

一家之言, 欢迎拍砖!

Happy Coding! Stay GOLDEN!

0
0
0
0
评论
未登录
看完啦,登录分享一下感受吧~
暂无评论