Multiplatform:用一套声明式 UI 连接多端

简介: Compose Multiplatform 是基于 Kotlin 的跨平台 UI 框架,延续 Jetpack Compose 声明式范式,支持 Android、iOS、桌面与 Web。它共享 UI 逻辑与状态,分层处理平台能力,适合工具类、中后台等 UI 高度一致的场景,助力 Android 团队高效拓展多端。(239 字)

Compose Multiplatform:用一套声明式 UI 连接多端

为什么会有 Compose Multiplatform

Android 开发者熟悉 Jetpack Compose 之后,很自然会遇到一个问题:既然 UI 已经可以用 Kotlin 和声明式方式表达,能不能把同一套 UI 思路带到桌面、iOS、Web 或更多平台?Compose Multiplatform 就是沿着这个方向发展出来的技术栈。

它不是简单地把 Android View 搬到其他平台,也不是让所有平台完全共用同一份应用代码。更准确地说,它提供了一套跨平台的 Compose Runtime、UI 组件模型和渲染能力,让开发者可以用 Kotlin 写共享界面和状态逻辑,再在不同平台接入各自的入口、权限、生命周期和系统能力。

对 Android 团队来说,它最大的吸引力有三个:语言和思维模型延续 Kotlin + Compose,业务页面可以在多个端复用,团队不必为每个平台完全重写一套 UI。它适合产品形态一致、交互差异不大、团队希望降低多端重复开发成本的场景。

它和 Jetpack Compose 的关系

Jetpack Compose 是 Android 官方的现代 UI 工具包,目标主要是 Android。Compose Multiplatform 则把 Compose 的核心思想扩展到更多平台。两者共享很多概念:Composable 函数、状态驱动 UI、remember、Modifier、Material 风格组件、预览和重组机制。

但它们也有明显区别。Android Compose 可以直接使用 Android SDK、Activity、ViewModel、Navigation、资源系统和平台组件;Compose Multiplatform 需要考虑多个平台,所以共享代码里不能随意依赖 Android 专属 API。涉及相机、定位、通知、文件选择、系统分享、支付、推送等能力时,通常要通过平台适配层实现。

可以把它理解为:Compose 的 UI 思维是共享的,平台能力仍然是分层的。写得好的多端项目,不是把平台差异藏起来假装不存在,而是把共性和差异放在合适的位置。

适合什么场景

Compose Multiplatform 比较适合这些类型的项目:

  • 工具类应用:例如笔记、待办、Markdown 编辑器、数据看板。
  • 中后台或桌面管理端:页面结构稳定,交互主要是表单、列表、筛选、编辑。
  • Android 团队主导的多端尝试:团队已有 Kotlin 和 Compose 经验。
  • 产品 UI 在多个平台高度一致:不需要大量平台原生交互。
  • 内部工具或新业务验证:希望快速覆盖桌面端和移动端。

它不太适合一开始就强行覆盖所有场景。如果应用高度依赖平台原生体验,例如复杂视频编辑、地图深度交互、系统级能力、强平台风格页面,跨平台共享 UI 的收益可能会被适配成本抵消。

推荐的工程结构

一个常见结构会把共享代码和平台入口分开:

composeApp/
  src/
    commonMain/
      kotlin/
        App.kt
        feature/
        ui/
        data/
    androidMain/
      kotlin/
        MainActivity.kt
    iosMain/
      kotlin/
        MainViewController.kt
    desktopMain/
      kotlin/
        main.kt

commonMain 放可共享的 Composable、状态管理、业务模型、Repository 接口和通用工具。androidMain、iosMain、desktopMain 放平台入口和平台专属实现。

这种结构的关键是依赖方向:共享层定义能力,平台层提供实现。不要让 commonMain 直接依赖 Android 的 Context、Activity、Uri 或资源访问方式,否则代码很快会失去跨平台意义。

从一个共享 App 开始

共享 UI 通常从一个根 Composable 开始:

@Composable
fun App() {
    MaterialTheme {
        var count by rememberSaveable { mutableStateOf(0) }

        Column(
            modifier = Modifier.fillMaxSize().padding(24.dp),
            verticalArrangement = Arrangement.Center,
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            Text(text = "Count: $count")
            Spacer(Modifier.height(12.dp))
            Button(onClick = { count++ }) {
                Text("Increase")
            }
        }
    }
}

Android 入口可以这样接入:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent { App() }
    }
}

桌面端入口则可能是:

fun main() = application {
    Window(onCloseRequest = ::exitApplication, title = "Compose App") {
        App()
    }
}

你会发现,共享 UI 的写法和 Android Compose 非常接近。真正需要设计的是:哪些状态和页面可以共享,哪些能力必须交给平台层。

状态管理:共享逻辑,隔离平台

Compose Multiplatform 里仍然推荐使用单向数据流。页面接收状态,事件向上抛,业务层处理后更新状态。

data class LoginUiState(
    val account: String = "",
    val password: String = "",
    val loading: Boolean = false,
    val error: String? = null
)

class LoginPresenter(
    private val repository: LoginRepository
) {
    var state by mutableStateOf(LoginUiState())
        private set

    fun onAccountChange(value: String) {
        state = state.copy(account = value)
    }

    fun onPasswordChange(value: String) {
        state = state.copy(password = value)
    }
}

在 Android 项目中,我们常把 ViewModel 作为状态容器。多端项目也可以使用适合 Kotlin Multiplatform 的状态容器方案,或者自己封装轻量 Presenter。重点不是名字,而是让状态转换可测试、可复用,并且不要绑死在某个平台生命周期上。

平台差异怎么处理

跨平台项目一定会遇到平台差异。正确做法不是在共享 UI 里到处写平台判断,而是用接口隔离。

interface ShareService {
    fun shareText(text: String)
}

共享页面只依赖接口:

@Composable
fun ArticleScreen(shareService: ShareService) {
    Button(onClick = { shareService.shareText("Compose Multiplatform") }) {
        Text("Share")
    }
}

Android、iOS、Desktop 分别提供自己的实现。这样共享页面仍然干净,平台能力也能保留各自体验。

更复杂的能力可以使用 expect/actual:

expect fun currentPlatformName(): String

然后在不同平台实现:

actual fun currentPlatformName(): String = "Android"

适配层要控制规模。接口越多,维护成本越高。只有确实需要跨平台调用的能力才抽象,不要为了抽象而抽象。

资源、主题与设计一致性

Compose Multiplatform 可以共享颜色、字体、间距、组件样式和部分资源。建议把设计系统放在共享层:

object AppSpacing {
    val small = 8.dp
    val medium = 16.dp
    val large = 24.dp
}

@Composable
fun PrimaryButton(text: String, onClick: () -> Unit) {
    Button(onClick = onClick, modifier = Modifier.height(48.dp)) {
        Text(text)
    }
}

共享设计系统的好处很直接:同一个按钮、同一个卡片、同一个空状态,不需要在不同端重复实现。但也要允许平台有自己的细节。例如桌面端可能需要 hover、右键菜单、快捷键;移动端更关注触摸区域、软键盘和手势。

好的多端 UI 不是所有像素完全一致,而是品牌和交互逻辑一致,同时尊重平台使用习惯。

导航与页面拆分

导航是多端项目容易变复杂的地方。简单项目可以自己维护一个页面栈:

sealed interface Screen {
    data object Home : Screen
    data class Detail(val id: String) : Screen
}

然后根据当前 Screen 渲染不同页面:

@Composable
fun AppRoot() {
    var screen by remember { mutableStateOf<Screen>(Screen.Home) }

    when (val current = screen) {
        Screen.Home -> HomeScreen(onOpenDetail = { screen = Screen.Detail(it) })
        is Screen.Detail -> DetailScreen(id = current.id, onBack = { screen = Screen.Home })
    }
}

业务复杂后,可以引入更完整的导航方案,但仍要注意:Android 的返回键、iOS 的手势返回、桌面端窗口行为并不完全一样。共享导航模型可以复用页面关系,平台入口仍要处理各自的系统交互。

网络、数据库与共享业务层

Compose Multiplatform 不只共享 UI,也可以共享业务模型、网络请求、序列化和部分数据层。常见组合包括 Ktor、kotlinx.serialization、SQLDelight、Settings、Koin 或其他多端友好的库。

Repository 可以放在共享层:

interface ArticleRepository {
    suspend fun loadArticles(): List<Article>
}

class RemoteArticleRepository(
    private val client: HttpClient
) : ArticleRepository {
    override suspend fun loadArticles(): List<Article> {
        return client.get("https://example.com/articles").body()
    }
}

这样 UI、状态和业务请求都可以复用。平台层只负责提供网络引擎、文件路径、数据库驱动或系统能力实现。

需要注意的是,共享层越重,构建、调试和平台差异处理也会越复杂。建议从 UI + 轻业务逻辑开始,再逐步把稳定的能力沉淀到共享层。

测试策略

多端项目更需要测试,因为共享代码的改动会影响多个目标平台。建议从这些地方开始:

  • 对状态容器写普通 Kotlin 单元测试。
  • 对 Repository 使用 Mock 网络响应测试解析和错误处理。
  • 对关键 Composable 做截图或语义节点测试。
  • 对平台适配层做少量集成测试。
  • 对核心用户路径做端到端冒烟测试。

共享逻辑的测试收益很高,因为一份测试能覆盖多个平台的公共行为。平台相关测试则要少而精,重点验证适配是否正确。

落地时最容易踩的坑

常见问题主要集中在这些方面:

  • 过早追求全量共享,导致平台体验变差。
  • 共享层依赖平台 API,后期拆分困难。
  • 抽象接口太多,简单功能也变得绕。
  • 忽视桌面端键盘、鼠标、窗口尺寸等交互差异。
  • 忽视 iOS 审核、权限、原生控件和团队协作成本。
  • 对构建链路、依赖版本和 Gradle 配置缺少治理。

更稳的做法是先选择一个边界清晰的模块试点,例如设置页、详情页、内部工具或新业务小应用。等团队熟悉工程结构、调试方式和发布流程后,再扩大共享范围。

和 Flutter、React Native 怎么取舍

如果团队主要是 Android/Kotlin 背景,已经在使用 Compose,那么 Compose Multiplatform 的学习成本相对低,代码和思维模型延续性很好。如果团队已有成熟 Flutter 或 React Native 体系,迁移成本就需要谨慎评估。

取舍时可以看几个问题:

  • 团队主力语言是不是 Kotlin?
  • 产品是否重视平台原生体验?
  • UI 是否高度一致?
  • 是否需要复用已有 Android Compose 页面?
  • iOS、桌面、Web 的投入预期有多高?
  • 构建、调试、测试和发布链路是否有人维护?

技术选择没有绝对答案。跨平台框架真正的成本往往不在写出第一个页面,而在长期维护、团队协作和平台差异处理。

实践建议

如果你准备在 Android 团队里尝试 Compose Multiplatform,可以按这个节奏推进:

  • 先用一个小模块验证工程链路。
  • 只共享页面、状态和稳定业务逻辑。
  • 平台能力用接口隔离,不要污染共享层。
  • 设计系统先统一基础组件,再逐步扩展复杂组件。
  • 对共享状态和 Repository 补单元测试。
  • 记录平台差异和适配约定,避免每个页面各写一套。

Compose Multiplatform 的价值,不是承诺“写一次到处完美运行”,而是让 Kotlin 和 Compose 的开发经验可以在更多平台复用。对 Android 开发者来说,它提供了一条自然的多端扩展路径:先把 UI 和状态写清楚,再把平台差异放到边界上处理。

当项目边界合适、团队能力匹配、平台差异可控时,它能明显减少重复开发,让多端应用的架构更统一。反过来,如果业务强依赖平台特性,就应该克制共享范围,把它用在真正能产生收益的部分。

相关文章
|
Android开发
解决圆形进度条ProgressBar的几个问题
Android自带的Progressbar默认就是圆形的,可以通过设置style属性 style="?android:attr/progressBarStyleHorizontal" 复制代码 这样就能变成条状进度条,如下: <ProgressBar android:layout_width="match_parent" android:layout_height="wrap_content" style="?android:attr/progressBarStyleHorizontal"/>
1836 0
|
4月前
|
数据采集 Web App开发 JavaScript
房源信息采集:链家/贝壳等房产网站的反爬策略应对方案
本文详解链家/贝壳房产数据采集的反爬困境与实战方案:针对IP封禁、滑块验证、JS动态渲染及“幽灵房”假数据等难题,提出OpenClaw驱动真实浏览器+站大爷高匿隧道代理+请求频率与指纹伪装三重防护策略,兼顾稳定性与合规性。(239字)
614 0
|
存储 安全 算法
使用jotp实现双因子验证
扫盲使用totp增强身份安全性指南,原理看懂也不用自己造轮子呀,最讨厌哪些啥也不懂的搬运工,我这里给大家解惑吧
1792 0
|
3月前
|
人工智能 运维 安全
零基础保姆级教程:阿里云百炼配置DeepSeek-V4-Pro完整指南
DeepSeek-V4-Pro是2026年推出的旗舰级MoE架构大模型,总参数1.6T、激活参数49B,原生支持100万Token上下文,在代码生成、数学推理、长文档分析等场景表现突出。阿里云百炼作为一站式大模型服务平台,提供开箱即用的DeepSeek-V4-Pro调用能力,无需自建算力、无需复杂运维,即可快速接入使用。本文从账号准备、模型开通、密钥创建、参数配置到API调用,提供零基础全流程配置指南,覆盖网页体验、代码调用等多种使用方式,附常见问题与避坑要点,确保新手也能顺利完成配置。
766 2
|
3月前
|
供应链 API
企业纳税人信息查询-纳税人类型查询-一般纳税人资格查询-企业纳税人类型查询API接口介绍
本API支持通过企业名称、注册号或统一信用代码查询一般纳税人信息,涵盖纳税人名称、识别号、资格类型、主管税务机关及有效期等关键字段,适用于商业合作、投融资风控、企业自检及政府监管等场景。
283 0
|
3月前
|
人工智能 Java 测试技术
2026年vibe coding模式实测 中小企业Java开发效率解析
本文实测2026年vibe coding模式在中小企业Java开发中的落地效果,基于12个Spring Boot+MyBatis项目,对比TRAE等6款AI工具。结果表明:该模式支持口语化模糊需求输入,自动适配项目规范,单元测试生成准确率提升至72%,开发耗时降低47%,测试覆盖率从35%升至70%+,显著提升中小团队迭代效率。(239字)
279 0
|
8月前
|
安全 Java API
SpringBoot 4 黑科技:接口组 ——10 行代码管理 100+ API 客户端
Spring 7 新增「HTTP接口组」特性,告别重复`@Bean`声明与手动配置。通过`@ImportHttpServices`按业务分组(如github、stackoverflow),支持统一超时、Token、baseUrl等配置,Java代码+YAML双驱动,大幅降低配置冗余,提升可维护性与开发效率。(239字)
587 3
|
Java
Java实例详解
Java实例是通过类创建的对象,其核心在于将抽象的类定义转化为具体的实体。类作为对象的模板定义了属性和行为,而实例则是这些定义的具体实现。通过`new`关键字可以创建实例,并利用点运算符访问其属性和方法。实例拥有自己的生命周期,从创建到使用直至被垃圾回收机制自动清理。此外,实例变量和静态变量的区别在于前者属于单个实例,后者则为所有实例共享。理解Java实例的概念及其管理对编程至关重要。
890 15
|
安全 编译器 C++
Microsoft Visual C++ Redistributable的作用主要体现以及可以删除吗?
这些是Microsoft Visual C++不同版本的Redistributable安装包,用于32位系统,确保相关应用正常运行。它们提供C++运行时环境,简化部署流程,支持第三方库及框架,并确保应用兼容性。定期更新可修复问题并引入新功能。在空间有限或需解决程序冲突时可考虑删除,但需谨慎操作以防影响应用稳定性和兼容性。删除前请确认无应用依赖,并通过控制面板安全卸载。
4267 1
Microsoft Visual C++ Redistributable的作用主要体现以及可以删除吗?

热门文章

最新文章