JavaFx 给你不一样的图形界面体验

2,449 阅读8分钟

「这是我参与2022首次更文挑战的第19天,活动详情查看:2022首次更文挑战」。

一、JavaFx 的简介

JavaFX 是开发 Java GUI 程序的新框架。相比传统的 AWT 和 Swing,JavaFx 有了更加丰富的使用方式。JavaFx 中提供了布局面板、按钮、图片、文字、颜色、形状等组件,丰富了我们界面,且界面比传统的 Swing 框架要更加柔美。另外,学过安卓的同学应该知道,安卓的布局实现十分方便。因为安卓采用布局文件的方式,而 JavaFx 同样可以使用布局文件,这样就大大减少了图形界面开发的工作量。

二、JavaFx 的简单使用

要使用 JavaFx,首先要你的 JDK 版本为 1.8 及以上我们可以使用 java -version 查看自己的 JDK 版本。我们先看一个最简单的窗口是什么样子:

package zack;

import javafx.application.Application;
import javafx.stage.Stage;

public class Win1 extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception {
        //设置窗口标题
        primaryStage.setTitle("first window");
        //设置窗口高度
        primaryStage.setHeight(300);
        //设置窗口宽度
        primaryStage.setWidth(400);
        //显示窗口
        primaryStage.show();
    }

    //运行窗口
    public static void main(String[] args){
        launch(args);
    }
}

首先是创建了一个名为 Win1 的类,我们让这个类继承 javafx.application.Application,Application 是一个抽象类,该类中包含一个 start 方法,我们实现这个方法即可。在方法中有一个 Stage 类型的参数,这个参数就是我们的界面了。我们在 start 方法中设置好窗口的属性,然后在 main 方法中运行即可。运行窗口如下:

在这里插入图片描述

我们再来丰富一下这个界面:

package zack;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.stage.Stage;

public class Win1 extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception {
        //创建一个按钮
        Button btn = new Button("OK");
        //创建一个场景
        Scene scene = new Scene(btn, 400, 300);
        //设置窗口标题
        primaryStage.setTitle("first window");
        //设置窗口的场景
        primaryStage.setScene(scene);
        //显示窗口
        primaryStage.show();
    }

    public static void main(String[] args){
        launch(args);
    }
}

在这里我们创建了一个按钮(javafx.scene.control.Button),然后将这个按钮放置到一个新创建的场景中,那么场景是什么东西呢?实际上 JavaFx 中的场景相当于 Swing 中的 Panel,我们可以在一个界面中放置场景,然后在场景上放置其它组件。窗口效果如下:

在这里插入图片描述

Stage、Scene 和其它组件之间的关系如下:

在这里插入图片描述

三、JavaFx 的基本组件

JavaFx 中包含了大量组件,我们先从最基本的来慢慢了解。包括了我们常用的文本框、按钮、图形还有我们的面板。我们先看看基本组件。

3.1 UI 组件

在我们编写一个界面时,通常会窗口、场景、面板、组件一起使用。为了丰富我们的窗口我们通常将组件放置在面板上。先看一个最简单的例子:

    @Override
    public void start(Stage primaryStage) throws Exception {
        //创建一个面板
        StackPane stackPane = new StackPane();
        //在面板上添加一个按钮
        stackPane.getChildren().add(new Button("OK"));
        //将面板添加到场景
        Scene scene = new Scene(stackPane, 400, 300);
        //设置窗口的场景
        primaryStage.setScene(scene);
        //显示窗口
        primaryStage.show();
    }

我们可以看到各个部分是依次叠加的,面板上面放组件、场景上面放面板、窗口上面显示场景。我们可以根据这个规律,创建更加丰富的窗口。

Label

Label 就是我们的文本标签,通常用来显示文字等。我们可以直接创建一个 Label:

Label label = new Label("test");

我们还可以创建一个带图形的标签:

Label label = new Label("Circle", new Circle(50, 50, 25));

我们可以设置文字颜色:

label.setTextFill(Color.RED);

我们还可以通过 style 的形式设置 Label 的属性:

label.setStyle("-fx-border-color: green;-fx-border-width: 2");
Button

button 在前面我们已经使用过,button 的使用和 Label 差不多,我们看一个简单的例子:

    @Override
    public void start(Stage primaryStage) throws Exception {
        StackPane stackPane = new StackPane();

        //创建一个按钮
        Button label = new Button("test", new Circle(50, 50, 25));
        //设置按钮的边框
        label.setStyle("-fx-border-color: green;-fx-border-width: 2");
        stackPane.getChildren().add(label);
        Scene scene = new Scene(stackPane, 400, 300);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

显示效果如下:

在这里插入图片描述

TextField

TextField 是文本域标签,即文本输入框,我们可以直接创建文本域:

TextField textField = new TextField();

我们可以在创建的时候传入文字:

TextField textField = new TextField("test");

传入的文字会作为默认文本显示。我们还可以通过 getCharacters 方法获取文本域的内容。

3.2 面板

JavaFx 中的面板更像是面板和布局的合并,我们可以不同面板上有不同的布局,我们可用通过将相应组件放置在相应的面板上来实现布局,我们先看看 JavaFx 中提供了哪些面板。

面板名称面板布局
FlowPane流式布局
GridPane网格布局
BorderPane边界布局
HBox水平布局
VBox垂直布局
StackPane栈布局

我们分别看看各个布局的用法。

FlowPane

为了方便,我们直接看 start 方法里面的代码:

@Override
public void start(Stage primaryStage) throws Exception {
        //创建一个流式布局
        FlowPane flowPane = new FlowPane();

        //创建两个按钮
        Button btn1 = new Button("btn1");
        Button btn2 = new Button("btn2");

        //将两个按钮放置到六十布局上面
        flowPane.getChildren().add(btn1);
        flowPane.getChildren().add(btn2);

        //创建场景,并将布局放到场景上
        Scene scene = new Scene(flowPane, 400, 300);
        //将场景放到窗口上
        primaryStage.setScene(scene);
        //显示窗口
        primaryStage.show();
 }

上面所有组件都是在 JavaFX 包中,我们看看界面效果如何:

在这里插入图片描述

可以看到两个按钮紧贴着,而且是从左到右排列。然后我们用上面的方式,在布局上添加 10 个按钮,看看会是什么显示效果:

在这里插入图片描述

我们看到横向超过了,超过部分另起一行继续添加按钮。由此我们可以知道流式布局的布局规律是从上到下从左到右依次排列。

GridPane

GridPane 面板内的组件以网格的方式排列:

public void start(Stage primaryStage) throws Exception {
        GridPane gridPane = new GridPane();
        //设置边距,上右下左
        gridPane.setPadding(new Insets(5, 5, 5, 5));
        //设置横向格子数
        gridPane.setHgap(5);
        //设置纵向格子数
        gridPane.setVgap(5);

        //循环放置按钮
        for(int i = 0; i < 5; i++){
            for(int j = 0; j < 5; j++){
                //放置组件,传入3个参数,组件,横,纵
                gridPane.add(new Button((i+1)*(j+1) + ""), i, j);
            }
        }

        Scene scene = new Scene(gridPane, 400, 300);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

显示效果如下:

在这里插入图片描述

BorderPane

BorderPane 可以将节点放置在五个区域:顶部、底部、左边、右边以及中间,分别使用 setTop(node)、setBottom(node)、setLeft(node)、setRight(node) 和 setCenter(node) 方法。例子如下:

    @Override
    public void start(Stage primaryStage) throws Exception {

        BorderPane borderPane = new BorderPane();

        borderPane.setTop(new Button("上"));
        borderPane.setBottom(new Button("下"));
        borderPane.setLeft(new Button("左"));
        borderPane.setRight(new Button("右"));
        borderPane.setCenter(new Button("中"));

        Scene scene = new Scene(borderPane, 400, 300);
        primaryStage.setScene(scene);
        primaryStage.show();
    }
HBox、VBox

如果你学过 Android,那么就可以把他当成 LinearLayout,HBox 相当于将 LinearLayout 设置为 Horizontal,而 VBox 相当于将 LinearLayout 设置为 Vertical。实际上 HBox 和 VBox 就是横向布局和纵向布局,我认为是最好用的布局,具体使用和其它布局类似。

三、使用布局文件

在 JavaFx 中,我们可以使用布局文件的方式来写布局,这样就大大方便了布局的编写。我们先看一个最简单的布局文件 sample.fxml:

<?import javafx.geometry.Insets?>
<?import javafx.scene.layout.GridPane?>

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<GridPane fx:controller="sample.Controller"
          xmlns:fx="http://javafx.com/fxml" alignment="center" hgap="10" vgap="10">
</GridPane>

在这个布局里面,我们放置了一个面板,我们将面板的横向数量和纵向数量都设置为 10,对齐方式设置为居中。

在文件的最前面,我们通过:

package sample;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
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"));
        primaryStage.setTitle("Hello World");
        //将布局文件放置到场景上,并设置窗口的场景
        primaryStage.setScene(new Scene(root, 300, 275));
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

我们在布局文件中添加一个按钮:

<?import javafx.geometry.Insets?>
        <?import javafx.scene.layout.GridPane?>

        <?import javafx.scene.control.Button?>
        <?import javafx.scene.control.Label?>
<GridPane fx:controller="sample.Controller"
          xmlns:fx="http://javafx.com/fxml" alignment="center" hgap="10" vgap="10">
    <Button fx:id="btn1">文本</Button>
</GridPane>

我们将按钮的 id 设置为 btn1,我们可以在主类中关联这个按钮:

   public void start(Stage primaryStage) throws Exception{
        Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
        //关联界面中的id
        Button btn = (Button) root.lookup("#btn1");

        primaryStage.setTitle("Hello World");
        primaryStage.setScene(new Scene(root, 300, 275));
        primaryStage.show();
    }

关联按钮后,我们可以直接操作主类中的 btn 对象,对该对象的操作就是对布局文件中按钮的操作。另外我们需要注意一点,我们通过 FXMLLoader 加载的配置文件,返回最外层的控件对象。我们使用 lookup 方法只能获取直接子节点的对象。

四、事件驱动

时间驱动我们通过组件的 setOnAction 方法实现。在图形界面中,我们对图形界面的每一个操作就是一个事件。我们点击按钮,编辑文本等都是事件。像对事件进行响应,我们必须要有一个监听者,在 JavaFx 中提供了 EventHandler 类用于实现监听。实现监听的方式有三种,我们来看看。

4.1 匿名内部类

界面我们还是用开始的,我们看看主类中如何编写:

  public void start(Stage primaryStage) throws Exception{
        Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));

        Button tf1 = (Button) root.lookup("#tf1");

        //设置监听者
        tf1.setOnAction(new EventHandler<ActionEvent>() {
            //重写监听方法
            @Override
            public void handle(ActionEvent event) {
                System.out.println("点击了按钮");
            }
        });

        primaryStage.setTitle("Hello World");
        primaryStage.setScene(new Scene(root, 300, 275));
        primaryStage.show();
    }

我们的监听者需要实现 handle 方法,我们操作界面后,被监听者监听到,然后执行监听者中的 handle 方法。在 handle 中,我们可以通过参数 event 获取一些信息:

//获取发出事件的源
Button btn = (Button) event.getSource();
//获取事件源的信息
String text = btn.getText();
System.out.println(text);

4.2 内部类

我们在主类中编写一个类,继承 EventHandler 接口:

    class Lintener implements EventHandler {
        @Override
        public void handle(Event event) {
            System.out.println("按了按钮");
        }
    }

继承后我们实现 handle 方法,我们我们就可以创建该类的对象,作为监听者。

        tf1.setOnAction(new Lintener());

另外我们可以编写一个外部类,这种方式和匿名内部类的形式相同。

4.3 实战

我们来编写一个登录界面:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>

<VBox maxHeight="300" maxWidth="400" xmlns="http://javafx.com/javafx/8.0.121" xmlns:fx="http://javafx.com/fxml/1">
    <HBox maxHeight="50" maxWidth="400">
        <Label>用户名:</Label>
        <TextField fx:id="tf_username" promptText="请输入用户名" />
      <VBox.margin>
         <Insets left="50.0" top="50.0" />
      </VBox.margin>
    </HBox>
    <HBox maxHeight="50" maxWidth="400">
        <Label>密码:</Label>
        <TextField fx:id="tf_password" promptText="请输入密码" />
      <VBox.margin>
         <Insets left="50.0" top="20.0" />
      </VBox.margin>
    </HBox>
    <HBox maxHeight="50" maxWidth="400" spacing="50.0">
        <Button fx:id="btn_login">登录</Button>
        <Button fx:id="btn_register">注册</Button>
      <padding>
         <Insets left="100.0" top="20.0" />
      </padding>
    </HBox>
</VBox>

在这里我们使用布局文件的形式,然后在主类中关联:

package sample;

import javafx.application.Application;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.stage.Stage;

public class Main extends Application implements EventHandler{
    //声明需要关联的组件
    private TextField tf_username;

    private TextField tf_password;

    private Button btn_login;

    private Button btn_register;

    @Override
    public void start(Stage primaryStage) throws Exception{
        Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));

        //关联组件
        initView(root);

        //注册监听
        btn_login.setOnAction(this);
        btn_register.setOnAction(this);

        primaryStage.setTitle("Hello World");
        primaryStage.setScene(new Scene(root, 400, 300));
        primaryStage.show();
    }

    /**
    *   关联各个组件
    */
    private void initView(Parent root) {
        tf_username = (TextField) root.lookup("#tf_username");
        tf_password = (TextField) root.lookup("#tf_password");
        btn_login = (Button) root.lookup("#btn_login");
        btn_register = (Button) root.lookup("#btn_register");
    }

    public static void main(String[] args) {
        launch(args);
    }

    //实现监听方法
    @Override
    public void handle(Event event) {
        Button btn = (Button) event.getSource();
        String text = btn.getText();
        if("登录".equals(text)){
            String name = tf_username.getText();
            String pwd = tf_password.getText();
            System.out.println("用户名:" + name + "密码" + pwd);
            System.out.println("点击了登录");
        }else if("注册".equals(text)){
            System.out.println("点击了注册");
        }
    }
}

运行界面如下,当我们点击登录时会打印用户名和密码,当我们点击注册时,会打印我在注册。

在这里插入图片描述

为了方便编写图形界面,我们可以使用 IDEA,在 IDEA 中有专门的 JavaFX 项目,当我们使用布局文件时会有如下界面。

在这里插入图片描述

当然了,最开始我们看到的是一串标签,我们可以点击红色部分,切换到 SceneBuilder 然后我们就可以开始编写我们的图形界面了。