这是专栏第一篇非Vert.x相关的文章,既然是真全栈,那就要拿出全栈的样子来,从GUI到数据库,从App到NoSQL,我们都要会,而不仅仅是Vert.x,当然Vert.x很好,只是挨踢世界这么大,总有Vert.x覆盖不到的地方,比如今天要说的JavaFX。
首先声明,Java的GUI曾经是最弱的一环,毋庸置疑,而且历史上因为跟M$的斗争,导致Java失去了Desktop发展的最佳时间点,即便后来SUN&Oracle推出了JavaFX,Desktop市场整体也在走下坡路,挨踢世界已经转入移动互联网时代,Desktop已不再是市场的热点,所以JavaFX几乎不可能像Android&IOS火爆,对此Java&JavaFX程序猿们应该有一个清醒的认识,但是不管怎样,桌面端软件是全栈中不可忽视的一环,虽然不那么重要,但是不能少了它。
下面介绍一下JavaFX的几个特性,这些特性的特点就是上手极为简单,不需要投入多少时间,一个普通的Java程序猿就能上手开发JavaFX的应用。
特性一:绑定Binding
JavaFX新增了Property类,比如DoubleProperty,IntegerProperty etc.,而且所有控件都有Property,区别于普通的Java属性,比如int, double属性。区别在于Property属性可以绑定,在UI线程刷新控件的时候,会自动读取Property属性所绑定的对应属性的值,而不用用户实现并发更新等操作,这么说有些拗口,等下举个例子就明白了,先说特性二。
特性二:Group类
Group是一个Swing中所没有的容器,用过Swing的都知道,Swing的控件容器是各种Pane,which特别难用,新手很容易迷失在BorderPane还是FlowPane的教条使用中。说白了就是搞了半天还是不会用,鬼知道那些API是做啥用的,哥当年学Java的时候,就特别讨厌教程里面的Swing部分,造成的恶果就是工作之后再也不想碰Java的GUI,当然后来阴差阳错,还是去救了一阵的火,但并不改变我对Swing以及各种Pane的看法,两个字就是:难用。好在JavaFX提供给了我们一个新的选择Group。
Group就是一个可以通过坐标设置控件位置的容器,而且控件和控件之间可以重叠,比如我们把两个控件的layoutX以及layoutY坐标都设置为0,那么这两个控件都会在Group的左上角出现,同时会重叠,所以这就让我们在编程的时候,可以针对某一个控件做调整,是不是很方便呢?在Group里的控件,有以下四个属性需要了解,分别是layoutX,layoutY,width,height,分别是控件左上角的x坐标,y坐标,宽度和高度。都是Property属性,我们可以将其绑定到任意一个其他Property属性中去,是不是超级方便啊?下面说个例子,比如我们有两个按钮,button0和button1,我们想在屏幕右下角,也就是高2/3,宽2/3处显示我们的第一个按钮,同时要在左下角,也就是宽1/3,高2/3处显示第二个按钮,同时设置两个按钮的宽度为窗口宽度的10%,这种需求是不是看上去非常无理啊?但是用Group类和按钮的上述四个Property就可以超级容易滴实现这一点,代码如下:
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Group;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.stage.Stage;
public class Main extends Application {
@Override
public void start(Stage primaryStage) throws Exception{
// Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
Stage stage = new Stage();
Group group = new Group();
stage.setTitle("Example");
stage.setScene(new Scene(group, 300, 275));
stage.show();
Button button0 = new Button("button0");
Button button1 = new Button("button1");
group.getChildren().addAll(button0,button1);
//注意,高潮来了
button0.layoutXProperty().bind(stage.widthProperty().multiply(2).divide(3));
button0.layoutYProperty().bind(stage.heightProperty().multiply(2).divide(3));
button0.prefWidthProperty().bind(stage.widthProperty().multiply(.1));
button1.layoutXProperty().bind(stage.widthProperty().multiply(1).divide(3));
button1.layoutYProperty().bind(stage.heightProperty().multiply(2).divide(3));
button1.prefWidthProperty().bind(stage.widthProperty().multiply(.1));
}
public static void main(String[] args) {
launch(args);
}
}
嗯,这个是普通的Java类,带有一个main函数,在IDE中运行该main函数,之后就会看到:
嗯,按钮因为太窄了,所以上面的字变成了点点点,拖动将其拉宽:
诶,按钮的宽度始终是窗口宽度的10%哦,嗯,这样是不是很方便呢?
不过聪慧的你也许看到了,按钮主体并不在1/3和2/3处居中对齐,而是左上角的点位于该处,嗯,那该如何将按钮的中心点对准我们要的点呢?留作思考题,文章最后会给你答案。知道了这么好用的Group以及x,y,width和height四个属性,是不是所有控件的摆放都能被你轻松搞定呢?
下面继续说其他特性。
特性三:CSS支持
JavaFX还可以使用FXML来绘制控件的位置,类似于网页里面的HTML技术,都是ML(markup language置标语言)嘛,但是同时JavaFX还支持CSS哦,跟网页里面的CSS并无太多不同,只是在属性前面要加上-fx-前缀,这样我们就可以很容易滴邀请网页的美术人员帮我们来美化我们的控件啦。
同时我们需要一个工具,来告诉我们,各个控件的CSS属性都是啥,这就是伟大好用的Scenic View,打开你的JavaFX界面,同时打开Scenic View,后者就能显示出来每一个控件对应的CSS属性以及其他属性,是不是超级方便呢?
特性四:Lambda
这个其实是Java自身的特性,传统我们在Swing控件中写事件,我们需要用匿名内部类,但是现在可以用Lambda啦,用一个简单的匿名函数便可,例如我们想点击button0之后在控制台打出hello world:
button0.setOnMouseClicked(value -> System.out.println("hello world"));
一行搞定,是不是很舒服呢?当然这个Swing也能做得到。
特性五:Native Compiling
之前很多人在做Java程序的时候,都觉得提供给客户是一个大问题,因为要求客户装JRE,不太现实,现在好了,JavaFX提供了将jar和jre打包成DMG以及EXE这些针对某个平台绿色包装的功能,是不是超级方便呢?你可以把你的JavaFX程序直接打包成EXE或者DMG然后发给用Windows或者MacOSX的朋友或者客户,他们双击之后就可以运行了,而无需安装Java或者JRE或者JDK之类的鬼东西,是不是很爽呢?
这里我们建议用IntelliJ IDEA的可视化工具来做,当然也可以用命令行,不过我觉得没有多大意义,直接截图了,懒得打字了:
是不是超级方便呢?
好公布文中答案,直接减去按钮的宽度和高度的二分之一不就好了?
button0.layoutXProperty().bind(stage.widthProperty().multiply(2).divide(3).subtract(button0.widthProperty().divide(2)));
button0.layoutYProperty().bind(stage.heightProperty().multiply(2).divide(3).subtract(button0.heightProperty().divide(2)));
button0.prefWidthProperty().bind(stage.widthProperty().multiply(.1));
button1.layoutXProperty().bind(stage.widthProperty().multiply(1).divide(3).subtract(button1.widthProperty().divide(2)));
button1.layoutYProperty().bind(stage.heightProperty().multiply(2).divide(3).subtract(button1.heightProperty().divide(2)));
button1.prefWidthProperty().bind(stage.widthProperty().multiply(.1));
有了这些功能,你还需要Swing吗?