Kotlin编写JavaFX的顺滑
如丝般顺滑的Kotlin+JavaFX
项目代码库
前言
谁会读这篇文章
- 致力于成为Kotlin桌面程序员的新手
- Kotlin爱好者想开发点桌面GUI来炫耀或者练习
- JavaFX爱好者
写作目的
- 把自己最近学习的心得记录下来,置于外部存储器
- 对初学者有一点点帮助
- 能够启发社区来投入JavaFX桌面开发
Kotlin桌面开发
Kotlin主要是为解决Android而开发的JVM编程语言,但是Kotlin的生命力使其迅速突破移动端。也有人说Kotlin四处出击,又是JVM又是Javascript,又是Native,没一个足够成熟,除提供语法糖之外并没有什么意义。但是我们作为用户,不需要去考虑那么多复杂的玩意。简单地说,试一试,好用就用。毕竟Talk is cheap, just show the code
.
JavaFx
JavaFX 官方网站:JavaFX
“JavaFX是一个开源的下一代客户端应用平台,用于桌面、移动和嵌入式系统,建立在Java之上。它是由许多个人和公司合作完成的,目标是为开发丰富的客户端应用程序制作一个现代、高效和功能齐全的工具包。”
Kotlin开发工具
目前用于开发Java的IDE普遍提供插件进行Kotlin开发。当然,最为方便的应该试试Jetbrains公司的Intelli IDEA,因为Kotlin就是他们公司的崽。
免费的社区版也就够用。
Kotlin最顺滑的地方就是Kotlin调用Java库非常简单,无缝调用加上语法糖,简直比Java自己调用还简单。
Kotlin+JavaFX的形状
HelloFXKt
还是拿26行的JavaFX程序,HelloFX.java,作为开始。
package hellofx
import javafx.application.Application
import javafx.scene.Scene
import javafx.scene.control.Label
import javafx.scene.layout.StackPane
import javafx.stage.Stage
class HelloFX : Application() {
override fun start(stage: Stage) {
val javaVersion = System.getProperty("java.version")
val javafxVersion = System.getProperty("javafx.version")
val l = Label("Hello, JavaFX $javafxVersion, running on Java $javaVersion.")
val scene = Scene(StackPane(l), 640.0, 480.0)
stage.scene = scene
stage.show()
}
}
fun main(args: Array<String>) {
Application.launch(HelloFX::class.java, *args)
}
程序大同小异,并没啥不同的。main
函数从类里面拿出来,launch
的调用也有一点点改变。去掉public
、new
、extends
,增加override
;set
方法变成属性赋值。还有字符串模板,写+
和${}
也没有很大区别。能看懂Java的基本上也能看懂Kotlin。
而且,上面的代码是用Jetbrains Intelli IDEA自动转化Java代码得到的,改动非常少(只有main
方法的部分)。
MVVM
但是有Kotlin之后,我们可以用干净利索点的方式来写。
MVVM模式有三个核心组件:模型、视图和视图模型。每一个都有不同的作用。下图显示了这三个组件之间的关系。
除了了解每个组件的职责外,了解它们如何相互作用也很重要。在高层次上,视图 "知道 "视图模型,而视图模型 "知道 "模型,但模型不知道视图模型,而视图模型也不知道视图。因此,视图模型将视图与模型隔离开来,并允许模型独立于视图来发展。
使用MVVM模式的好处有以下几点:
- 如果有一个现有的模型实现封装了现有的业务逻辑,那么改变它可能会很困难或有风险。在这种情况下,视图模型作为模型类的适配器,使你能够避免对模型代码做出任何重大的改变。
- 开发人员可以为视图模型和模型创建单元测试,而不使用视图。视图模型的单元测试可以行使与视图所使用的完全相同的功能。
- 应用程序的UI可以不修改业务代码的情况下被重新设计。
- 在开发过程中,设计师和开发人员可以独立地、同时地在他们的组件上工作。设计师可以专注于视图,而开发人员可以在视图模型和模型组件上工作。
有效使用MVVM的关键在于理解如何将应用代码纳入正确的类中,以及理解这些类是如何互动的。
JavaFX例子
我们把Hellofx程序做一点点小改动,增加一个按钮。
点击这个按钮,原来的文本标签变成显示JavaFX和Java的版本。
程序设计
JavaFX的主程序为Application
类不变。这个类显示View作为GUI用户界面,数据存储在Model中,ViewModel作为数据和界面解耦的部分存在。
主程序
主程序维持JavaFX应用的通用结构。继承Application
,实现start
方法,在这个方法中创建Model
对象,ViewModel
对象,Scene
对象,显示在屏幕上。
import javafx.application.Application
import javafx.scene.Scene
import javafx.stage.Stage
class HelloFX : Application() {
override fun start(stage: Stage) {
val m = Model()
val vm = ViewModel(m)
val root = getRoot(vm.versionText, vm::updateVersionText)
val scene = Scene(root, 640.0, 480.0)
stage.scene = scene
stage.show()
}
}
fun main(args: Array<String>) {
Application.launch(HelloFX::class.java, *args)
}
程序中,界面的元素为Scene
对象构造函数中的root
,从这里看到,root
为一个函数的范围值,这个函数的参数包括一个文本属性,一个函数。
下面就是界面设计,实际上也就是View多对应的那个函数的实现。
界面设计
界面包括一个Label
和一个Button
。View中处理界面的组合方式,处理界面的对齐方式,处理一切与数据不相关的部分。
fun textLabel(t: SimpleStringProperty): Node{
return Label(t.value).apply {
textProperty().bind(t)
}
}
fun updateButton(clickAction: () -> Unit): Node = Button("Check").apply {
setOnAction {
clickAction()
}
}
fun getRoot(text: SimpleStringProperty, clickAction: () -> Unit): Parent = VBox(10.0).apply {
children.add(textLabel(text))
children.add(updateButton(clickAction))
alignment = Pos.CENTER
}
可以看到,getRoot
调用两个函数来产生文本标签和按钮,这两个参数,一个绑定(下次把这个翻译出来)到标签的文本属性,一个函数绑定到按钮的事件。
这样的方式,在JetPack Compose中称为声明式界面设计。完全没有假设别的东西。View部分只对一个文本属性SimpleStringProperty
和一个() -> Unit
的函数提出要求。这就是MVVM中核心的要素,分割责任。这个部分可以任意更改,任意变更,只需要满足设计条件:
- 界面跟踪一个文本的变化;
- 界面出发一个函数调用。
这种方式保证了充分的内聚性。
Model设计
这里的数据模型非常简单。考虑到目前的交互是单向的,Model只需要提供数据,并没有反馈数据。
data class Model(
val javaVersion: String = System.getProperty("java.version"),
val javafxVersion: String = System.getProperty("javafx.version")
)
ViewModel
ViewModel沟通Model和Application,因此其构造函数包含Model的实例作为参数。
import javafx.beans.property.SimpleStringProperty
class ViewModel(private val m: Model) {
val versionText = SimpleStringProperty("Hello!")
fun updateVersionText() {
versionText.value = "Hello, JavaFX ${m.javafxVersion}, running on Java ${m.javaVersion}."
}
}
结论
通过一个特别简单的例子,可以看到JavaFX+Kotlin之后,界面的开发是多么的顺滑。
上面的程序有几个特点:
- 界面不需要知道任何ViewModel或者Model的细节,也不需要维护相应的对象引用;
- 界面用函数调用的形式来实现,通过绑定来明确其交互的效果,语义和语法都非常清晰;
- 界面的设计可以单独完成,并且自由更改;
- Model的部分不需要考虑任何可视化和人机交互,只需要单独考虑业务逻辑;
- Model可以单独开发,自由更改;
- 交互的业务逻辑在ViewModel中实现;
- ViewModel持有Model对象的引用;
- 当Model的接口改变时,需要修改ViewModel的代码,因此Model和ViewModel是紧耦合;
- View和其他部分是松耦合,只与Application耦合,不依赖于程序的任何其他部分;
- Model和其他部分是松耦合,不依赖于程序中的任何部分。
- 最终的界面交互逻辑和业务交互逻辑的关联由Application来维护。