历经了漫长的等待,Compose Multiplatform 1.8.0也终于是问世了,此次更新代表着 Compose Multiplatform iOS 稳定版发布:可用于生产环境,并支持 hotload,除了包含 Jetpack Compose 1.8.0 的更新,如自动填充密码、自适应大小文本、可见区域检测…… 之外,在 navigation、字体、DeepLink 方面也有新变化,本文就来看看
以下是本次功能版本更新的亮点:
可 在 GitHub 上 查看此版本的完整变更列表
依赖项
- Gradle 插件
org.jetbrains.compose,版本1.8.0。基于 Jetpack Compose 库: - Lifecycle 库
org.jetbrains.androidx.lifecycle:lifecycle-*:2.9.0-beta01。基于 Jetpack Lifecycle2.9.0-beta01 - Navigation 库
org.jetbrains.androidx.navigation:navigation-*:2.9.0-beta01。基于 Jetpack Navigation2.9.0-beta01 - Material3 Adaptive 库
org.jetbrains.compose.material3.adaptive:adaptive*:1.1.0。基于 Jetpack Material3 Adaptive1.1.0
破坏性变更
Compose Multiplatform 完全迁移至 K2 编译器
在此版本中,Compose Multiplatform 代码库已完全迁移至 K2 编译器。从 1.8.0 开始,依赖 Compose Multiplatform 的项目生成的 Natice 和 Web klibs 只能在使用 Kotlin 2.1.0 或更新版本使用
除了 Compose 编译器 Gradle 插件的底层变更之外,这对您的项目影响如下:
- 对于使用依赖 Compose Multiplatform 的库的应用:建议将其更新到 Kotlin
2.1.20,并将依赖项更新到针对 Compose Multiplatform1.8.0和 Kotlin2.1.x编译的版本。 - 对于依赖 Compose Multiplatform 的库:您需要将项目更新到 Kotlin
2.1.x和 Compose1.8.0,然后重新编译库并发布新版本。
如果您在升级到 Compose Multiplatform 1.8.0 时遇到任何兼容性问题,请通过在 YouTrack 中提交 issue 告知官方。
移除了对 material-icons-core 的隐式依赖
Compose Multiplatform 1.8.0 集成了 Material 库中的一项变更:不再对 material-icons-core 存在传递性依赖。这与逐步[淘汰使用 K1 构建的依赖项](#Compose Multiplatform 完全迁移至 K2 编译器)相符。
如果您的项目需要继续使用 material-icons-core 库,请显式地将其依赖添加到您的 build.gradle.kts 中,例如:
implementation("org.jetbrains.compose.material:material-icons-core:1.7.3")
Navigation 从 Bundle 迁移到 SavedState
Compose Multiplatform 1.8.0 中的 Navigation,与 Android Navigation 组件一样,正在过渡到使用 SavedState 类来存储 UI 状态。这改变了在 navigation 中声明目标时,访问参数的模式。当升级到 2.9.* 版本的 Navigation 库时,请确保更新此类代码以使用 SavedState 的访问器。
为了更健壮的架构,请使用类型安全的方法进行导航,避免使用字符串路由。
Before:
composable(Destinations.Followers.route) { navBackStackEntry ->
val uId = navBackStackEntry.arguments?.getString("userid")
val page = navBackStackEntry.arguments?.getString("page")
if (uId != null && page != null) {
FollowersMainComposable(navController, accountId = uId, page = page)
}
}
从 Compose Multiplatform 1.8.0 开始:
composable(Destinations.Followers.route) { navBackStackEntry ->
val uId = navBackStackEntry.arguments?.read { getStringOrNull("userid") }
val page = navBackStackEntry.arguments?.read { getStringOrNull("page") }
if (uId != null && page != null) {
FollowersMainComposable(navController, accountId = uId, page = page)
}
}
iOS 上弃用 ComposeUIViewControllerDelegate
ComposeUIViewControllerDelegate API 已被弃用,取而代之的是使用其 parent view controller。如果您在 Compose Multiplatform 1.8.0 中使用此已弃用的 API,将会遇到弃用错误,提示您应通过父 UIViewController 来覆盖 UIViewController 类方法。
更多关于子-父视图控制器关系的信息,请查阅 Apple 的开发者文档。
移除了 iOS 上过时的 platformLayers 选项
platformLayers 实验性选项在 1.6.0 引入,用于启用替代的分层模式,并允许在父容器边界之外绘制弹出窗口和对话框。
此模式现在是 iOS 上的默认行为,并且启用此选项的功能已作为过时选项被移除。
测试中的破坏性变更
测试中协程延迟的新处理方式
以前,Compose Multiplatform 测试不会将带有 delay() 调用的副作用视为 idle(闲置状态)。因此,例如以下测试将无限期挂起:
@Test
fun loopInLaunchedEffectTest() = runComposeUiTest {
setContent {
LaunchedEffect(Unit) {
while (true) {
delay(1000)
println("Tick")
}
}
}
}
当协程在组合作用域中启动后调用 delay() 函数时,waitForIdle()、awaitIdle() 和 runOnIdle() 函数现在会将 Compose 视为 idle。此变更修复了上述挂起的测试,但会破坏依赖 waitForIdle()、awaitIdle() 和 runOnIdle() 来执行带有 delay() 的协程的测试。
在这些情况下要产生相同的结果,请人为地推进时间:
var updateText by mutableStateOf(false)
var text by mutableStateOf("0")
setContent {
LaunchedEffect(updateText) {
if (updateText) {
delay(1000)
text = "1"
}
}
}
updateText = true
waitForIdle()
// 由于 waitForIdle() 不再等待带有延迟的 LaunchedEffect() 完成,
// 测试需要推进时间以使后续断言正确:
mainClock.advanceTimeBy(1001)
assertEquals("1", text)
对于已使用 mainClock.advanceTimeBy() 来推进测试时钟的测试,其重组、布局、绘制和效果的行为可能会有所不同。
runOnIdle() 的实现与 Android 对齐
为了使 Compose Multiplatform 的 runOnIdle() 测试函数实现与 Android 行为一致,我们引入了以下变更:
runOnIdle()现在 在 UI 线程上执行其action。runOnIdle()在执行action后不再调用waitForIdle()。
如果您的测试依赖于 runOnIdle() 操作后的额外 waitForIdle() 调用,请在为 Compose Multiplatform 1.8.0 更新测试时根据需要添加该调用。
测试中推进时间与渲染解耦
在 Compose Multiplatform 1.8.0 中,如果时间未推进到渲染下一帧的点(虚拟测试帧每 16 毫秒渲染一次),则 mainClock.advanceTimeBy() 函数不再导致重组、布局或绘制。
这可能会破坏依赖于每次 mainClock.advanceTimeBy() 调用触发渲染的测试。详情请参阅 PR 说明。
跨平台
可变字体
Compose Multiplatform 1.8.0 在所有平台上支持可变字体。借助可变字体,您可以使用一个字体文件包含所有样式偏好设置,例如字重 (weight)、字宽 (width)、倾斜度 (slant)、斜体 (italic)、自定义轴 (custom axes)、带有排版颜色的视觉字重 (visual weight with typographic color) 以及适应特定文本尺寸的调整。
详情请参阅 Jetpack Compose 可变字体文档。
Skia 更新至 Milestone 132
Compose Multiplatform 通过 Skiko 使用的 Skia 版本已更新至 Milestone 132。
之前使用的 Skia 版本是 Milestone 126。您可以在发行说明中查看这些版本之间的变更。
新的 Clipboard 接口
Compose Multiplatform 已采用 Jetpack Compose 的新 Clipboard 接口。
以前使用的 ClipboardManager 接口,由于 Web 上 Clipboard API 的异步特性而在 Web 目标上不可访问,现已弃用,转而使用 Clipboard 接口。新接口支持 suspend 函数,并兼容包括 Web 在内的所有目标平台。
目前,来自 common 代码的 Clipboard 交互受限于 API 设计。更多详情请参阅 CMP-7624。
译注:新的 Clipboard 目前还没有完全 Common 实现,比如:
// PlatformClipboard.desktop.kt actual class ClipEntry(val nativeClipEntry: Any) { // TODO https://youtrack.jetbrains.com/issue/CMP-1260/ClipboardManager.-Implement-getClip-getClipMetadata-setClip actual val clipMetadata: ClipMetadata get() = TODO("ClipMetadata is not implemented. Consider using nativeClipboard") }目前可以自己实现,类似于
clipboard.setClipEntry(clipEntryOf(code)) expect fun clipEntryOf(string: String): ClipEntry //Desktop actual fun clipEntryOf(string: String): ClipEntry = ClipEntry(string) // Web actual fun clipEntryOf(string: String): ClipEntry = ClipEntry.withPlainText(string)
行高对齐方式(Alignment)
以前仅在 Android 上支持的行高 Alignment API,现在已支持所有平台。使用 LineHeightStyle.Alignment,您可以配置文本行在由 lineHeight 提供的空间内如何对齐。文本行可以对齐到保留空间的底部、中心或顶部,也可以根据其上升和下降值进行比例调整。
请注意,在 Material3 中,行高对齐的默认值是 Center,这意味着除非另有指定,否则在所有平台上的 Material3 组件中,带有 lineHeight 的文本将应用居中对齐。
iOS
DeepLink
使用 Compose Multiplatform 1.8.0 以及 org.jetbrains.androidx.navigation.navigation-compose 2.9.0-beta01,您可以使用 Compose 的方式在 iOS 上实现 Deep Link:将其分配给目标,并使用 NavController 导航到它们。
有关将 Deep Link 引入 common 代码的指南,请参阅 此文档。
无障碍功能支持改进
对从右到左语言的支持
Compose Multiplatform 1.8.0 引入了对从右到左语言的无障碍功能支持,包括对手势的正确文本方向处理。
要了解更多关于 RTL 支持的信息,请参阅 从右到左语言。
可滚动列表的无障碍功能
此版本改进了滚动边界和元素位置计算的性能和准确性。通过考虑安全区域(例如刘海屏和屏幕边缘),我们确保了在靠近间隙和边距处滚动的精确无障碍属性。
我们还引入了对滚动状态播报的支持。启用 VoiceOver 后,执行三指滚动手势时,您将听到列表状态更新。播报包括:
- 在列表顶部时播报“第一页”。
- 向前滚动时播报“下一页”。
- 向后滚动时播报“上一页”。
- 到达末尾时播报“最后一页”。
还提供了这些播报的本地化版本,VoiceOver 可以使用您选择的语言进行播报。
容器视图的无障碍功能
默认情况下,屏幕阅读器按照固定的顺序遍历 UI 元素,遵循从左到右、从上到下的布局顺序。对于复杂的布局,如果没有正确定义的遍历语义,屏幕阅读器无法确定正确的阅读顺序。这对于包含容器视图(例如表格和嵌套视图)的布局尤其重要——它们支持内部视图的滚动和缩放。
从 Compose Multiplatform 1.8.0 开始,您可以为容器定义遍历语义属性,以确保在滚动和滑动复杂视图时具有正确的阅读顺序。
例如,如果您希望屏幕阅读器首先聚焦浮动操作按钮,可以将其 traversal index 设置为 -1f。索引的默认值为 0f,这意味着 值较小的元素 将在屏幕上的其他元素之前被读取。
@Composable
fun FloatingBox() {
Box(
modifier =
Modifier.semantics {
isTraversalGroup = true
// 设置负索引以优先于具有默认索引的元素
traversalIndex = -1f
}
) {
FloatingActionButton(onClick = {}) {
Icon(
imageVector = Icons.Default.Add,
contentDescription = "Icon of floating action button" // 浮动操作按钮的图标
)
}
}
}
除了为屏幕阅读器正确排序元素之外,对遍历属性的支持还支持使用向上滑动或向下滑动的无障碍手势在不同的遍历组之间进行导航。要切换到容器的无障碍导航模式,请在 VoiceOver 激活时在屏幕上旋转两根手指。
无障碍文本输入
在 Compose Multiplatform 1.8.0 中,我们引入了对文本字段无障碍特性(accessibility traits)的支持。当文本输入字段获得焦点时,它现在被标记为可编辑 (editable),确保正确的无障碍状态表示。
您现在也可以在 UI 测试中使用无障碍文本输入。
支持通过触控板和键盘进行控制
Compose Multiplatform for iOS 现在支持两种额外的输入方法来控制您的设备。除了依赖于触摸屏之外,您可以启用 AssistiveTouch 来使用鼠标或触控板,或者启用 Full Keyboard Access 来使用键盘:
- AssistiveTouch(设置 | 辅助功能 | 触控 | AssistiveTouch)允许您使用连接的鼠标或触控板上的指针来控制您的 iPhone 或 iPad。您可以使用指针点击屏幕上的图标,浏览 AssistiveTouch 菜单,或使用屏幕键盘输入。
- Full Keyboard Access(设置 | 辅助功能 | 键盘 | 全键盘访问)允许使用连接的键盘控制设备。您可以使用 Tab 等键进行导航,并使用 空格键 激活项目。
按需加载无障碍树
现在,您无需设置 Compose 语义树与 iOS 无障碍树同步的特定模式,而是可以依赖 Compose Multiplatform 惰性处理此过程。在 iOS 无障碍引擎的首次请求后,无障碍树才会完全加载,并在屏幕阅读器停止与其交互时被销毁。
这使得完全支持 iOS Voice Control、VoiceOver 和其他依赖无障碍树的无障碍工具成为可能。
与之对应的,用于配置无障碍树同步的 AccessibilitySyncOptions 类已因不再需要而被移除。
提高无障碍属性计算的准确性
我们已更新 Compose Multiplatform 组件的无障碍属性,以匹配 UIKit 组件的预期行为。UI 元素现在提供丰富的无障碍数据,且任何 alpha 值为 0 的透明组件将不再提供无障碍语义。
语义上的对其还使我们能够修复与无障碍属性计算不正确相关的几个问题,例如 DropDown 元素的命中区域缺失、可见文本与无障碍标签不匹配以及单选按钮状态不正确等问题。
用于 iOS 日志记录的稳定 API
启用 iOS 操作系统日志记录的 API 现在已经稳定。enableTraceOSLog() 函数不再需要实验性 opt-in,并且现在与 Android 风格的日志记录对齐。此日志记录提供了跟踪信息,可以使用 Xcode Instruments 进行调试和性能分析。
Drag-and-drop(实验性)
拖放 (实验性)
Compose Multiplatform for iOS 引入了对拖放功能的支持,允许您将内容拖入或拖出 Compose 应用程序(请参阅 pull request 1690 查看演示视频)。要定义可拖动内容和放置目标,请使用 dragAndDropSource 和 dragAndDropTarget 修饰符。
在 iOS 上,拖放会话数据由 UIDragItem 表示。此对象包含跨进程数据传输的信息以及用于应用内使用的可选本地对象。例如,您可以使用 DragAndDropTransferData(listOf(UIDragItem.fromString(text))) 来拖动文本,其中 UIDragItem.fromString(text) 将文本编码为适合拖放操作的格式。目前,仅支持 String 和 NSObject 类型。
对于常见用例,请参阅 Jetpack Compose 文档中的专门文章。
改进了滚动互操作视图的触控处理
在此版本中:
- 内容不可滚动的 Compose 视图,如果以 Modal
UIViewController呈现,可以使用向下滑动手势关闭。 - 嵌套的可滚动视图在通用的互操作触控框架中可正确工作:当在可滚动的 Compose 视图中滚动native 内容,或在可滚动的原生视图中滚动 Compose 内容时,UI 密切遵循 iOS 逻辑来解决模糊的触控序列。
Opt-in 并发渲染(实验性)
Compose Multiplatform for iOS 现在支持将渲染任务分载到专用的渲染线程。并发渲染可以在没有 UIKit 互操作的场景中提升性能。
通过启用 ComposeUIViewControllerConfiguration 类的 useSeparateRenderThreadWhenPossible 标志,或直接在 ComposeUIViewController 配置块中启用 parallelRendering 属性,可在单独的渲染线程上编码渲染命令:
@OptIn(ExperimentalComposeUiApi::class) // 需要 ExperimentalComposeUiApi opt-in
fun main(vararg args: String) {
UIKitMain {
ComposeUIViewController(configure = {
parallelRendering = true // 启用并发渲染
}) {
// ... 您的 Compose UI
}
}
}
Web
Navigation库支持浏览器控件
在使用 Compose Multiplatform 构建的 Kotlin/Wasm 和 Kotlin/JS 应用程序中,导航现在可以正确地与基本浏览器控件配合工作。要启用此功能,请使用 window.bindToNavigation() 方法将浏览器窗口链接到主导航图。启用后,Web 应用将正确响应使用浏览器的后退和前进按钮来浏览历史记录(请参阅 pull request 1621 查看演示视频)。
Web 应用还会修改浏览器地址栏以反映当前目标路由,并在用户粘贴包含正确编码路由的 URL 时直接导航到目标(请参阅 pull request 1640 查看演示视频)。window.bindToNavigation() 方法有一个可选的 getBackStackEntryPath 参数,允许您自定义将路由字符串转换为 URL 片段的方式。
设置浏览器光标(实验性)
我们引入了一个实验性的 PointerIcon.Companion.fromKeyword() 函数,用于管理可在浏览器页面上用作鼠标指针的图标。通过将关键字作为参数传递,您可以根据上下文指定要显示的光标类型。例如,您可以为选择文本、打开上下文菜单或指示加载过程分配不同的指针图标。
查看所有可用关键字的完整列表。
资源预加载(实验性)
Compose Multiplatform 1.8.0 为 Web 目标引入了一个新的实验性 API,用于预加载字体和图像。预加载有助于防止视觉问题,例如未样式化文本的闪烁(FOUT)或图像/图标的闪烁。
现在可以使用以下函数来加载和缓存资源:
preloadFont(),用于预加载字体。preloadImageBitmap(),用于预加载位图图像。preloadImageVector(),用于预加载矢量图像。
详情请参阅文档。
Desktop
Windows 上的软件渲染改进
在 Windows 上为 Skia 切换到推荐的 clang 编译器加快了依赖 CPU 的渲染速度。这主要影响纯软件渲染,因为渲染通常依赖于 GPU,只有部分计算在 CPU 上完成。因此,在某些虚拟机和一些不受 Skia 支持的旧显卡上,这种改进非常明显:与 Compose Multiplatform 1.7.3 相比,Compose Multiplatform 生成的 Windows 应用在这些环境中的速度提高了高达 6 倍。
这一改进,加上对 Windows for ARM64 的支持,使得在 macOS 下的虚拟 Windows 系统上运行的 Compose Multiplatform UI 性能显著提升。
支持 Windows for ARM64
Compose Multiplatform 1.8.0 引入了在 JVM 上对 Windows for ARM64 的支持,提高了在基于 ARM 的 Windows 设备上构建和运行应用程序的整体体验。
Gradle 插件
androidLibrary 目标中的多平台资源支持(实验性)
从 Android Gradle 插件 8.8.0 版本开始,您可以在新的 androidLibrary 目标中使用生成的资源。为了使 Compose Multiplatform 与这些变更对齐,我们引入了对新的目标配置的支持,以处理打包到 Android 资源中的多平台资源。
如果您正在使用 androidLibrary 目标,请在配置中启用资源:
kotlin {
androidLibrary {
experimentalProperties["android.experimental.kmp.enableAndroidResources"] = true // 启用 Android 资源的实验性 KMP 支持
}
}
否则,您将遇到以下异常:org.jetbrains.compose.resources.MissingResourceException: Missing resource with path: …。
原文完毕,大伙儿对 Compose Multiplatform 1.8 的更新怎么看呢?