以下内容来自公众号code小生,关注每日干货及时送达![64e98037931a2d1c6f86480f2870b7c6.png](https://i-blog.csdnimg.cn/blog_migrate/c0aec297ebf234fdcfb464b7306e2e0e.png)
前言
Compose Multiplatform 1.5.0 现已正式推出。它采用适用于 Kotlin 的 Jetpack Compose 声明式 UI 框架,并将其从 Android 扩展到桌面端、iOS 和 Web。桌面版本已经稳定,iOS 处于 Alpha 阶段,Web 支持仍为实验性。有关完整说明,请参阅 Compose Multiplatform 网站¹。
此版本的一些亮点包括:
Dialog、Popup 和 WindowInsets API 现在采用通用代码。
对于 iOS 滚动,资源管理和文本字段已得到改进。
UI 测试框架在桌面端已经稳定。
此版本基于 Jetpack Compose 1.5²,重点关注性能改进。同时,它以 1.1 版 Material Design 3³ 为基础构建, 包括日期选择器和时间选择器等新组件。
Compose Multiplatform 支持 Dialog、Popup 和 WindowInsets
从 1.5 版开始,Compose Multiplatform 中提供对话框和弹出窗口。对话框用于模态事件,用户在其中做出选择或输入数据。同时,弹出窗口用于非模态行为,例如提供可选功能。
在此版本中,基类型 Dialog⁴ 和 Popup⁵,以及 DropdownMenu⁶ 和 AlertDialog⁷ 都可以从通用代码中访问。这避免了提供平台特定功能的需要。
例如,下面的可组合项完全以通用代码编写:
@Composable
fun CommonDialog() {
var isDialogOpen by remember { mutableStateOf(false) }
Button(onClick = { isDialogOpen = true }) {
Text("Open")
}
if (isDialogOpen) {
AlertDialog(
onDismissRequest = { },
confirmButton = {
Button(onClick = { isDialogOpen = false }) {
Text("OK")
}
},
title = { Text("Alert Dialog") },
text = { Text("Lore ipsum") },
)
}
}
在桌面端、Android 和 iOS 上的显示方式:
桌面端对话框演示
Android 和 iOS 对话框演示
此版本提供的第三项功能是 WindowInsets API⁸,描述为了防止内容与系统 UI 重叠而需要进行多少调整。从版本 1.5 开始,此功能包含在 Compose Multiplatform 中,因此可在 Android 和 iOS 上使用。
使用 WindowInsets API,可以通过 Compose Multiplatform 在凹口后绘制背景内容。无需在应用程序顶部添加白线。差异如以下屏幕截图所示:
iOS 上的改进
iOS 平台是此次发布的重点,包含大量改进。滚动模仿了平台的外观和风格,资源管理得到简化,文本处理有所增强。
自然滚动
在此版本中,iOS 滚动已调整为模仿原生滚动。假设代码中要显示的条目的数量和/或大小超出可用空间:
@Composable
fun NaturalScrolling() {
val items = (1..30).map { "Item $it" }
LazyColumn {
items(items) {
Text(
text = it,
fontSize = 30.sp,
modifier = Modifier.padding(start = 20.dp)
)
}
}
}
滚动时,条目会从屏幕边缘弹开,与原生 iPhone 应用程序相同:
对动态字体的支持
iOS 上的动态字体功能⁹允许用户设置偏好字体大小 – 大字体便于查看,小字体可容纳更多内容。应用中使用的文本大小应与此系统设置相关。
Compose Multiplatform 现在支持此功能。缩放文本时使用的增量与原生应用程序中使用的增量相同,因此行为将相同。
以如下可组合项为例:
@Composable
fun DynamicType() {
Text("This is some sample text", fontSize = 30.sp)
}
首选阅读大小设为最小时的显示画面:
这是首选阅读大小为最大时的结果:
对高刷新率显示屏的支持
在之前的版本中,最大帧率为 60 FPS。这可能导致 UI 在 120Hz 屏幕的设备上缓慢且滞后。从这个版本开始,支持的帧率最高为 120 FPS。
简化了资源管理
从 1.5.0 开始,iOS 源集的资源文件夹中的任何资源都会默认复制到应用程序捆绑包中。例如,如果将图像文件放入 src/commonMain/resources/,它将被复制到捆绑包中并可从代码使用。Android Flutter Kotlin资料:https://pan.quark.cn/s/029c21024a18
使用 CocoaPods 时,不再需要在 Gradle 构建文件中配置此行为。您也不需要重新调用 podInstall 来确保资源在修改后被复制。
从这个版本开始,如果您试图在构建脚本中显式配置行为(如下所示),您将收到错误:
kotlin {
cocoapods {
extraSpecAttributes["resources"] = "..."
}
}
有关完整详细信息以及迁移现有代码的指南,请参阅此文档¹º 。
改进了 TextField
早期版本中,在两种情况下输入文本可能导致意外行为。从这个版本开始,增强的 TextField 已经克服了这些问题。
大小写问题
首先,TextField 现在可以识别首字母自动大写是否已禁用。这在输入密码时尤其重要。您可以通过 keyboardOptions 实参控制此行为。
为了说明这一点,请查看下面的可组合项:
fun TextFieldCapitalization() {
var text by remember { mutableStateOf("") }
TextField(
value = text,
onValueChange = { text = it },
keyboardOptions = KeyboardOptions(
capitalization = KeyboardCapitalization.Sentences,
autoCorrect = false,
keyboardType = KeyboardType.Ascii,
),
)
}
左图是大写属性设为 KeyboardCapitalization.None 时的情形,右图则显示了值为 KeyboardCapitalization.Sentences 时的情形。
硬件键盘
第二种情况与硬件键盘有关。在以前的版本中,使用硬件键盘时,按 Enter 会导致多个换行符,按 Backspace 会触发多个删除。从这个版本开始,这些事件可以正确处理。
桌面端改进
稳定了测试框架
此版本稳定了对 Compose for Desktop 测试的支持。Jetpack Compose 提供了一组测试 API¹¹ 来验证 Compose 代码的行为。这些 API 先前已移植到桌面端并在之前的版本中可用,但存在限制。这些限制现已移除,让您可以为应用程序编写全面的 UI 测试。
为了快速展示测试功能,我们来创建并测试一个简单的 UI。下方是我们的示例可组合项:
@Composable
fun App() {
var searchText by remember { mutableStateOf("cats") }
val searchHistory = remember { mutableStateListOf() }
Column(modifier = Modifier.padding(30.dp)) {
TextField(
modifier = Modifier.testTag("searchText"),
value = searchText,
onValueChange = {
searchText = it
}
)
Button(
modifier = Modifier.testTag("search"),
onClick = {
searchHistory.add("You searched for: $searchText")
}
) {
Text("Search")
}
LazyColumn {
items(searchHistory) {
Text(
text = it,
fontSize = 20.sp,
modifier = Modifier.padding(start = 10.dp).testTag("attempt")
)
}
}
}
}
这将创建一个记录搜索尝试的简单 UI:
请注意,Modifier.testTag 已用于为 TextField、Button 和 LazyColumn 中的条目指定名称。
然后,我们可以在 JUnit 测试中操作 UI:
class SearchAppTest {
@get:Rule
val compose = createComposeRule()
@Test
fun `Should display search attempts`() {
compose.setContent {
App()
}
val testSearches = listOf("cats", "dogs", "fish", "birds")
for (text in testSearches) {
compose.onNodeWithTag("searchText").performTextReplacement(text)
compose.onNodeWithTag("search").performClick()
}
val lastAttempt = compose
.onAllNodesWithTag("attempt")
.assertCountEquals(testSearches.size)
.onLast()
val expectedText = "You searched for: ${testSearches.last()}"
lastAttempt.assert(hasText(expectedText))
}
}
使用特定于 Compose 的 JUnit 规则:
将 UI 的内容设置为应用可组合项。
通过 onNodeWithTag 查找文本字段和按钮。
在文本字段中重复输入示例值,然后点击按钮。
通过 onAllNodesWithTag 查找生成的所有文本节点。
断言当前已创建的文本节点数,并获取最后一个。
断言最后一次尝试包含预期消息。
增强了 Swing 互操作性
此版本对 Swing 组件内 Compose 面板的改进呈现引入了实验性支持。这可以防止在显示、隐藏或调整面板大小时出现过渡呈现问题。它还支持在组合 Swing 组件和 Compose 面板时进行适当分层。Swing 组件现在可以在 ComposePanel 上方或下方显示。
为了说明这一点,请查看下面的示例:
fun main() {
System.setProperty("compose.swing.render.on.graphics", "true")
SwingUtilities.invokeLater {
val composePanel = ComposePanel().apply {
setContent {
Box(modifier = Modifier.background(Color.Black).fillMaxSize())
}
}
val popup = object : JComponent() { ... }
val rightPanel = JLayeredPane().apply {
add(composePanel)
add(popup)
...
}
val leftPanel = JPanel().apply { background = CYAN }
val splitter = JSplitPane(..., leftPanel,rightPanel)
JFrame().apply {
add(splitter)
setSize(600, 600)
isVisible = true
}
}
}
在这段代码中,我们创建并显示一个 Swing JFrame,内容如下:
JFrame 包含带有垂直分隔线的 JSplitPane。
拆分窗格的左侧是青色的标准 JPanel。
右侧是 JLayeredPane,由两层组成:
包含 Box 可组合项的 ComposePanel,颜色为黑色
自定义 Swing 组件,其中文本“Popup”出现在白色矩形内。这通过重写 paintComponent 方法实现。
属性 compose.swing.render.on.graphics 设为 true 时:
自定义 Swing 组件显示在 Box 可组合项顶部。
移动滑块时不会出现过渡图形伪影。
如果此标志未设置,则自定义组件将不可见,并且滑块移动时可能出现过渡伪影:
JetBrains 宣布 Jetpack Compose for Web