一、Java事件处理机制的应用
JavaFX:JavaFX是Java平台上的一个GUI工具包,它提供了一些内置的事件处理机制。
Swing:Swing是Java平台上的另一个GUI工具包,它也提供了一些内置的事件处理机制。
二、JavaFX和Swing的关键区别:
以下是JavaFX和Swing之间的一些关键区别:
- JavaFX是比Swing更新的技术,旨在最终取代它。
- JavaFX旨在比Swing更现代和视觉上更吸引人,支持动画、3D图形和其他高级功能。
- JavaFX使用场景图模型进行渲染,而Swing使用更传统的基于小部件的方法。
- JavaFX比Swing更好地支持CSS样式,使创建视觉上一致的UI更容易。
- JavaFX比Swing更好地支持多媒体和Web内容。
三、 使用idea创建JavaFX应用
BootstrapFX:https://github.com/kordamp/bootstrapfx
提供特殊的CSS样式表用于美化JavaFX的GUI
ControlsFX:https://controlsfx.github.io/ 提供更多JavaFX中没有的组件
FormsFX:https://github.com/dlsc-software-consulting-gmbh/FormsFX/ 可以快速创建和设计表单
FXGL:https://github.com/AlmasB/FXGL?tab=readme-ov-file
Ikonli:https://kordamp.org/ikonli/
图标包
TilesFX:https://github.com/HanSolo/tilesfx?tab=readme-ov-file
游戏开发框架
ValidatorFX: https://github.com/effad/ValidatorFX
用于表单验证等
三、笔记
本笔记大部分根据此视频整理:https://www.bilibili.com/video/BV1Qf4y1F7Zv/?spm_id_from=333.337.search-card.all.click&vd_source=4085910f7c5c4dddcc04446ebf3aed6b
1. 基本机构
一个javaFX程序一般会有一个或多个窗口(Stage类的实例),
窗口可以设置一个场景(Scene类的实例),窗口可以切换不同的场景,但是一次只能设置一个,每个场景中可以添加一个或多个节点(node),每个node中也可以嵌套多个(node),node需有一个根节点
基本代码结构
Test就相当于程序的入口类,这个入口类必须继承Application,然后重写Application中的start方法,start方法中有个Stage 参数,这个就是表示的程序的窗口类,然后在重写的start方法中去实现一些场景,布局,节点等。
public class Test extends Application {
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage stage) throws Exception {
//窗口
stage.setTitle("我时窗口");
//标签
Label label = new Label("我是标签");
//场景
Scene scene = new Scene(label,300 , 300);
stage.setScene(scene);
stage.show();
}
}
2. 涉及类介绍
Application
Application类是程序入口类继承之后,重写其中的start()方法即可,但如果你要在窗口的创建前后做一些其他事情,那可以重写其init()方法和stop()方法,因为Application的生命周期为init() ----->start() ----->stop()(窗口关闭时自动调用)。
在idea中使用alt+insert ,点击Override Method 重写 init() 和stop()
Stage
窗口
常用方法:
-
Title
-
icon 设置程序的
此icon图片在resources下的images目录,resources目录的属性为 Rsources Root
stage.getIcons().add(new Image(Objects.requireNonNull(getClass().getResourceAsStream("/images/img.png"))));
-
resiziable
窗口大小是否可变,默认true可变,false 不可变stage.setResizable(false);
-
x,y,width,height
窗口的宽高,一般不设置,都是设置场景的宽高 -
StageStyye
窗口样式,比如不显示关闭和图片等外边样式。//默认样式,系统样式 stage.initStyle(StageStyle.DECORATED);
具体查看StageStyle中的其他枚举
-
Modality
窗口的模态,也就是窗口之间的影响关系,比如子窗口打开之后不能点击父窗口这种功能。stage.initModality(Modality.NONE);
具体查看Modality中的其他枚举
-
event
事件
//设置关闭窗口时,程序是否退出 默认为true 退出 false 不退出
Platform.setImplicitExit(false);
stage.setOnCloseRequest(event -> {
System.out.println("关闭窗口");
//取消默认关闭窗口事件
event.consume();
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
alert.setTitle("退出程序");
alert.setHeaderText(null);
alert.setContentText("点击确定退出");
alert.showAndWait().ifPresent(response -> {
if (response == ButtonType.OK) {
stage.close();
Platform.exit();
}
});
});
Scene
场景
- 场景切换
创建button --> 设置button位置—>把button放入锚点布局中—>创建场景,把锚点布局放入场景中 —>给button设置事件(因为是切换场景,所以给button设置事件要有第二个场景对象,所以此处给button设置事件写在最后,也就是第二个场景创建完成之后)
public void start(Stage stage) throws Exception {
//创建一个button
Button button0 = new Button("切换场景");
//设置button的位置
button0.setLayoutX(150);
button0.setLayoutY(150);
//把button放入锚点布局
AnchorPane anchorPane = new AnchorPane(button0);
//创建场景,把锚点布局放入场景
Scene scene = new Scene(anchorPane, 300, 300);
Button button1 = new Button("返回场景");
button1.setLayoutX(150);
button1.setLayoutY(150);
//标签
Label label = new Label("我是标签");
label.setLayoutX(100);
label.setLayoutY(100);
AnchorPane anchorPane1 = new AnchorPane(button1, label);
//场景2
Scene scene2 = new Scene(anchorPane1, 300, 300);
//给button0 添加事件
button0.setOnAction(event -> {
stage.setScene(scene2);
});
button1.setOnAction(event -> {
stage.setScene(scene);
});
//窗口
stage.setTitle("我时窗口");
stage.getIcons().add(new Image(Objects.requireNonNull(getClass().getResourceAsStream("/images/img.png"))));
stage.setScene(scene);
stage.show();
}
- 鼠标箭头的样式
scene.setCursor(new ImageCursor(new Image(Objects.requireNonNull(getClass().getResourceAsStream("/images/1xxx.png")))));
scene2.setCursor(new ImageCursor(new Image(Objects.requireNonNull(getClass().getResourceAsStream("/images/2xxx.png")))));
Node
Node类是一个抽象类,所有控件
都是的父类都是最终继承的Node抽象类,包括但不限于 按钮、复选框、颜色选择、日期选择、文字输入框、导航栏以及所有的可见控件。
常用方法
下面以Label标签为例来展示Node中的一些常用方法
//标签
Label label = new Label("我是标签");
//设置标签的位置
label.setLayoutX(100);
label.setLayoutY(100);
//设置标签的样式
label.setStyle("-fx-font-size: 20px; -fx-border-color: blue; -fx-border-width: 3px");
//设置标签的宽高
label.setPrefWidth(200);
label.setPrefHeight(50);
//设置标签的对齐方式
label.setAlignment(Pos.CENTER);
//设置标签的可见性
// label.setVisible(false);
//设置标签的混合模式 比如两个图层交叉部分的混合样式,不常用
// label.setBlendMode(BlendMode.ADD);
//设置标签的透明度
label.setOpacity(0.5);
//设置标签的旋转角度
label.setRotate(45);
//设置标签的平移
label.setTranslateX(20);
label.setTranslateY(20);
//设置3D的旋转角度
label.setScaleX(2);
label.setScaleY(2);
label.setScaleY(2);
//获取父节点
Parent parent = label.getParent();
//获取节点所在的场景
Scene scene1 = label.getScene();
//获取节点的id
String id = label.getId();
Node属性的 单向绑定 和 监听器
Circle circle = new Circle();
circle.setCenterX(50);
circle.setCenterY(50);
circle.setRadius(25);
circle.setFill(Color.WHITE);
circle.setStroke(Color.BLACK);
//Node属性的 单向绑定 ,例如 当scene的宽高改变时,circle的宽高也会改变
circle.centerXProperty().bind(scene2.widthProperty().divide(2));
circle.centerYProperty().bind(scene2.heightProperty().divide(2));
//Node属性的 监听器 ,监听属性的变化
circle.centerXProperty().addListener((observable, oldValue, newValue) -> {
System.out.println("X 轴位置改变了,原来是 "+oldValue+",现在是 "+newValue);
});
circle.centerYProperty().addListener((observable, oldValue, newValue) -> {
System.out.println("Y 轴位置改变了,原来是 "+oldValue+",现在是 "+newValue);
});
控件事件绑定
控件也就是继承node的那些控件,比如按钮,文本框,标签等,见上面Node介绍。
控件事件绑定就是给按钮绑定点击事件,键盘事件,鼠标事件,拖拽等。
其使用方法基本都是 控件.setOn事件(event)
,具体使用查看文档,事件非常多。
示例为给按钮绑定点击事件
,给场景绑定键盘按下释放时的事件
。
Button button3 = new Button("向上移动");
//设置button的位置
button3.setLayoutX(200);
button3.setLayoutY(200);
//button3按钮绑定事件 label 向上移动5px ---- 主要方法
button3.setOnAction(event -> {
label.setLayoutY(label.getLayoutY()-5);
});
AnchorPane anchorPane1 = new AnchorPane(button1, label,circle,button3);
//场景2
Scene scene2 = new Scene(anchorPane1, 300, 300);
//给场景2添加键盘事件 如果按键盘向下键击,label 向下移动5px
scene2.setOnKeyReleased(event -> {
KeyCode code = event.getCode();
if (code == KeyCode.DOWN){
label.setLayoutY(label.getLayoutY()+5);
}
});
Color、Font、Image
color
Color black = Color.BLACK;
Color rgb = Color.rgb(255, 0, 0);
Color hsb = Color.hsb(0, 0, 0);
Color web = Color.web("#ff0000");
Font
Font font1 = new Font("微软雅黑", 20);
Font font = Font.font("微软雅黑", 20);
// 字体,自重(bold是加粗),大小
Font font2 = Font.font("仿宋", FontWeight.BOLD, 20);
//使用资源文件种的字体 或者加载网络中的字体
Font font3 = Font.loadFont(Objects.requireNonNull(Test.class.getResource("")).toExternalForm(), 20);
Image
可以使用本地或者网络中的图片
ImageView imageView = new ImageView();
Image image = new Image(Objects.requireNonNull(getClass().getResourceAsStream("/images/img.png")));
imageView.setImage(image);
3. FXML布局文件
想写什么控件,就用什么标签,标签里的属性与写类代码时的属性一直
4. SceneBuilder
首先下载SceneBuilder: https://openjfx.cn/scene-builder/
一路下一步安装,安装之后在idea中配置
配置完之后在idea中打开fxml使用SceneBuilder打开方式
在SceneBuilder中创建FXMl,拖拽创建控件,然后修改控件属性,绑定事件
出现控制器中的代码
file–>save 保存 FXML
5. Controller中的initialize方法
布局文件 和 此controller 设置的属性和事件都初始化完毕,则会自动调用 initialize
也就是说,在initialize 方法中,可以获取布局文件中的控件,并做相应的初始化,如果布局文件中控件已经设置了对应的属性或事件,则initialize方法中会覆盖。
示例:以下代码中initialize会覆盖moveLable方法。因为moveLable是bu在FXMl中绑定的setOnAction事件,但是initialize又给bu绑定了另一个setOnAction事件,又因为initialize是在控件所有属性和事件都初始化完毕之后才执行的,所以就覆盖掉了之前绑定的事件。
public class HelloController2 {
@FXML
public Label la2;
@FXML
public Button bu;
@FXML
void moveLable() {
System.out.println("点击");
// la2.setLayoutY(la2.getLayoutY() - 10);
}
//布局文件 和 此controller 设置的属性和事件都初始化完毕,则会自动调用 initialize
//也就是说, 在initialize 方法中,可以获取布局文件中的控件,并做相应的初始化,如果布局文件中控件已经设置了对应的属性或事件,则initialize方法中会覆盖
public void initialize() {
bu.setOnAction(event -> {
System.out.println("xxxxxx");
});
}
}
6. 入口类Application中操作controller
比如,当需要图形根据scene 的大小的变化而变化时,也就是scene 的操作需要影响控件。
主要方法:fxmlLoader.getController()
public class HelloApplication extends Application {
@Override
public void start(Stage stage) throws IOException {
FXMLLoader fxmlLoader = new FXMLLoader(HelloApplication.class.getResource("demo.fxml"));
Scene scene = new Scene(fxmlLoader.load(), 600, 400);
//-----主要方法
HelloController2 controller = fxmlLoader.getController();
//circleBind 为controller中自己定义的方法
controller.circleBind(scene);
//---主要方法
stage.setTitle("Hello!");
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
public class HelloController2 {
@FXML
private Circle uu;
public void circleBind(Scene scene){
//单向数据绑定
uu.centerYProperty().bind(scene.heightProperty().divide(2));
uu.centerXProperty().bind(scene.widthProperty().divide(2));
}
}
7. Platform.runLater()
javaFX程序中,为了避免其他线程污染ui界面,其不允许除主线程以外的其他线程去更改或刷新ui,也就是说,刷新界面只能通过main方法来实现,也就是继承Application那个类。
如果想在主线程刷新ui界面,可以使用Platform.runLater(),该方法给让主线程空闲时候允许Platform.runLater()队列里的内容。也就是说把对ui操作的代码放在Platform.runLater()中。
Platform.runLater() 不是开启多线程,因为其和主线程的线程名称都一样。
参考:https://blog.csdn.net/weixin_57792864/article/details/127025819
很多控件的事件方法其实内部都是使用了Platform.runLater()
错误示例:
Exception in thread "Thread-3" java.lang.IllegalStateException: Not on FX application thread; currentThread = Thread-3
public class Test2 extends Application {
@Override
public void start(Stage stage) throws Exception {
Button button = new Button("获取姓名");
button.setLayoutX(200);
button.setLayoutY(200);
Label label = new Label("我的姓名是");
label.setLayoutX(200);
label.setLayoutY(100);
//错误代码 ----------------------------
button.setOnAction(event -> {
new Thread(() -> {
label.setText("我的姓名是 - 王二小");
}).start();
});
AnchorPane anchorPane = new AnchorPane(button,label);
Scene scene = new Scene(anchorPane,400,400);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
正确示例:
public class Test2 extends Application {
@Override
public void start(Stage stage) throws Exception {
Button button = new Button("获取姓名");
button.setLayoutX(200);
button.setLayoutY(200);
Label label = new Label("我的姓名是");
label.setLayoutX(200);
label.setLayoutY(100);
//主要代码 -----------------------------------
button.setOnAction(event -> {
Platform.runLater(() -> {
label.setText("我的姓名是 - 王二小");
});
});
AnchorPane anchorPane = new AnchorPane(button,label);
Scene scene = new Scene(anchorPane,400,400);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
8. Canvas 画布
https://www.bilibili.com/video/BV1Qf4y1F7Zv?p=14
可以在 Canvas 上绘制 线段、图形、图片、文字等
9. 动画类AnimationTimer
JavaFX有三类动画实现方式:Transition,TimeLine和AnimationTimer。
AnimationTimer看起来像是一个计时器,其实他更适合叫做心跳循环。JavaFX绘图的每一帧都会自动调用用AnimationTimer. AnimationTimer是一个抽象类。实现该类需要实现一个函数handle,其参数为调用时的nanoTime()。
public abstract void handle(long var1);
public void start();
public void stop();
public class Test4 extends Application {
private static final int WIDTH = 800; // 窗口宽度
private static final int Height = 600;
private static final Canvas canvas = new Canvas(WIDTH, Height);
private final GraphicsContext gc = canvas.getGraphicsContext2D();
Image bjimage = new Image(Objects.requireNonNull(getClass().getResourceAsStream("/images/beijing.png")));
Image dianImage = new Image(Objects.requireNonNull(getClass().getResourceAsStream("/images/点.png")));
double x = 400, y = 300;
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage stage) throws Exception {
update();
AnchorPane anchorPane = new AnchorPane(canvas);
Scene scene = new Scene(anchorPane);
scene.setOnKeyPressed(event -> {
switch (event.getCode()) {
case UP:
y -= 5;
break;
case DOWN:
y += 5;
break;
case LEFT:
x -= 5;
break;
case RIGHT:
x += 5;
break;
}
});
stage.setScene(scene);
stage.show();
}
private void draw() {
gc.drawImage(bjimage, 0, 0);
gc.drawImage(dianImage, x, y);
}
private void update() {
AnimationTimer timer = new AnimationTimer() {
@Override
public void handle(long now) {
draw();
}
};
timer.start();
}
}
问题:
JDK11 使用AudioClip
首先jdk11 javafx 没有media包,所以要先去maven仓库下载
<!--https://mvnrepository.com/artifact/org.openjfx/javafx-media-->
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-media</artifactId>
<version>17.0.10</version>
</dependency>
其次,在module-info.java文件中要引入media包
module com.wang.tank01 {
requires javafx.controls;
requires javafx.fxml;
requires javafx.media; //引入音频库
opens com.wang.tank01 to javafx.fxml;
exports com.wang.tank01;
exports com.wang.tank01.controller;
opens com.wang.tank01.controller to javafx.fxml;
}