一文速览 Compose 跨平台最新动态

以下内容来自公众号code小生,关注每日干货及时送达64e98037931a2d1c6f86480f2870b7c6.png

前言

Compose Multiplatform 1.5.0 现已正式推出。它采用适用于 Kotlin 的 Jetpack Compose 声明式 UI 框架,并将其从 Android 扩展到桌面端、iOS 和 Web。桌面版本已经稳定,iOS 处于 Alpha 阶段,Web 支持仍为实验性。有关完整说明,请参阅 Compose Multiplatform 网站¹。

此版本的一些亮点包括:

  1. Dialog、Popup 和 WindowInsets API 现在采用通用代码。

  2. 对于 iOS 滚动,资源管理和文本字段已得到改进。

  3. UI 测试框架在桌面端已经稳定。

此版本基于 Jetpack Compose 1.5²,重点关注性能改进。同时,它以 1.1 版 Material Design 3³ 为基础构建, 包括日期选择器和时间选择器等新组件。

96c85cc38e03b4eed53e28e86d78d1c6.png

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 上的显示方式:

dfc70e0b2686c61333a5ae4139d3b2f0.gif

桌面端对话框演示

55e15587be8b423c01af69fee96a1b55.gif

Android 和 iOS 对话框演示

此版本提供的第三项功能是 WindowInsets API⁸,描述为了防止内容与系统 UI 重叠而需要进行多少调整。从版本 1.5 开始,此功能包含在 Compose Multiplatform 中,因此可在 Android 和 iOS 上使用。

使用 WindowInsets API,可以通过 Compose Multiplatform 在凹口后绘制背景内容。无需在应用程序顶部添加白线。差异如以下屏幕截图所示:

f8e99c47acc40e1f3654d2d33ffab2f8.png

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 应用程序相同:

2cd4e6a948294ab480141f7cbb877a25.gif

对动态字体的支持


iOS 上的动态字体功能⁹允许用户设置偏好字体大小 – 大字体便于查看,小字体可容纳更多内容。应用中使用的文本大小应与此系统设置相关。

Compose Multiplatform 现在支持此功能。缩放文本时使用的增量与原生应用程序中使用的增量相同,因此行为将相同。

以如下可组合项为例:

@Composable
fun DynamicType() {
   Text("This is some sample text", fontSize = 30.sp)
}

首选阅读大小设为最小时的显示画面:

8f71e34af16d87560d72fd6fc69c3933.png

这是首选阅读大小为最大时的结果:

5d70cabba891de61df10c21bb5ca15ae.png

对高刷新率显示屏的支持

在之前的版本中,最大帧率为 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 已经克服了这些问题。

986f101028143366fd81b0eaaa7f06fd.png

大小写问题

首先,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 时的情形。

b8ea056548ad5f479ef20b76dc2c417a.gif

66d5ed35358f8b979321fcb612962976.png

硬件键盘

第二种情况与硬件键盘有关。在以前的版本中,使用硬件键盘时,按 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:

e57512fdde463dd936abed913235bd8c.gif

请注意,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 规则:

  1. 将 UI 的内容设置为应用可组合项。

  2. 通过 onNodeWithTag 查找文本字段和按钮。

  3. 在文本字段中重复输入示例值,然后点击按钮。

  4. 通过 onAllNodesWithTag 查找生成的所有文本节点。

  5. 断言当前已创建的文本节点数,并获取最后一个。

  6. 断言最后一次尝试包含预期消息。

增强了 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,内容如下:

  1. JFrame 包含带有垂直分隔线的 JSplitPane。

  2. 拆分窗格的左侧是青色的标准 JPanel。

  3. 右侧是 JLayeredPane,由两层组成:

  • 包含 Box 可组合项的 ComposePanel,颜色为黑色

  • 自定义 Swing 组件,其中文本“Popup”出现在白色矩形内。这通过重写 paintComponent 方法实现。

属性 compose.swing.render.on.graphics 设为 true 时:

  • 自定义 Swing 组件显示在 Box 可组合项顶部。

  • 移动滑块时不会出现过渡图形伪影。

7f55218c15b2fa917385d207d3c497a1.gif

如果此标志未设置,则自定义组件将不可见,并且滑块移动时可能出现过渡伪影:

5d4446a707a15944877615ef372c8034.gif

Jetpack Compose 最新进展

安卓UI开发新技能-Jetpack Compose

JetBrains 宣布 Jetpack Compose for Web

4ea9e0e6ce543029c9965d31d77bb63c.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值