第十五章 事件驱动编程和动画
15.1 引言
一个简单的例子
package com.example.demo;
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
public class Test extends Application {
@Override
public void start(Stage primaryStage) {
HBox pane = new HBox(10);
pane.setAlignment(Pos.CENTER);
Button btOK = new Button("OK");
Button btCancel = new Button("Cancel");
// 当单击OK按钮的时候,将显示消息“OK button clicked”。当单击Cancel按钮的时候,将显示消息“Cancel button clicked”
OKHandlerClass handler1 = new OKHandlerClass();
btOK.setOnAction(handler1);
CancelHandlerClass handler2 = new CancelHandlerClass();
btCancel.setOnAction(handler2);
pane.getChildren().addAll(btOK, btCancel);
Scene scene = new Scene(pane);
primaryStage.setTitle("HandleEvent");
primaryStage.setScene(scene);
primaryStage.show();
}
}
// 下面的代码定义了2个处理类,每个处理类都实现了EventHandler<ActionEvent>接口并覆写了handle方法来处理事件
class OKHandlerClass implements EventHandler<ActionEvent> {
@Override
public void handle(ActionEvent e) {
System.out.println("OK button clicked");
}
}
class CancelHandlerClass implements EventHandler<ActionEvent> {
@Override
public void handle(ActionEvent e) {
System.out.println("Cancel button clicked");
}
}
15.2 事件和事件源
15.2.1 解释
事件源类似于一个JavaFX的节点,是一个可供操作的用户界面控件,如按钮、文本框、菜单等
事件是从事件源产生的,用来执行一个命令,如单击按钮,输入文本框等。事件对象是一个具体的事件类的实例,例如ActionEvent
类、MouseEvent
类、KeyEvent
类等
在事件下达指令后,由事件处理器进行处理,来实现某些功能,例如,单击按钮以暂停/重启程序
15.2.2 事件处理器
一个类想成为事件处理器,需要同时满足2个条件:
第一,这个类必须实现统一的处理器接口EventHandler(或者其他对应的事件处理接口),并覆写抽象方法handle()
第二,必须使用代码来注册一个源对象,例如
// button是源对象,setOnAction是事件,EnlargeHandler是处理器
button.setOnAction(new EnlargeHandler());
15.2.3 图解
15.3 注册处理器和处理事件
package com.example.demo;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.BorderPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
// 代码实现:通过按钮点按,圆就会放大或缩小
public class ControlCircle extends Application {
// 创建一个字段circlePane,类型是CirclePane,这样,circlePane就可以调用CirclePane类的方法了
// 需要注意的是,CirclePane是一个面板,继承了StackPane,根据代码,调用了CirclePane的无参构造方法,就意味着CirclePane中添加了一个半径是50的圆
private CirclePane circlePane = new CirclePane();
@Override
public void start(Stage primaryStage) {
// 生成两个按钮
HBox hBox = new HBox();
hBox.setSpacing(10);
hBox.setAlignment(Pos.CENTER);
Button btEnlarge = new Button("Enlarge");
Button btShrink = new Button("Shrink");
hBox.getChildren().add(btEnlarge);
hBox.getChildren().add(btShrink);
// 当用户点击按钮时,下面的代码就会调用setOnAction方法,参数是内部类
// 在内部类中,会自动执行handle()方法,进行半径的扩大或缩小
btEnlarge.setOnAction(new EnlargeHandler());
btShrink.setOnAction(new ShrinkHandler());
BorderPane borderPane = new BorderPane();
borderPane.setCenter(circlePane);
borderPane.setBottom(hBox);
BorderPane.setAlignment(hBox, Pos.CENTER);
Scene scene = new Scene(borderPane, 200, 150);
primaryStage.setTitle("ControlCircle"); // Set the stage title
primaryStage.setScene(scene); // Place the scene in the stage
primaryStage.show(); // Display the stage
}
class EnlargeHandler implements EventHandler<ActionEvent> {
@Override // Override the handle method
public void handle(ActionEvent e) {
// circlePane的实际类型是CirclePane,所以可以调用这个方法
circlePane.enlarge();
}
}
class ShrinkHandler implements EventHandler<ActionEvent> {
@Override // Override the handle method
public void handle(ActionEvent e) {
circlePane.shrink();
}
}
}
class CirclePane extends StackPane {
private Circle circle = new Circle(50);
public CirclePane() {
getChildren().add(circle);
circle.setStroke(Color.BLACK);
circle.setFill(Color.WHITE);
}
// 更新属性
public void enlarge() {
circle.setRadius(circle.getRadius() + 2);
}
// 更新属性
public void shrink() {
circle.setRadius(circle.getRadius() > 2 ?
circle.getRadius() - 2 : circle.getRadius());
}
}
15.4 内部类
一个内部类被编译成一个名为OuterClass$InnerClass的类,例如,Test中的内部类A被编译成Test$A.class
一个内部类可以引用定义在它外部类中的数据和方法。所以,你没有必要将外部类对象的引用传递给内部类的构造方法
一个内部类可以使用可见性修饰符所定义
一个内部类可以被定义为 static
15.5 匿名内部类处理器
在注册事件的时候,先写出注册事件的语句,然后再调用内部类EnlargeHandler()的构造方法比较麻烦
btEnlarge.setOnAction(new EnlargeHandler());
class EnlargeHandler implements EventHandler<ActionEvent> {
@Overrid
public void handle(ActionEvent e) {
circlePane.enlarge();
}
}
为了简化代码使用,可以不显示声明EnlargeHandler类,而是变成一个匿名内部类。在内部类中,只保留new操作符和实现的接口,然后在类中写下要实现的代码即可
btEnlarge.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent e) {
circlePane.enlarge();
}
});
使用匿名内部类扩大缩小圆圈的完整代码如下:
package com.example.demo;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.BorderPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
// 代码实现:通过按钮点按,圆就会放大或缩小
public class ControlCircle extends Application {
// 创建一个字段circlePane,类型是CirclePane,这样,circlePane就可以调用CirclePane类的方法了
// 需要注意的是,CirclePane是一个面板,继承了StackPane,根据代码,调用了CirclePane的无参构造方法,就意味着CirclePane中添加了一个半径是50的圆
private CirclePane circlePane = new CirclePane();
@Override
public void start(Stage primaryStage) {
// 生成两个按钮
HBox hBox = new HBox();
hBox.setSpacing(10);
hBox.setAlignment(Pos.CENTER);
Button btEnlarge = new Button("Enlarge");
Button btShrink = new Button("Shrink");
hBox.getChildren().add(btEnlarge);
hBox.getChildren().add(btShrink);
// 当用户点击按钮时,下面的代码就会调用setOnAction方法,参数是内部类
// 在内部类中,会自动执行handle()方法,进行半径的扩大或缩小
btEnlarge.setOnAction(new EventHandler<ActionEvent>() {
@Override // Override the handle method
public void handle(ActionEvent e) {
circlePane.enlarge();
}
});
btShrink.setOnAction(new EventHandler<ActionEvent>() {
@Override // Override the handle method
public void handle(ActionEvent e) {
circlePane.shrink();
}
});
BorderPane borderPane = new BorderPane();
borderPane.setCenter(circlePane);
borderPane.setBottom(hBox);
BorderPane.setAlignment(hBox, Pos.CENTER);
Scene scene = new Scene(borderPane, 200, 150);
primaryStage.setTitle("ControlCircle"); // Set the stage title
primaryStage.setScene(scene); // Place the scene in the stage
primaryStage.show(); // Display the stage
}
}
class CirclePane extends StackPane {
private Circle circle = new Circle(50);
public CirclePane() {
getChildren().add(circle);
circle.setStroke(Color.BLACK);
circle.setFill(Color.WHITE);
}
// 更新属性
public void enlarge() {
circle.setRadius(circle.getRadius() + 2);
}
// 更新属性
public void shrink() {
circle.setRadius(circle.getRadius() > 2 ?
circle.getRadius() - 2 : circle.getRadius());
}
}
15.6 使用lambda表达式简化事件处理
如果要编译器理解lambda表达式,接口必须只包含一个抽象方法。这样的接口称为功能接口(functional interface)或者一个单抽象方法(Single Abstract Method, SAM)接口
15.7 示例学习:贷款计算器
15.8 鼠标事件
当一个鼠标按键在一个节点上或者一个场景中被按下、释放、单击、移动或者拖动时,一个MouseEvent事件被触发
15.9 键盘事件
在一个节点或者一个场景上面只要按下、释放或者敲击键盘按键,就会触发一个 KeyEvent 事件
15.10 可观察对象的监听器
监听器类通过实现InvalidationListener接口以重写invalidated(Observable o)方法,从而处理值的改变
15.11 动画
抽象类 Animation 提供了 JavaFX 中动画制作的核心功能, JavaFX 提供了许多 Animation 的具体子类,本节一一介绍:
15.11.1 PathTransition
PathTransition类制作一个在给定时间内,节点沿着一条路径从一个端点到另外一个端点的移动动画
15.11.2 FadeTransition
FadeTransition 类在一个给定的时间内,通过改变一个节点的透明度来产生动画
15.11.3 Timeline
Timeline类可以用于通过使用一个或者更多的KeyFrame(关键帧)来编写任意动画
15.12 示例学习:弹球
自行观看