支持 PC 、Web 和 Android 的 Compose Multiplatform 进入 Alpha 版本

原文链接:blog.jetbrains.com/kotlin/2021…

Compose Multiplatform 的发布标志着使用 Kotlin 开发统一的 UI 支持又迈进了新一步!

基于 Google 发布的 Jetpack Compose Android 1.0 稳定版 的前提 :

  • Compose for Desktop 和 Compose for Web 已经升级为 Alpha 版本,它们的版本控制现在在 Compose Multiplatform 下保持一致,从而可以使用相同的 artifacts 构建 Android、Desktop 和 Web UI。

  • IDE 管理应用 JetBrains Toolbox App 已完成迁移到 Compose for Desktop。

  • IntelliJ IDEA 和 Android Studio 的新插件 通过@Preview注释可以启用 Compose for Desktop 的组件预览。

  • Compose for Desktop 现在默认使用可组合 API Window ,提供对自适应窗口大小、统一图像资源的新支持,以及对 ARM64 上 Linux 的新平台支持,允许在 Raspberry Pi 等目标上运行它。

  • Compose for Web 进一步扩展了 DOM 和 CSS API。

另外在 The Compose Story 中 概述了 Compose 的路径,并分享了更多声明式多平台用户界面的信息!

统一 Desktop、Web 和 Android UI 开发

Jetpack Compose 是一个为 Android 构建原生用户界面的响应式开发 UI 框架,JetBrains 在 Google 的 Jetpack Compose 的基础上将 Compose 框架拓展到更多新的平台。

借助 Compose Multiplatform,开发者可以使用和 Android Jetpack Compose 相同的 API 开发 Desktop 和 Web 构建用户界面。

使用 Kotlin Multiplatform 提供的机制,现在可以针对同一项目中的以下任意组合:

  • Android (Jetpack Compose)
  • Desktop
  • Web

以前 Compose for Desktop 和 Compose for Web 使用不同的 artifacts ,而现在开始它们将统一在一个 Gradle 插件和组件下,这意味着基于 Compose 开发 Android、Desktop 和 Web 用户界面会更容易。

凭借 Alpha 版本的更新,Compose Multiplatform 提供的 API 已经很接近其最终形态,我们将全力投入 Compose 的开发支持,并期望在 2021 年内能达到 1.0 版本,更多详细信息:了解有关 COMPOSE 多平台的更多信息

Compose in Production:JetBrains Toolbox 应用

在 JetBrains 现在一些生产应用已经开始采用 Compose,首先是 JetBrains Toolbox App,它是 JetBrains IDE 的管理应用程序,每月活跃用户超过 800,000。

在它们的最新版本中,该团队已将应用程序的实现完全转换为 Compose for Desktop,在从基于 Electron 的 UI 迁移期间,团队注意到了 Compose 的许多优势:

  • 内存消耗显着减少,特别是当应用程序在后台运行时
  • 安装程序大小减少了大约 50%
  • 应用整体渲染性能显着提升

JetBrains Toolbox 团队负责人 Victor Kropp 也在帖子中分享了他对 Compose for Desktop 的看法:

Compose for Desktop 虽然仍处于早期阶段,但已被证实了 Toolbox App 的绝佳选择。在开发框架同事的支持下,几乎可以在短时间内重写整个 UI。这使我们能够统一开发体验,因此从业务逻辑到 UI,从应用到服务器,Toolbox 现在 100% 是 Kotlin。

用于 Compose Multiplatform 的新 IntelliJ IDEA 和 Android Studio 插件

在此版本中还发布了一个新的 IDE 插件来支持开发工作:用于 IntelliJ IDEA 和 Android Studio 的 Compose Multiplatform 插件,它与框架的新版本一起发布,并提供额外的功能来帮助用户界面的开发工作

第一个版本包括了一项期待已久的功能:能够直接在 IDE 中预览 Compose for Desktop 和 Android 组件,甚至无需启动应用程序

要显示@Composable不带参数的函数预览,可以将@Preview注释添加到其定义中,这时候会添加一个小的装订线图标,可以使用它来切换组件的预览窗格:

这个新的预览版可以帮助开发者缩短开发周期,并更轻松地将想法转化为基于 Compose 的真实设计和布局。

要查找并安装新插件,请在插件市场中搜索“Compose Multiplatform IDE Support”,或点击下方直接打开插件页面:安装 COMPOSE 多平台插件

Compose for Desktop 的新增功能

除了将 Compose for Desktop 升级为 Alpha 的一大步之外,在此版本中还改进了其 API 并增加了对新平台的支持。

默认情况下可组合的窗口 API

在里程碑4桌面版中,我们推出了一个实验组APIWindowMenuBarTray,这些新 API 都使用 @Composable 与应用程序中的其他组件相同的状态管理、行为和条件呈现概念。

在此版本中,这些可组合版本现在是管理窗口、菜单栏和托盘图标的默认方式,取代了旧的窗口 API。

如果还没有尝试过这些新 API,或者只是想了解有关它们提供的行为和功能的更多信息,可以参考更新的 Compose for Desktop 教程,了解窗口和托盘管理。

自适应窗口大小

有时我们想将某些内容作为一个整体显示,而在不事先知道将显示的内容的情况下,这意味着我们不知道它的最佳窗口尺寸。

为了更轻松地开发这些 UI 场景,我们引入了自适应窗口大小功能,通过将窗口的一个或两个维度设置WindowSizeDp.Unspecified,Compose for Desktop 将自动调整该维度中窗口的初始大小以适应其内容:

fun main() = application {
   val state = rememberWindowState(width = Dp.Unspecified, height = Dp.Unspecified) //automatic size
   Window(
       onCloseRequest = ::exitApplication,
       state = state,
       title = "Adaptive",
       resizable = false
   ) {
       Column(Modifier.background(Color(0xFFEEEEEE))) {
           Row {
               Text("label 1", Modifier.size(100.dp, 100.dp).padding(10.dp).background(Color.White))
               Text("label 2", Modifier.size(150.dp, 200.dp).padding(5.dp).background(Color.White))
               Text("label 3", Modifier.size(200.dp, 300.dp).padding(25.dp).background(Color.White))
           }
       }
   }
}

连同删除窗口(通过undecorated = true 的应用程序Window定义),我们相信这种创建动态大小窗口的新方法,为各种形状和大小的用户界面新增了额外的可能性。

可组合窗口菜单的附加功能

桌面应用通常带有丰富而复杂的窗口菜单。在此版本中还添加了允许创建丰富菜单的其他 API。

它们可以被结构化,通过图标、快捷方式和助记符来丰富,并集成广泛使用的复选框和单选列表(单选按钮)的逻辑:

@OptIn(ExperimentalComposeUiApi::class)
@Composable
private fun MenuBarScope.FileMenu() = Menu("Settings", mnemonic = 'S') {
   Item(
       "Reset",
       mnemonic = 'R',
       shortcut = KeyShortcut(Key.R, ctrl = true),
       onClick = { println("Reset") }
   )
   CheckboxItem(
       "Advanced settings",
       mnemonic = 'A',
       checked = isAdvancedSettings,
       onCheckedChange = { isAdvancedSettings = !isAdvancedSettings }
   )
   if (isAdvancedSettings) {
       Menu("Theme") {
           RadioButtonItem(
               "Light",
               mnemonic = 'L',
               icon = ColorCircle(Color.LightGray),
               selected = theme == Theme.Light,
               onClick = { theme = Theme.Light }
           )
           RadioButtonItem(
               "Dark",
               mnemonic = 'D',
               icon = ColorCircle(Color.DarkGray),
               selected = theme == Theme.Dark,
               onClick = { theme = Theme.Dark }
           )
       }
   }
}

支持上下文菜单

Compose for Desktop Alpha 支持默认和自定义上下文菜单,可以通过单击鼠标右键触发。

对于可选择的文本和文本字段,该框架提供了一组默认的上下文菜单项,让用户可以复制、粘贴、剪切和选择。

@OptIn(ExperimentalComposeUiApi::class, androidx.compose.foundation.ExperimentalFoundationApi::class)
fun main() = singleWindowApplication(title = "Context menu") {
   DesktopMaterialTheme { //it is mandatory for Context Menu
       val text = remember {mutableStateOf("Hello!")}
       ContextMenuDataProvider(
           items = {
               listOf(ContextMenuItem("Clear") { text.value = "" })
           }
       ) {
               TextField(
                   value = text.value,
                   onValueChange = { text.value = it },
                   label = { Text(text = "Input") }
               )
       }
   }
}

光标更改行为和指针图标 API

在此版本的 Compose for Desktop 开始,当鼠标悬停在文本字段或可选文本上时,鼠标指针现在会自动变成文本选择光标,表示可以进行文本选择,使应用感觉更加贴近原生。

对于自己的组件,还可以使用新添加的 pointerIcon 修饰符来调整鼠标指针的行为,可以在将鼠标悬停在特定组件上时更改指针。

@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun ApplicationScope.pointerIcons() {
   Window(onCloseRequest = ::exitApplication, title = "Pointer icons") {
           Text(
               modifier = Modifier.pointerIcon(PointerIcon.Hand),
               text = "Hand icon!"
           )
   }
}

鼠标可点击修改器

为了更方便处理在鼠标单击时单击或按下的鼠标按钮和键盘修饰键,这里引入了一个带有.mouseClickable 修饰符的新 API 。

将此修饰符添加到组件就可以允许指定接收 MouseClickScope 的回调,它提供了有关事件的完整信息:

@ExperimentalDesktopApi
@Composable
fun ApplicationScope.mouseClickable() {
   Window(onCloseRequest = ::exitApplication, title = "mouseClickable") {
       Box {
           var clickableText by remember { mutableStateOf("Click me!") }

           Text(
               modifier = Modifier.mouseClickable(
                   onClick = {
                       if (buttons.isPrimaryPressed && keyboardModifiers.isShiftPressed)  {
                           clickableText = "Shift + left-mouse click!"
                       } else {
                           clickableText = "Wrong combination, try again!"
                       }
                   }
               ),
               text = clickableText
           )

       }
   }
}

请注意,这个 API 还不是最终版本,目前正在继续开发,将来可能会改变它。

统一的图像资源和 Painter

在进一步稳定 Compose for Desktop 的 API 的过程中,现在可以使用统一的 painterResource,而不是将图形资源分成 svgResourceimageResourcevectorXmlResource : 

@Composable
fun ApplicationScope.painterResource() {
   Window(onCloseRequest = ::exitApplication, title = "Image resources") {
       Column {
           Image(
               painter = painterResource("sample.svg"), // Vector
               contentDescription = "Sample",
               modifier = Modifier.fillMaxSize()
           )
           Image(
               painter = painterResource("sample.xml"), // Vector
               contentDescription = "Sample",
               modifier = Modifier.fillMaxSize()
           )
           Image(
               painter = painterResource("sample.png"), // ImageBitmap
               contentDescription = "Sample",
               modifier = Modifier.fillMaxSize()
           )
       }
   }
}

我们还将 window icon 属性从 java.awt.Image 更改为androidx.compose.ui.graphics.painter.Painter,因此除了未来的光栅图形之外,还可以使用基于矢量的图标:

fun vectorWindowIcon() {
   application {
       var icon = painterResource("sample.svg") //vector icon
       Window(onCloseRequest = ::exitApplication, icon = icon) {
           Text("Hello world!")
       }
   }
}

支持 ARM64 上的 Linux

在此版本中,除了现有的 x86-64 支持之外,Compose for Desktop 还增加了对运行在基于 ARM64 处理器的设备上的 Linux 的支持。

总的来说,现在可以使用 Compose for Desktop 为以下平台编写 UI:

  • x64 和 arm64 上的 macOS
  • x64 和 arm64 上的 Linux
  • x64 上的 Windows

Compose for Web 的新增功能

除了 Compose for Desktop,Compose for Web 也已升级为 Alpha 版本,两者都已经调整了版本控制方案和发布周期,并通过他们的 DSL 扩展了可用的功能,用于样式和事件管理。

扩展的 CSS API

我们将继续改进和完善 API,以通过 CSS 指定样式规则,这个最新版本在类型安全的 DSL 中增加了对算术运算、设置属性和动画的更好支持。

CSS 单元的算术运算

现在可以对 CSS 数值执行任意操作,如果对相同单位的两个值使用运算,将获得相同单位的新值,如下例所示:

val a = 5.px
val b = 20.px
borderBottom(a + b) // 25px

用于设置属性的 CSS API

扩展了对所有最常用的 CSS 属性的类型安全访问,并涵盖了大部分的现代浏览器支持的所有 CSS 属性。

这意味着在大多数情况下能够直接从类型安全的 API 中获得支持,而对于更奇特的属性,或尚不支持的属性,还可以通过property直接获取键和值的函数进行赋值:

borderWidth(topLeft = 4.px, bottomRight = 10%) // type-safe access!

property("some-exotic-property", "hello-friend") // raw property assignment

动画API

为了使基于 Compose 的用户界面更加动态,现在提供了从类型安全的 DSL 中创建 CSS 动画的选项:

object AppStyleSheet : StyleSheet() {
   val bounce by keyframes {
       from {
           property("transform", "translateX(50%)")
       }

       to {
           property("transform", "translateX(-50%)")
       }
   }

   val myClass by style {
       animation(bounce) {
           duration(2.s)
           timingFunction(AnimationTimingFunction.EaseIn)
           direction(AnimationDirection.Alternate)
       }
   }
}

如果想自己更多地探索这些 API,请务必查看我们新添加的示例,这些示例展示了一些更高级的 CSS 动画和 DOM 操作功能。

事件层次结构、事件侦听器和新的输入类型

处理事件,尤其是由输入组件发出的事件,是对 Compose 应用程序中的更改做出反应的关键部分之一。

在此版本中简化了对事件属性的访问,使其更容易定义事件侦听器,并提供了不同的输入类型。

事件类型层次结构

以前大多数基于事件的 API 要求直接使用nativeEvent eventTarget  以访问感兴趣的事件的值。

从此版本的 Compose for Web 开始,您现在可以访问 a SyntheticEvent,其子类型使其更容易访问所发出事件的相关属性。

  • SyntheticMouseEvent 公开坐标;
  • SyntheticInputEvent公开文本值;
  • SyntheticKeyEvent公开 click 键

举几个例子:

Div(attrs = {
   onClick { event -> // SyntheticMouseEvent
       val x = event.x
       val y = event.y
   }
})

这些新事件类型旨在直接提供对 在 Native 事件中可用 相同的属性的访问,而无需访问 nativeEvent 或直接事件的目标。

输入

在常规 HTML 中,不同的输入类型,从文本字段到复选框都共享同一个标签——输入。

为了更轻松地在 Kotlin DSL 中使用这些不同的输入类型并提供更多相关提示,这里引入了许多用于创建不同类型输入的附加函数:

TextInput(value = "text", attrs = {
   onInput { } // all these components have attrs same as HTMLInputElement
})
CheckboxInput(checked = false)
RadioInput(checked = false)
NumberInput(value = 0, min = 0, max = 10)
DateInput(value = 2021-10-10")
TelInput(value = "0123456")
EmailInput()
// and other input types

事件监听器

进一步统一了用于监听不同输入类型事件的函数。

输入侦听器的输入类型特定函数 onCheckBoxInput 已被删除,现在可以直接使用 onInputonChange ,这意味着不再需要搜索正确命名的回调:

Input(type = InputType.Text, attrs = {
   onInput { event ->
       val inputValue: String = event.value
   }
})

Input(type = InputType.Checkbox, attrs = {
   onInput { event ->
       val isChecked: Boolean = event.value
   }
})

试用 Compose Multiplatform Alpha!

无论是 Web、桌面、Android 还是这三者,我们都希望你能尝试一下 Compose Multiplatform!

我们预计 Compose Multiplatform 1.0(我们的第一个稳定版本)将于今年晚些时候发布,因此现在是尝试为您的生产应用评估 Compose Multiplatform 的理想时机。

这里有有许多可用资源:

预发布说明

Compose Multiplatform 目前处于 Alpha 阶段 ,虽然大多数 API 现在已经非常类似于它们的稳定形状,但请记住仍可能会更改某些 API,以确保最终版本提供尽可能最佳的开发体验。

随着接近稳定版本,我们将继续依靠用户的反馈来帮助实现这一目标!