JVM桌面框架的状态:Jetpack Compose for Desktop


Getting your feet wet

最初,Jetpack Compose是Android运行时的框架。Compose for Desktop是JVM的端口。

传统的GUI开发遵循OOP原则:单个图形组件封装状态并提供更改状态的行为。这就是AWT,Swing,JavaFX甚至SWT的工作方式。

Compose背后的想法是从OOP转向功能编程原则。组件由函数建模,状态作为其参数传递。状态更改时调用该函数。

这是一个Compose片段,在文本字段中显示一个值:

fun main() = Window {
  TextField("Hello world!")   
}

TextField 不是对构造函数的调用,而是对函数的调用 源代码确实确实消除了任何潜在的误解:

androidx.compose.material.TextField.kt
@Composable
fun TextField(
    value: String,
    ...
)

State hoisting

从组件中删除状态称为状态提升。

大多数应用程序不会停止显示状态,而是提供了一种更新状态的方法。GUI应用程序中的经典示例是具有一个镜像文本字段值的标签。

basic-compose.jpg

这是在Compose中完成的方式:

fun main() = Window {                                        
  val state = remember { mutableStateOf("Hello world!") }    
  Row {                                                      
    TextField(
      state.value,                                           
      { state.value = it }                                   
    )
    Text(state.value)
  }
}

这值得一些解释:

  1. 顶级容器。驱动撰写循环。我们将在下一节中讨论compose循环。
  2. 将状态对象包装在可变容器中
  3. 布局。在没有固定布局的情况下,组件会相互绘制
  4. 初始值
  5. 每次更改值时执行的功能
    Compose框架是围绕的概念设计的State。它为此类的实例提供了几种功能。

20210220114556.png

就其本身而言,状态并不有趣,就像镜像文本字段的值一样。让我们想象一个简单的计算器用例,但将其限制为两个字段的整数值之和。

derived-state-compose.jpg

我们需要一个状态对象来保存总和。Compose提供了派生状态的概念:

fun main() = Window {
  val first = remember { mutableStateOf(0) }                           
  val second = remember { mutableStateOf(0) }                          
  val sum = derivedStateOf { first.value + second.value }              
  Row {
    TextField(first.value.toString(),  { first.value = it.toInt() })
    TextField(second.value.toString(), { second.value = it.toInt() })
    Text(sum.value.toString())
  }
}
  1. First value field
  2. Second value field
  3. Whenever first or second value changes, sum is re-computed

您自己的撰写组件 创建自己的Compose组件就像实现一个函数并使用进行注释一样容易@Composable。

可将Composable应用于函数或lambda,以指示该函数/ lambda可用作构成从应用程序数据到树或层次结构的转换的组成部分。

—可组合的JavaDocs 上面的“计算器”代码段可以这样重写:

@Composable
fun IntField(state: MutableState<Int>) = TextField(     
  state.value.toString(),
  { state.value = it.toInt() }
)

fun main() = Window {
  val first = remember { mutableStateOf(0) }
  val second = remember { mutableStateOf(0) }
  val sum = derivedStateOf { first.value + second.value }
  Row {
    IntField(first)
    IntField(second)
    Text(sum.value.toString())
  }
}

瞧,一个新的自定义组件!

用注释组件@Composable有一个重要的后果:它更改字节码中函数的签名。在此,它类似于协程。

这是该IntField函数的反编译版本:

public static final void IntField(
    androidx.compose.runtime.MutableState<java.lang.Integer>,
    androidx.compose.runtime.Composer<?>, int
);

注意其他Composer参数。这就是Compose的魔力所在。

尽管编译器本身可以处理协程,但Compose需要专用的编译器插件来实现相同的结果。

撰写循环

到目前为止,我们将帖子集中在如何使用Compose进行开发上。我们避免了Compose的工作方式。尽管如此,我仍然相信使用Compose进行开发与使用其他框架有很大不同,因此值得一小节。

请记住,每个Compose组件都只是一个函数。这些函数是无状态的,并且通过传递参数从外部注入状态。状态更改时,Compose会检测到它并触发应用程序的重新绘制。再次调用函数,包括最上面的Window()一个。

Compose通过函数中添加的Composer参数来实现@Composable。最后,该Window功能设置了此机制。

有趣的是,Compose for Desktop依赖于从... Swing继承的GUI类JFrame。以下类图对此进行了总结:

20210220114202.png

记住状态

现在该写remember函数了。我们知道Compose会为每个状态更改调用函数。状态存储在这些函数内部的变量中。因此,当Compose调用一个函数时,状态会丢失并重置为其初始值。

运行不带该remember()功能的上述任何代码段:它们不会执行任何操作,因为每次更改后状态都会丢失。

为了跟踪重组的状态,您需要将其包装在一个remember块中。这告诉Compose缓存状态的值,并在调用函数后再次设置它-记住它。

QQ图片20210220114006.png 第一个remember()功能将仅calculation在初始合成期间运行。进一步的重组将产生缓存的值。

重载函数允许传递一个或多个参数。如果自上次合成以来参数已更改,则Compose将调用该calculation函数并将状态设置为其返回值。否则,它的行为将与上面相同-缓存值。

其他注意事项

Α

首先,请注意Compose for Desktop是alpha。随时可能更改。您已被警告。

Gradle插件

由于其Android根源,Compose插件仅在Gradle中可用,该插件具有更改字节码中的功能签名的魔力。为此,请放心,我敢肯定没有Maven插件会正式发布。除非你写一个。

分配

该插件提供了package创建操作系统特定的安装程序的任务。这非常适合分发您的应用程序。

该任务jpackage在后台使用,因此请确保使用JDK 14或更高版本。另外,请注意,您仍然需要JRE才能执行已安装的应用程序。

标签

要标记字段,请避免Text像以前的框架一样在UI上放置组件。而是将它们自己设置在字段上。

TextField(
    value = "Hello world!",
    onValueChange = {},
    label = { Text("Say hello!") },
)

没有任何值,Compose将文本标签显示为占位符。具有一个值或当它们获得焦点时,它将使它们移到上方。

显示标签

缺少功能

同样由于Android和框架的成熟,缺少一些重要的功能。我至少注意到以下几点:

  • 没有制表符,即按TAB跳转到下一个字段
  • 没有表组件àla JTable。另一方面,Compose提供了一个SwingPanel允许您嵌入任何Swing组件的工具。
  • 可能有复杂的布局,但至少对于我来说实现起来似乎很复杂 这是最终结果:

result-compose.jpg

结论

Jetpack Compose for Desktop似乎是一个有趣的举措。该框架尚处于初期阶段。但是,与所有其他Java桌面框架相比,功能方法是原始的。

可以在Github上以Maven格式找到此帖子的完整源代码。


原文链接:http://codingdict.com