《Java 程序设计》第 15 章 - 事件处理与常用控件

发布于:2025-08-01 ⋅ 阅读:(19) ⋅ 点赞:(0)

        大家好!今天我们来学习《Java 程序设计》中的第 15 章内容:事件处理与常用控件。这一章是 Java GUI 编程的核心,掌握这些内容将帮助你创建交互性强、用户体验好的桌面应用程序

思维导图

15.1 事件处理

        在 GUI 应用程序中,用户与界面的交互(如点击按钮、输入文本、移动鼠标等)都会产生事件。事件处理就是对这些用户行为做出响应的机制。

15.1.1 事件处理模型

Java 采用委托事件模型(Delegation Event Model) 来处理事件,其核心思想是:

  • 事件源(Event Source):产生事件的组件(如按钮、文本框等)
  • 事件对象(Event Object):封装了事件的相关信息
  • 事件监听器(Event Listener):负责处理事件的对象

15.1.2 事件类和事件类型

JavaFX 中提供了多种事件类,用于表示不同类型的事件,常用的有:

  • ActionEvent:动作事件,如按钮点击、菜单选择等
  • MouseEvent:鼠标事件,如点击、移动、拖拽等
  • KeyEvent:键盘事件,如按键按下、释放等
  • WindowEvent:窗口事件,如打开、关闭、最小化等
  • ScrollEvent:滚动事件
  • TouchEvent:触摸事件(适用于触摸屏设备)

下面是主要事件类的类图:

@startuml
class Event {
  +getSource(): Object
  +consume(): void
  +isConsumed(): boolean
  +getEventType(): EventType
}

class ActionEvent {
  +getActionCommand(): String
  +copyFor(Object, EventTarget): ActionEvent
}

class MouseEvent {
  +getX(): double
  +getY(): double
  +getButton(): MouseButton
  +getClickCount(): int
}

class KeyEvent {
  +getCode(): KeyCode
  +getText(): String
  +isShiftDown(): boolean
  +isControlDown(): boolean
}

class WindowEvent {
  +getNewState(): int
  +getOldState(): int
}

Event <|-- ActionEvent
Event <|-- MouseEvent
Event <|-- KeyEvent
Event <|-- WindowEvent
@enduml

15.1.3 使用事件处理器

在 JavaFX 中,我们通过事件监听器来处理事件。常用的注册监听器的方式有:

  1. 使用setOnXXX()方法(如setOnAction()setOnMouseClicked()等)
  2. 使用addEventHandler()方法
  3. 实现特定的事件监听器接口

下面是一个简单的示例,展示如何使用事件处理器:

import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class EventHandlerDemo extends Application {
    
    @Override
    public void start(Stage primaryStage) {
        // 创建按钮(事件源)
        Button btn = new Button("点击我");
        
        // 方式1:使用setOnAction()方法添加事件处理器(匿名内部类)
        btn.setOnAction(new javafx.event.EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                System.out.println("按钮被点击了!");
            }
        });
        
        // 方式2:使用Lambda表达式(Java 8+)
        btn.setOnAction(e -> System.out.println("按钮被点击了(Lambda方式)!"));
        
        StackPane root = new StackPane();
        root.getChildren().add(btn);
        
        Scene scene = new Scene(root, 300, 250);
        
        primaryStage.setTitle("事件处理器示例");
        primaryStage.setScene(scene);
        primaryStage.show();
    }
    
    public static void main(String[] args) {
        launch(args);
    }
}

15.1.4 动作事件

ActionEvent是最常用的事件类型之一,通常由用户的交互动作触发,如点击按钮、在文本框中按 Enter 键、选择菜单项等。

下面是一个动作事件的综合示例:

import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class ActionEventDemo extends Application {
    
    private int clickCount = 0;
    
    @Override
    public void start(Stage primaryStage) {
        // 创建控件
        Label label = new Label("点击按钮或在文本框按Enter:");
        Button btn = new Button("点击我");
        TextField textField = new TextField();
        Label resultLabel = new Label("结果将显示在这里");
        
        // 按钮的动作事件处理器
        btn.setOnAction(e -> {
            clickCount++;
            resultLabel.setText("按钮被点击了 " + clickCount + " 次");
        });
        
        // 文本框的动作事件处理器(按Enter键触发)
        textField.setOnAction(e -> {
            String text = textField.getText();
            resultLabel.setText("你输入了:" + text);
            textField.clear(); // 清空文本框
        });
        
        // 创建布局并添加控件
        VBox root = new VBox(10); // 垂直布局,间距10
        root.getChildren().addAll(label, btn, textField, resultLabel);
        root.setStyle("-fx-padding: 20;"); // 设置内边距
        
        // 创建场景并显示
        Scene scene = new Scene(root, 300, 200);
        primaryStage.setTitle("动作事件示例");
        primaryStage.setScene(scene);
        primaryStage.show();
    }
    
    public static void main(String[] args) {
        launch(args);
    }
}

15.1.5 鼠标事件

MouseEvent处理与鼠标相关的事件,如点击、移动、拖拽、进入 / 离开组件等。

下面是一个鼠标事件的示例:

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;

public class MouseEventDemo extends Application {
    
    @Override
    public void start(Stage primaryStage) {
        // 创建控件
        Label infoLabel = new Label("移动鼠标或点击圆形");
        Label statusLabel = new Label("状态:等待鼠标操作");
        
        // 创建一个圆形,作为鼠标事件的目标
        Circle circle = new Circle(50, Color.BLUE);
        
        // 鼠标进入圆形
        circle.setOnMouseEntered(e -> {
            statusLabel.setText("鼠标进入圆形");
            circle.setFill(Color.GREEN);
        });
        
        // 鼠标离开圆形
        circle.setOnMouseExited(e -> {
            statusLabel.setText("鼠标离开圆形");
            circle.setFill(Color.BLUE);
        });
        
        // 鼠标在圆形上移动
        circle.setOnMouseMoved(e -> {
            String pos = String.format("位置:(%.0f, %.0f)", e.getX(), e.getY());
            statusLabel.setText(pos);
        });
        
        // 鼠标点击圆形
        circle.setOnMouseClicked(e -> {
            String msg = "";
            if (e.getButton() == MouseButton.PRIMARY) {
                msg += "左键点击";
            } else if (e.getButton() == MouseButton.SECONDARY) {
                msg += "右键点击";
            }
            
            if (e.getClickCount() == 2) {
                msg += "(双击)";
            }
            
            statusLabel.setText(msg);
        });
        
        // 创建布局并添加控件
        VBox root = new VBox(10);
        root.getChildren().addAll(infoLabel, circle, statusLabel);
        root.setStyle("-fx-padding: 20; -fx-alignment: center;");
        
        // 创建场景并显示
        Scene scene = new Scene(root, 300, 250);
        primaryStage.setTitle("鼠标事件示例");
        primaryStage.setScene(scene);
        primaryStage.show();
    }
    
    public static void main(String[] args) {
        launch(args);
    }
}

15.1.6 键盘事件

KeyEvent用于处理键盘输入事件,包括按键按下、释放和敲击(按下并释放)等动作。

下面是一个键盘事件的示例:

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class KeyEventDemo extends Application {
    
    @Override
    public void start(Stage primaryStage) {
        // 创建控件
        Label label = new Label("请在下方区域输入内容,或按方向键:");
        TextArea textArea = new TextArea();
        textArea.setPrefRowCount(5);
        Label statusLabel = new Label("状态:等待键盘输入");
        
        // 按键按下事件
        textArea.setOnKeyPressed(e -> {
            KeyCode code = e.getCode();
            String msg = "按下:" + code.getName();
            
            // 检查是否按下了修饰键
            if (e.isShiftDown()) msg += " + Shift";
            if (e.isControlDown()) msg += " + Ctrl";
            if (e.isAltDown()) msg += " + Alt";
            
            statusLabel.setText(msg);
            
            // 特殊处理方向键
            if (code.isArrowKey()) {
                e.consume(); // 消费事件,阻止文本区域处理方向键
                statusLabel.setText("方向键 " + code.getName() + " 被按下");
            }
        });
        
        // 按键释放事件
        textArea.setOnKeyReleased(e -> {
            statusLabel.setText("释放:" + e.getCode().getName());
        });
        
        // 按键敲击事件(按下并释放)
        textArea.setOnKeyTyped(e -> {
            String text = e.getCharacter();
            if (!text.isEmpty()) {
                statusLabel.setText("输入:" + text);
            }
        });
        
        // 创建布局并添加控件
        VBox root = new VBox(10);
        root.getChildren().addAll(label, textArea, statusLabel);
        root.setStyle("-fx-padding: 20;");
        
        // 创建场景并显示
        Scene scene = new Scene(root, 400, 300);
        primaryStage.setTitle("键盘事件示例");
        primaryStage.setScene(scene);
        primaryStage.show();
    }
    
    public static void main(String[] args) {
        launch(args);
    }
}

15.1.7 为属性添加监听器

        JavaFX 允许我们为控件的属性添加监听器,当属性值发生变化时得到通知。这在需要根据控件状态变化来更新 UI 或执行其他操作时非常有用。

下面是一个属性监听器的示例:

import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.control.Slider;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class PropertyListenerDemo extends Application {
    
    @Override
    public void start(Stage primaryStage) {
        // 创建控件
        Label titleLabel = new Label("属性监听器示例");
        titleLabel.setStyle("-fx-font-weight: bold; -fx-font-size: 16px;");
        
        // 文本框属性监听
        Label textFieldLabel = new Label("文本框内容变化:");
        TextField textField = new TextField();
        Label textFieldStatus = new Label("当前内容:");
        
        // 监听文本框的文本变化
        textField.textProperty().addListener(new ChangeListener<String>() {
            @Override
            public void changed(ObservableValue<? extends String> observable, 
                               String oldValue, String newValue) {
                textFieldStatus.setText("当前内容:" + newValue);
            }
        });
        
        // 滑块属性监听
        Label sliderLabel = new Label("滑块值变化:");
        Slider slider = new Slider(0, 100, 50);
        Label sliderStatus = new Label("当前值:" + slider.getValue());
        
        // 监听滑块的值变化
        slider.valueProperty().addListener(new ChangeListener<Number>() {
            @Override
            public void changed(ObservableValue<? extends Number> observable, 
                               Number oldValue, Number newValue) {
                sliderStatus.setText("当前值:" + String.format("%.1f", newValue));
            }
        });
        
        // 复选框属性监听
        CheckBox checkBox = new CheckBox("选中我");
        Label checkBoxStatus = new Label("选中状态:" + checkBox.isSelected());
        
        // 监听复选框的选中状态变化
        checkBox.selectedProperty().addListener(new ChangeListener<Boolean>() {
            @Override
            public void changed(ObservableValue<? extends Boolean> observable, 
                               Boolean oldValue, Boolean newValue) {
                checkBoxStatus.setText("选中状态:" + newValue);
            }
        });
        
        // 创建布局并添加控件
        VBox root = new VBox(15);
        root.getChildren().addAll(
            titleLabel,
            textFieldLabel, textField, textFieldStatus,
            sliderLabel, slider, sliderStatus,
            checkBox, checkBoxStatus
        );
        root.setStyle("-fx-padding: 20;");
        
        // 创建场景并显示
        Scene scene = new Scene(root, 300, 350);
        primaryStage.setTitle("属性监听器示例");
        primaryStage.setScene(scene);
        primaryStage.show();
    }
    
    public static void main(String[] args) {
        launch(args);
    }
}

15.2 常用控件

JavaFX 提供了丰富的 UI 控件,用于构建用户界面。下面我们介绍一些最常用的控件及其用法。

15.2.1 Label 类

Label是用于显示文本的控件,它不能被用户直接编辑。可以设置文本、字体、颜色等属性。

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.stage.Stage;

public class LabelDemo extends Application {
    
    @Override
    public void start(Stage primaryStage) {
        // 创建不同样式的Label
        Label label1 = new Label("普通标签");
        
        Label label2 = new Label("带有样式的标签");
        label2.setFont(new Font("Arial", 16)); // 设置字体和大小
        label2.setTextFill(Color.BLUE); // 设置文本颜色
        
        Label label3 = new Label("多行文本标签\n第二行文本\n第三行文本");
        label3.setStyle("-fx-font-weight: bold; -fx-text-fill: green;");
        
        // 创建带图标的标签(需要有对应的图片文件)
        Label iconLabel = new Label("带图标的标签");
        // 如果有图片,可以这样设置:
        // Image image = new Image("icon.png");
        // iconLabel.setGraphic(new ImageView(image));
        
        // 创建布局并添加控件
        VBox root = new VBox(10);
        root.getChildren().addAll(label1, label2, label3, iconLabel);
        root.setStyle("-fx-padding: 20;");
        
        // 创建场景并显示
        Scene scene = new Scene(root, 300, 250);
        primaryStage.setTitle("Label示例");
        primaryStage.setScene(scene);
        primaryStage.show();
    }
    
    public static void main(String[] args) {
        launch(args);
    }
}

15.2.2 Button 类

Button是用于触发动作的控件,用户可以点击它来执行特定操作。

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

public class ButtonDemo extends Application {
    
    private int clickCount = 0;
    
    @Override
    public void start(Stage primaryStage) {
        // 创建标签显示信息
        Label label = new Label("点击按钮进行操作");
        Label statusLabel = new Label("按钮未被点击");
        
        // 创建普通按钮
        Button clickButton = new Button("点击我");
        clickButton.setOnAction(e -> {
            clickCount++;
            statusLabel.setText("按钮被点击了 " + clickCount + " 次");
        });
        
        // 创建禁用按钮
        Button disabledButton = new Button("我是禁用的");
        disabledButton.setDisable(true); // 禁用按钮
        
        // 创建带图标的按钮(需要有对应的图片文件)
        Button iconButton = new Button("带图标的按钮");
        // 如果有图片,可以这样设置:
        // Image image = new Image("button-icon.png");
        // iconButton.setGraphic(new ImageView(image));
        
        // 创建布局并添加控件
        VBox root = new VBox(10);
        root.getChildren().addAll(label, clickButton, disabledButton, iconButton, statusLabel);
        root.setStyle("-fx-padding: 20;");
        
        // 创建场景并显示
        Scene scene = new Scene(root, 300, 250);
        primaryStage.setTitle("Button示例");
        primaryStage.setScene(scene);
        primaryStage.show();
    }
    
    public static void main(String[] args) {
        launch(args);
    }
}

15.2.3 TextField 类和 PasswordField 类

TextField用于接收用户的单行文本输入PasswordFieldTextField的子类,专门用于输入密码,输入的内容会被掩盖。

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;

public class TextFieldDemo extends Application {
    
    @Override
    public void start(Stage primaryStage) {
        // 创建标签和输入框
        Label nameLabel = new Label("用户名:");
        TextField nameField = new TextField();
        nameField.setPromptText("请输入用户名"); // 设置提示文本
        
        Label passwordLabel = new Label("密码:");
        PasswordField passwordField = new PasswordField();
        passwordField.setPromptText("请输入密码");
        
        Label infoLabel = new Label();
        
        // 创建按钮
        Button submitButton = new Button("提交");
        submitButton.setOnAction(e -> {
            String username = nameField.getText();
            String password = passwordField.getText();
            infoLabel.setText("用户名:" + username + ",密码长度:" + password.length());
        });
        
        Button clearButton = new Button("清空");
        clearButton.setOnAction(e -> {
            nameField.clear();
            passwordField.clear();
            infoLabel.setText("");
        });
        
        // 创建网格布局并添加控件
        GridPane root = new GridPane();
        root.setHgap(10); // 水平间距
        root.setVgap(10); // 垂直间距
        root.setStyle("-fx-padding: 20;");
        
        root.add(nameLabel, 0, 0);
        root.add(nameField, 1, 0);
        root.add(passwordLabel, 0, 1);
        root.add(passwordField, 1, 1);
        root.add(submitButton, 0, 2);
        root.add(clearButton, 1, 2);
        root.add(infoLabel, 0, 3, 2, 1); // 跨两列
        
        // 创建场景并显示
        Scene scene = new Scene(root, 350, 200);
        primaryStage.setTitle("TextField和PasswordField示例");
        primaryStage.setScene(scene);
        primaryStage.show();
    }
    
    public static void main(String[] args) {
        launch(args);
    }
}

15.2.4 TextArea 类

TextArea用于接收或显示多行文本,适合需要输入或显示大量文本的场景。

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class TextAreaDemo extends Application {
    
    @Override
    public void start(Stage primaryStage) {
        // 创建标签
        Label label = new Label("请输入多行文本:");
        
        // 创建文本区域
        TextArea textArea = new TextArea();
        textArea.setPromptText("在这里输入多行文本...");
        textArea.setPrefRowCount(10); // 设置默认行数
        textArea.setWrapText(true); // 自动换行
        
        // 创建按钮
        Button countButton = new Button("统计字数");
        Button clearButton = new Button("清空内容");
        Label statusLabel = new Label();
        
        // 按钮事件处理
        countButton.setOnAction(e -> {
            String text = textArea.getText();
            int charCount = text.length();
            int lineCount = textArea.getParagraphs().size();
            statusLabel.setText("字符数:" + charCount + ",行数:" + lineCount);
        });
        
        clearButton.setOnAction(e -> {
            textArea.clear();
            statusLabel.setText("");
        });
        
        // 创建布局并添加控件
        VBox root = new VBox(10);
        root.getChildren().addAll(label, textArea, countButton, clearButton, statusLabel);
        root.setStyle("-fx-padding: 20;");
        
        // 创建场景并显示
        Scene scene = new Scene(root, 400, 350);
        primaryStage.setTitle("TextArea示例");
        primaryStage.setScene(scene);
        primaryStage.show();
    }
    
    public static void main(String[] args) {
        launch(args);
    }
}

15.2.5 CheckBox 类

CheckBox是复选框控件,允许用户选择或取消选择一个选项,多个复选框可以同时被选中。

import javafx.application.Application;
import javafx.collections.ListChangeListener;
import javafx.scene.Scene;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class CheckBoxDemo extends Application {
    
    @Override
    public void start(Stage primaryStage) {
        // 创建标签
        Label label = new Label("请选择你喜欢的编程语言:");
        Label selectedLabel = new Label("你选择了:");
        
        // 创建复选框
        CheckBox javaBox = new CheckBox("Java");
        CheckBox pythonBox = new CheckBox("Python");
        CheckBox cBox = new CheckBox("C++");
        CheckBox jsBox = new CheckBox("JavaScript");
        
        // 设置一个默认选中
        javaBox.setSelected(true);
        
        // 监听每个复选框的选中状态变化
        javaBox.selectedProperty().addListener((obs, oldVal, newVal) -> updateSelectedLabel(selectedLabel, javaBox, pythonBox, cBox, jsBox));
        pythonBox.selectedProperty().addListener((obs, oldVal, newVal) -> updateSelectedLabel(selectedLabel, javaBox, pythonBox, cBox, jsBox));
        cBox.selectedProperty().addListener((obs, oldVal, newVal) -> updateSelectedLabel(selectedLabel, javaBox, pythonBox, cBox, jsBox));
        jsBox.selectedProperty().addListener((obs, oldVal, newVal) -> updateSelectedLabel(selectedLabel, javaBox, pythonBox, cBox, jsBox));
        
        // 初始更新一次标签
        updateSelectedLabel(selectedLabel, javaBox, pythonBox, cBox, jsBox);
        
        // 创建布局并添加控件
        VBox root = new VBox(10);
        root.getChildren().addAll(label, javaBox, pythonBox, cBox, jsBox, selectedLabel);
        root.setStyle("-fx-padding: 20;");
        
        // 创建场景并显示
        Scene scene = new Scene(root, 300, 250);
        primaryStage.setTitle("CheckBox示例");
        primaryStage.setScene(scene);
        primaryStage.show();
    }
    
    // 更新选中状态标签
    private void updateSelectedLabel(Label label, CheckBox... checkBoxes) {
        StringBuilder sb = new StringBuilder("你选择了:");
        boolean first = true;
        
        for (CheckBox cb : checkBoxes) {
            if (cb.isSelected()) {
                if (!first) {
                    sb.append("、");
                }
                sb.append(cb.getText());
                first = false;
            }
        }
        
        if (first) {
            sb.append("没有选择任何语言");
        }
        
        label.setText(sb.toString());
    }
    
    public static void main(String[] args) {
        launch(args);
    }
}

15.2.6 RadioButton 类

RadioButton单选按钮控件,通常成组使用,在一组单选按钮中只能有一个被选中。

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.RadioButton;
import javafx.scene.control.Toggle;
import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class RadioButtonDemo extends Application {
    
    @Override
    public void start(Stage primaryStage) {
        // 创建标签
        Label label = new Label("请选择你的性别:");
        Label selectedLabel = new Label("你的选择:");
        
        // 创建单选按钮组
        ToggleGroup group = new ToggleGroup();
        
        // 创建单选按钮并加入到组中
        RadioButton maleButton = new RadioButton("男");
        maleButton.setToggleGroup(group);
        
        RadioButton femaleButton = new RadioButton("女");
        femaleButton.setToggleGroup(group);
        
        RadioButton otherButton = new RadioButton("其他");
        otherButton.setToggleGroup(group);
        
        // 设置一个默认选中
        maleButton.setSelected(true);
        
        // 监听选择变化
        group.selectedToggleProperty().addListener((obs, oldVal, newVal) -> {
            if (newVal != null) {
                RadioButton selected = (RadioButton) newVal;
                selectedLabel.setText("你的选择:" + selected.getText());
            }
        });
        
        // 初始更新一次标签
        selectedLabel.setText("你的选择:" + ((RadioButton) group.getSelectedToggle()).getText());
        
        // 创建第二个单选按钮组 - 选择喜欢的颜色
        Label colorLabel = new Label("\n请选择你喜欢的颜色:");
        ToggleGroup colorGroup = new ToggleGroup();
        
        RadioButton redButton = new RadioButton("红色");
        redButton.setToggleGroup(colorGroup);
        
        RadioButton greenButton = new RadioButton("绿色");
        greenButton.setToggleGroup(colorGroup);
        
        RadioButton blueButton = new RadioButton("蓝色");
        blueButton.setToggleGroup(colorGroup);
        
        // 监听颜色选择变化
        colorGroup.selectedToggleProperty().addListener((obs, oldVal, newVal) -> {
            if (newVal != null) {
                // 可以根据选择的颜色改变界面样式
                String color = ((RadioButton) newVal).getText();
                String style = "";
                
                switch (color) {
                    case "红色":
                        style = "-fx-text-fill: red;";
                        break;
                    case "绿色":
                        style = "-fx-text-fill: green;";
                        break;
                    case "蓝色":
                        style = "-fx-text-fill: blue;";
                        break;
                }
                
                colorLabel.setStyle(style);
            }
        });
        
        // 创建布局并添加控件
        VBox root = new VBox(10);
        root.getChildren().addAll(
            label, maleButton, femaleButton, otherButton, selectedLabel,
            colorLabel, redButton, greenButton, blueButton
        );
        root.setStyle("-fx-padding: 20;");
        
        // 创建场景并显示
        Scene scene = new Scene(root, 300, 350);
        primaryStage.setTitle("RadioButton示例");
        primaryStage.setScene(scene);
        primaryStage.show();
    }
    
    public static void main(String[] args) {
        launch(args);
    }
}

15.2.7 ComboBox 类

ComboBox下拉列表控件,允许用户从预定义的选项中选择一个。

import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class ComboBoxDemo extends Application {
    
    @Override
    public void start(Stage primaryStage) {
        // 创建标签
        Label label = new Label("请选择你的专业:");
        Label selectedLabel = new Label("你选择了:");
        
        // 创建选项列表
        ObservableList<String> majors = FXCollections.observableArrayList(
            "计算机科学", "软件工程", "电子信息工程",
            "机械工程", "土木工程", "经济学", "管理学"
        );
        
        // 创建下拉列表
        ComboBox<String> comboBox = new ComboBox<>(majors);
        comboBox.setPromptText("请选择...");
        comboBox.setEditable(true); // 设置为可编辑,允许用户输入新值
        
        // 监听选择变化
        comboBox.getSelectionModel().selectedItemProperty().addListener(
            (obs, oldVal, newVal) -> {
                if (newVal != null) {
                    selectedLabel.setText("你选择了:" + newVal);
                }
            }
        );
        
        // 创建第二个下拉列表 - 选择城市
        Label cityLabel = new Label("\n请选择你所在的城市:");
        ComboBox<String> cityComboBox = new ComboBox<>();
        cityComboBox.setPromptText("请选择城市...");
        
        // 动态添加选项
        cityComboBox.getItems().addAll("北京", "上海", "广州", "深圳", "杭州");
        
        // 添加一个按钮来添加新城市
        Label dynamicLabel = new Label("输入新城市并按回车添加:");
        ComboBox<String> dynamicComboBox = new ComboBox<>();
        dynamicComboBox.setEditable(true);
        dynamicComboBox.setPromptText("输入新城市...");
        
        // 回车时添加新选项
        dynamicComboBox.setOnAction(e -> {
            String newCity = dynamicComboBox.getEditor().getText();
            if (newCity != null && !newCity.isEmpty() && 
                !dynamicComboBox.getItems().contains(newCity)) {
                dynamicComboBox.getItems().add(newCity);
                dynamicComboBox.setValue(newCity);
            }
        });
        
        // 创建布局并添加控件
        VBox root = new VBox(10);
        root.getChildren().addAll(
            label, comboBox, selectedLabel,
            cityLabel, cityComboBox,
            dynamicLabel, dynamicComboBox
        );
        root.setStyle("-fx-padding: 20;");
        
        // 创建场景并显示
        Scene scene = new Scene(root, 300, 400);
        primaryStage.setTitle("ComboBox示例");
        primaryStage.setScene(scene);
        primaryStage.show();
    }
    
    public static void main(String[] args) {
        launch(args);
    }
}

15.2.8 Slider 类

Slider是滑块控件,允许用户通过拖动滑块来选择一个范围内的值

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.Slider;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.stage.Stage;

public class SliderDemo extends Application {
    
    @Override
    public void start(Stage primaryStage) {
        // 创建标签
        Label titleLabel = new Label("滑块示例");
        titleLabel.setFont(new Font(16));
        
        // 1. 音量滑块(0-100)
        Label volumeLabel = new Label("音量控制:");
        Slider volumeSlider = new Slider(0, 100, 50); // 最小值,最大值,初始值
        volumeSlider.setShowTickLabels(true); // 显示刻度标签
        volumeSlider.setShowTickMarks(true); // 显示刻度线
        volumeSlider.setMajorTickUnit(20); // 主刻度间隔
        volumeSlider.setMinorTickCount(4); // 主刻度之间的小刻度数
        Label volumeValueLabel = new Label("当前音量:50%");
        
        // 监听音量滑块变化
        volumeSlider.valueProperty().addListener((obs, oldVal, newVal) -> {
            int value = (int) Math.round(newVal.doubleValue());
            volumeValueLabel.setText("当前音量:" + value + "%");
        });
        
        // 2. 亮度滑块(0-1)
        Label brightnessLabel = new Label("\n亮度控制:");
        Slider brightnessSlider = new Slider(0, 1, 0.7);
        brightnessSlider.setOrientation(javafx.geometry.Orientation.HORIZONTAL);
        Label brightnessValueLabel = new Label("当前亮度:70%");
        
        // 监听亮度滑块变化
        brightnessSlider.valueProperty().addListener((obs, oldVal, newVal) -> {
            double value = newVal.doubleValue();
            brightnessValueLabel.setText(String.format("当前亮度:%.0f%%", value * 100));
            
            // 改变背景亮度
            root.setStyle(String.format("-fx-background-color: rgba(255,255,255,%.2f); -fx-padding: 20;", value));
        });
        
        // 3. 字体大小滑块(10-30)
        Label fontSizeLabel = new Label("\n字体大小控制:");
        Slider fontSizeSlider = new Slider(10, 30, 16);
        Label fontSizeValueLabel = new Label("当前字体大小:16px");
        Label sampleTextLabel = new Label("这是一段示例文本,用于展示字体大小变化");
        
        // 监听字体大小滑块变化
        fontSizeSlider.valueProperty().addListener((obs, oldVal, newVal) -> {
            int value = (int) Math.round(newVal.doubleValue());
            fontSizeValueLabel.setText("当前字体大小:" + value + "px");
            sampleTextLabel.setFont(new Font(value));
        });
        
        // 创建布局并添加控件
        VBox root = new VBox(10);
        root.getChildren().addAll(
            titleLabel,
            volumeLabel, volumeSlider, volumeValueLabel,
            brightnessLabel, brightnessSlider, brightnessValueLabel,
            fontSizeLabel, fontSizeSlider, fontSizeValueLabel, sampleTextLabel
        );
        root.setStyle("-fx-padding: 20;");
        
        // 创建场景并显示
        Scene scene = new Scene(root, 400, 450);
        primaryStage.setTitle("Slider示例");
        primaryStage.setScene(scene);
        primaryStage.show();
    }
    
    public static void main(String[] args) {
        launch(args);
    }
}

15.2.9 菜单设计

JavaFX 提供了完整的菜单组件,包括MenuBar(菜单栏)、Menu(菜单)、MenuItem(菜单项)等,可以创建复杂的应用程序菜单。

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class MenuDemo extends Application {
    
    @Override
    public void start(Stage primaryStage) {
        // 创建主面板
        BorderPane root = new BorderPane();
        
        // 创建文本区域作为主内容
        TextArea textArea = new TextArea();
        textArea.setPromptText("这是一个文本编辑区域...");
        root.setCenter(textArea);
        
        // 创建状态栏
        Label statusLabel = new Label("就绪");
        root.setBottom(statusLabel);
        
        // 创建菜单栏
        MenuBar menuBar = new MenuBar();
        
        // 1. 文件菜单
        Menu fileMenu = new Menu("文件(F)");
        fileMenu.setMnemonicParsing(true); // 启用助记符解析(Alt+F)
        
        // 文件菜单项
        MenuItem newItem = new MenuItem("新建(N)");
        newItem.setMnemonicParsing(true);
        newItem.setOnAction(e -> {
            textArea.clear();
            statusLabel.setText("新建文件");
        });
        
        MenuItem openItem = new MenuItem("打开(O)");
        openItem.setMnemonicParsing(true);
        openItem.setOnAction(e -> statusLabel.setText("打开文件"));
        
        MenuItem saveItem = new MenuItem("保存(S)");
        saveItem.setMnemonicParsing(true);
        saveItem.setOnAction(e -> statusLabel.setText("保存文件"));
        
        // 添加分隔线
        SeparatorMenuItem separator = new SeparatorMenuItem();
        
        MenuItem exitItem = new MenuItem("退出(X)");
        exitItem.setMnemonicParsing(true);
        exitItem.setOnAction(e -> primaryStage.close());
        
        // 将菜单项添加到文件菜单
        fileMenu.getItems().addAll(newItem, openItem, saveItem, separator, exitItem);
        
        // 2. 编辑菜单
        Menu editMenu = new Menu("编辑(E)");
        editMenu.setMnemonicParsing(true);
        
        MenuItem cutItem = new MenuItem("剪切(T)");
        cutItem.setMnemonicParsing(true);
        cutItem.setOnAction(e -> {
            textArea.cut();
            statusLabel.setText("剪切");
        });
        
        MenuItem copyItem = new MenuItem("复制(C)");
        copyItem.setMnemonicParsing(true);
        copyItem.setOnAction(e -> {
            textArea.copy();
            statusLabel.setText("复制");
        });
        
        MenuItem pasteItem = new MenuItem("粘贴(P)");
        pasteItem.setMnemonicParsing(true);
        pasteItem.setOnAction(e -> {
            textArea.paste();
            statusLabel.setText("粘贴");
        });
        
        // 添加子菜单:格式
        Menu formatMenu = new Menu("格式(O)");
        formatMenu.setMnemonicParsing(true);
        
        // 子菜单项:字体大小
        Menu fontSizeMenu = new Menu("字体大小");
        RadioMenuItem smallItem = new RadioMenuItem("小");
        RadioMenuItem mediumItem = new RadioMenuItem("中");
        RadioMenuItem largeItem = new RadioMenuItem("大");
        mediumItem.setSelected(true);
        
        // 将单选菜单项加入到一个组中
        ToggleGroup sizeGroup = new ToggleGroup();
        smallItem.setToggleGroup(sizeGroup);
        mediumItem.setToggleGroup(sizeGroup);
        largeItem.setToggleGroup(sizeGroup);
        
        // 监听字体大小变化
        sizeGroup.selectedToggleProperty().addListener((obs, oldVal, newVal) -> {
            if (newVal == smallItem) {
                textArea.setFont(new javafx.scene.text.Font(12));
                statusLabel.setText("字体大小:小");
            } else if (newVal == mediumItem) {
                textArea.setFont(new javafx.scene.text.Font(16));
                statusLabel.setText("字体大小:中");
            } else if (newVal == largeItem) {
                textArea.setFont(new javafx.scene.text.Font(20));
                statusLabel.setText("字体大小:大");
            }
        });
        
        fontSizeMenu.getItems().addAll(smallItem, mediumItem, largeItem);
        
        // 子菜单项:粗体
        CheckMenuItem boldItem = new CheckMenuItem("粗体(B)");
        boldItem.setMnemonicParsing(true);
        boldItem.setOnAction(e -> statusLabel.setText("粗体:" + boldItem.isSelected()));
        
        formatMenu.getItems().addAll(fontSizeMenu, boldItem);
        
        editMenu.getItems().addAll(cutItem, copyItem, pasteItem, separator, formatMenu);
        
        // 3. 帮助菜单
        Menu helpMenu = new Menu("帮助(H)");
        helpMenu.setMnemonicParsing(true);
        
        MenuItem aboutItem = new MenuItem("关于(A)");
        aboutItem.setMnemonicParsing(true);
        aboutItem.setOnAction(e -> {
            Alert alert = new Alert(Alert.AlertType.INFORMATION);
            alert.setTitle("关于");
            alert.setHeaderText("文本编辑器");
            alert.setContentText("这是一个简单的文本编辑器示例,用于演示JavaFX菜单功能。");
            alert.showAndWait();
        });
        
        helpMenu.getItems().add(aboutItem);
        
        // 将所有菜单添加到菜单栏
        menuBar.getMenus().addAll(fileMenu, editMenu, helpMenu);
        
        // 将菜单栏添加到主面板顶部
        root.setTop(menuBar);
        
        // 创建场景并显示
        Scene scene = new Scene(root, 600, 400);
        primaryStage.setTitle("菜单示例");
        primaryStage.setScene(scene);
        primaryStage.show();
    }
    
    public static void main(String[] args) {
        launch(args);
    }
}

15.2.10 FileChooser 类

FileChooser文件选择对话框,用于让用户选择文件或目录,常用于打开文件、保存文件等操作。

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.VBox;
import javafx.stage.FileChooser;
import javafx.stage.Stage;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;

public class FileChooserDemo extends Application {
    
    private Stage primaryStage;
    private TextArea textArea;
    private String currentFilePath = null; // 当前打开的文件路径
    
    @Override
    public void start(Stage primaryStage) {
        this.primaryStage = primaryStage;
        
        // 创建文本区域
        textArea = new TextArea();
        textArea.setPrefRowCount(15);
        
        // 创建按钮
        Button openButton = new Button("打开文件");
        openButton.setOnAction(e -> openFile());
        
        Button saveButton = new Button("保存文件");
        saveButton.setOnAction(e -> saveFile());
        
        Button saveAsButton = new Button("另存为...");
        saveAsButton.setOnAction(e -> saveFileAs());
        
        // 创建状态标签
        Label statusLabel = new Label("就绪");
        
        // 创建布局并添加控件
        VBox root = new VBox(10);
        root.getChildren().addAll(
            new Label("简易文本编辑器"),
            new HBox(10, openButton, saveButton, saveAsButton),
            textArea,
            statusLabel
        );
        root.setStyle("-fx-padding: 20;");
        
        // 创建场景并显示
        Scene scene = new Scene(root, 600, 500);
        primaryStage.setTitle("FileChooser示例");
        primaryStage.setScene(scene);
        primaryStage.show();
    }
    
    // 打开文件
    private void openFile() {
        FileChooser fileChooser = new FileChooser();
        
        // 设置标题
        fileChooser.setTitle("打开文本文件");
        
        // 设置初始目录
        fileChooser.setInitialDirectory(new File(System.getProperty("user.home")));
        
        // 设置文件过滤器
        fileChooser.getExtensionFilters().addAll(
            new FileChooser.ExtensionFilter("文本文件", "*.txt"),
            new FileChooser.ExtensionFilter("Java文件", "*.java"),
            new FileChooser.ExtensionFilter("所有文件", "*.*")
        );
        
        // 显示打开文件对话框
        File file = fileChooser.showOpenDialog(primaryStage);
        
        if (file != null) {
            try {
                // 读取文件内容
                byte[] bytes = Files.readAllBytes(Paths.get(file.getPath()));
                String content = new String(bytes, StandardCharsets.UTF_8);
                textArea.setText(content);
                currentFilePath = file.getPath();
                primaryStage.setTitle("FileChooser示例 - " + file.getName());
            } catch (IOException e) {
                showAlert("错误", "无法打开文件", e.getMessage());
            }
        }
    }
    
    // 保存文件
    private void saveFile() {
        if (currentFilePath != null) {
            saveToFile(new File(currentFilePath));
        } else {
            saveFileAs();
        }
    }
    
    // 另存为
    private void saveFileAs() {
        FileChooser fileChooser = new FileChooser();
        
        // 设置标题
        fileChooser.setTitle("保存文本文件");
        
        // 设置初始目录
        fileChooser.setInitialDirectory(new File(System.getProperty("user.home")));
        
        // 设置建议的文件名
        fileChooser.setInitialFileName("untitled.txt");
        
        // 设置文件过滤器
        fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("文本文件", "*.txt"));
        
        // 显示保存文件对话框
        File file = fileChooser.showSaveDialog(primaryStage);
        
        if (file != null) {
            // 如果文件没有.txt扩展名,则添加
            if (!file.getPath().endsWith(".txt")) {
                file = new File(file.getPath() + ".txt");
            }
            saveToFile(file);
        }
    }
    
    // 保存内容到文件
    private void saveToFile(File file) {
        try {
            // 写入文件内容
            Files.write(Paths.get(file.getPath()), 
                       textArea.getText().getBytes(StandardCharsets.UTF_8));
            currentFilePath = file.getPath();
            primaryStage.setTitle("FileChooser示例 - " + file.getName());
            showAlert("成功", "文件保存成功", "文件已保存到:" + file.getPath());
        } catch (IOException e) {
            showAlert("错误", "无法保存文件", e.getMessage());
        }
    }
    
    // 显示对话框
    private void showAlert(String title, String header, String content) {
        Alert alert = new Alert(Alert.AlertType.INFORMATION);
        alert.setTitle(title);
        alert.setHeaderText(header);
        alert.setContentText(content);
        alert.showAndWait();
    }
    
    public static void main(String[] args) {
        launch(args);
    }
}

15.3 音频和视频

JavaFX 提供了对音频和视频的支持,可以轻松地在应用程序中播放音频和视频文件。

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import javafx.scene.media.MediaView;
import javafx.stage.FileChooser;
import javafx.stage.Stage;

import java.io.File;

public class MediaDemo extends Application {
    
    private MediaPlayer audioPlayer;
    private MediaPlayer videoPlayer;
    
    @Override
    public void start(Stage primaryStage) {
        // 创建音频控制区域
        Label audioLabel = new Label("音频播放:");
        Button audioOpenButton = new Button("选择音频文件");
        Button audioPlayButton = new Button("播放");
        Button audioPauseButton = new Button("暂停");
        Button audioStopButton = new Button("停止");
        
        // 禁用音频控制按钮,直到选择了文件
        audioPlayButton.setDisable(true);
        audioPauseButton.setDisable(true);
        audioStopButton.setDisable(true);
        
        // 音频按钮事件
        audioOpenButton.setOnAction(e -> {
            // 停止当前播放的音频
            if (audioPlayer != null) {
                audioPlayer.stop();
            }
            
            // 选择音频文件
            FileChooser fileChooser = new FileChooser();
            fileChooser.setTitle("选择音频文件");
            fileChooser.getExtensionFilters().addAll(
                new FileChooser.ExtensionFilter("音频文件", "*.mp3", "*.wav", "*.aac")
            );
            
            File file = fileChooser.showOpenDialog(primaryStage);
            if (file != null) {
                try {
                    Media media = new Media(file.toURI().toString());
                    audioPlayer = new MediaPlayer(media);
                    
                    // 启用控制按钮
                    audioPlayButton.setDisable(false);
                    audioPauseButton.setDisable(false);
                    audioStopButton.setDisable(false);
                    
                    audioLabel.setText("正在准备播放:" + file.getName());
                } catch (Exception ex) {
                    audioLabel.setText("无法打开音频文件:" + ex.getMessage());
                }
            }
        });
        
        audioPlayButton.setOnAction(e -> {
            if (audioPlayer != null) {
                audioPlayer.play();
                audioLabel.setText("正在播放音频...");
            }
        });
        
        audioPauseButton.setOnAction(e -> {
            if (audioPlayer != null) {
                audioPlayer.pause();
                audioLabel.setText("音频已暂停");
            }
        });
        
        audioStopButton.setOnAction(e -> {
            if (audioPlayer != null) {
                audioPlayer.stop();
                audioLabel.setText("音频已停止");
            }
        });
        
        // 创建视频控制区域
        Label videoLabel = new Label("\n视频播放:");
        Button videoOpenButton = new Button("选择视频文件");
        Button videoPlayButton = new Button("播放");
        Button videoPauseButton = new Button("暂停");
        Button videoStopButton = new Button("停止");
        
        // 禁用视频控制按钮,直到选择了文件
        videoPlayButton.setDisable(true);
        videoPauseButton.setDisable(true);
        videoStopButton.setDisable(true);
        
        // 创建视频视图
        MediaView mediaView = new MediaView();
        mediaView.setFitWidth(400);
        mediaView.setFitHeight(300);
        mediaView.setPreserveRatio(true);
        
        // 视频按钮事件
        videoOpenButton.setOnAction(e -> {
            // 停止当前播放的视频
            if (videoPlayer != null) {
                videoPlayer.stop();
            }
            
            // 选择视频文件
            FileChooser fileChooser = new FileChooser();
            fileChooser.setTitle("选择视频文件");
            fileChooser.getExtensionFilters().addAll(
                new FileChooser.ExtensionFilter("视频文件", "*.mp4", "*.mov", "*.avi")
            );
            
            File file = fileChooser.showOpenDialog(primaryStage);
            if (file != null) {
                try {
                    Media media = new Media(file.toURI().toString());
                    videoPlayer = new MediaPlayer(media);
                    mediaView.setMediaPlayer(videoPlayer);
                    
                    // 视频结束时的事件
                    videoPlayer.setOnEndOfMedia(() -> {
                        videoLabel.setText("视频播放结束");
                    });
                    
                    // 启用控制按钮
                    videoPlayButton.setDisable(false);
                    videoPauseButton.setDisable(false);
                    videoStopButton.setDisable(false);
                    
                    videoLabel.setText("正在准备播放:" + file.getName());
                } catch (Exception ex) {
                    videoLabel.setText("无法打开视频文件:" + ex.getMessage());
                }
            }
        });
        
        videoPlayButton.setOnAction(e -> {
            if (videoPlayer != null) {
                videoPlayer.play();
                videoLabel.setText("正在播放视频...");
            }
        });
        
        videoPauseButton.setOnAction(e -> {
            if (videoPlayer != null) {
                videoPlayer.pause();
                videoLabel.setText("视频已暂停");
            }
        });
        
        videoStopButton.setOnAction(e -> {
            if (videoPlayer != null) {
                videoPlayer.stop();
                videoLabel.setText("视频已停止");
            }
        });
        
        // 创建布局并添加控件
        VBox root = new VBox(10);
        root.getChildren().addAll(
            new Label("音频和视频播放示例"),
            // 音频部分
            audioLabel,
            new HBox(10, audioOpenButton, audioPlayButton, audioPauseButton, audioStopButton),
            // 视频部分
            videoLabel,
            new HBox(10, videoOpenButton, videoPlayButton, videoPauseButton, videoStopButton),
            mediaView
        );
        root.setStyle("-fx-padding: 20;");
        
        // 创建场景并显示
        Scene scene = new Scene(root, 500, 500);
        primaryStage.setTitle("音频和视频示例");
        primaryStage.setScene(scene);
        primaryStage.show();
    }
    
    public static void main(String[] args) {
        launch(args);
    }
}

15.4 动画

JavaFX 提供了强大的动画支持,可以创建各种动画效果,如过渡、淡入淡出、移动、缩放、旋转等。

15.4.1 过渡动画

过渡动画是指在一段时间内,将控件的属性从一个值平滑地过渡到另一个值

import javafx.animation.FadeTransition;
import javafx.animation.ScaleTransition;
import javafx.animation.TranslateTransition;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;

public class TransitionAnimationDemo extends Application {
    
    @Override
    public void start(Stage primaryStage) {
        // 创建一个矩形作为动画对象
        Rectangle rect = new Rectangle(100, 100, Color.BLUE);
        
        // 创建按钮
        Button fadeButton = new Button("淡入淡出");
        Button moveButton = new Button("移动");
        Button scaleButton = new Button("缩放");
        
        // 淡入淡出动画
        fadeButton.setOnAction(e -> {
            FadeTransition fade = new FadeTransition(Duration.seconds(2), rect);
            fade.setFromValue(1.0); // 开始时完全不透明
            fade.setToValue(0.1);   // 结束时几乎透明
            fade.setCycleCount(2);  // 循环2次
            fade.setAutoReverse(true); // 自动反转
            fade.play();
        });
        
        // 移动动画
        moveButton.setOnAction(e -> {
            TranslateTransition translate = new TranslateTransition(Duration.seconds(2), rect);
            translate.setFromX(0);  // 开始X坐标
            translate.setToX(200);  // 结束X坐标
            translate.setCycleCount(2);
            translate.setAutoReverse(true);
            translate.play();
        });
        
        // 缩放动画
        scaleButton.setOnAction(e -> {
            ScaleTransition scale = new ScaleTransition(Duration.seconds(2), rect);
            scale.setFromX(1);  // 开始X方向缩放比例
            scale.setFromY(1);  // 开始Y方向缩放比例
            scale.setToX(1.5);  // 结束X方向缩放比例
            scale.setToY(1.5);  // 结束Y方向缩放比例
            scale.setCycleCount(2);
            scale.setAutoReverse(true);
            scale.play();
        });
        
        // 创建布局并添加控件
        VBox root = new VBox(20);
        HBox buttons = new HBox(10, fadeButton, moveButton, scaleButton);
        root.getChildren().addAll(buttons, rect);
        root.setStyle("-fx-padding: 20; -fx-alignment: center;");
        
        // 创建场景并显示
        Scene scene = new Scene(root, 400, 300);
        primaryStage.setTitle("过渡动画示例");
        primaryStage.setScene(scene);
        primaryStage.show();
    }
    
    public static void main(String[] args) {
        launch(args);
    }
}

15.4.2 淡出效果

淡出效果是一种常见的动画效果,使控件逐渐变得透明

import javafx.animation.FadeTransition;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Duration;

public class FadeOutDemo extends Application {
    
    @Override
    public void start(Stage primaryStage) {
        // 创建一个图片视图(如果有图片的话)
        ImageView imageView = new ImageView();
        // 尝试加载Java图标(如果没有图片,会显示空白)
        try {
            Image image = new Image("https://upload.wikimedia.org/wikipedia/en/thumb/3/30/Java_programming_language_logo.svg/1200px-Java_programming_language_logo.svg.png");
            imageView.setImage(image);
            imageView.setFitWidth(200);
            imageView.setFitHeight(200);
            imageView.setPreserveRatio(true);
        } catch (Exception e) {
            // 如果图片加载失败,使用一个矩形替代
            javafx.scene.shape.Rectangle rect = new Rectangle(200, 200, javafx.scene.paint.Color.RED);
            imageView = new ImageView(); // 清空图片视图
            // 这里只是为了演示,实际中可以直接使用矩形
        }
        
        // 创建按钮
        Button fadeOutButton = new Button("淡出效果");
        Button fadeInButton = new Button("淡入效果");
        Button fadeToggleButton = new Button("淡入淡出切换");
        
        // 淡出效果
        fadeOutButton.setOnAction(e -> {
            FadeTransition fade = new FadeTransition(Duration.seconds(2), imageView);
            fade.setFromValue(1.0);
            fade.setToValue(0.0);
            fade.play();
        });
        
        // 淡入效果
        fadeInButton.setOnAction(e -> {
            FadeTransition fade = new FadeTransition(Duration.seconds(2), imageView);
            fade.setFromValue(imageView.getOpacity());
            fade.setToValue(1.0);
            fade.play();
        });
        
        // 淡入淡出切换
        fadeToggleButton.setOnAction(e -> {
            FadeTransition fade = new FadeTransition(Duration.seconds(2), imageView);
            if (imageView.getOpacity() > 0.5) {
                fade.setFromValue(1.0);
                fade.setToValue(0.0);
            } else {
                fade.setFromValue(0.0);
                fade.setToValue(1.0);
            }
            fade.play();
        });
        
        // 创建布局并添加控件
        VBox root = new VBox(20);
        root.getChildren().addAll(fadeOutButton, fadeInButton, fadeToggleButton, imageView);
        root.setStyle("-fx-padding: 20; -fx-alignment: center;");
        
        // 创建场景并显示
        Scene scene = new Scene(root, 300, 350);
        primaryStage.setTitle("淡出效果示例");
        primaryStage.setScene(scene);
        primaryStage.show();
    }
    
    public static void main(String[] args) {
        launch(args);
    }
}

15.4.3 移动效果

移动效果使控件在界面上按照指定的路径移动。

import javafx.animation.PathTransition;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Shape;
import javafx.stage.Stage;
import javafx.util.Duration;

public class MoveEffectDemo extends Application {
    
    @Override
    public void start(Stage primaryStage) {
        // 创建一个面板作为容器
        Pane pane = new Pane();
        pane.setPrefSize(400, 300);
        pane.setStyle("-fx-background-color: #f0f0f0;");
        
        // 创建一个圆形作为移动的对象
        Circle circle = new Circle(20, Color.RED);
        
        // 添加圆形到面板
        pane.getChildren().add(circle);
        
        // 创建按钮
        Button linearMoveButton = new Button("直线移动");
        Button pathMoveButton = new Button("沿路径移动");
        Button resetButton = new Button("重置位置");
        
        // 直线移动
        linearMoveButton.setOnAction(e -> {
            // 停止任何正在进行的动画
            stopAllAnimations(circle);
            
            // 创建平移过渡
            javafx.animation.TranslateTransition translate = 
                new javafx.animation.TranslateTransition(Duration.seconds(3), circle);
            translate.setFromX(0);
            translate.setFromY(0);
            translate.setToX(300);
            translate.setToY(200);
            translate.play();
        });
        
        // 沿路径移动
        pathMoveButton.setOnAction(e -> {
            // 停止任何正在进行的动画
            stopAllAnimations(circle);
            
            // 重置位置
            circle.setTranslateX(0);
            circle.setTranslateY(0);
            circle.setX(20);
            circle.setY(20);
            
            // 创建一个路径(三角形)
            Shape path = new javafx.scene.shape.Path(
                new javafx.scene.shape.MoveTo(20, 20),
                new javafx.scene.shape.LineTo(300, 20),
                new javafx.scene.shape.LineTo(160, 200),
                new javafx.scene.shape.ClosePath()
            );
            path.setStroke(Color.LIGHTGRAY);
            path.setFill(null);
            pane.getChildren().add(path); // 添加路径到面板,仅用于可视化
            
            // 创建路径过渡
            PathTransition pathTransition = new PathTransition();
            pathTransition.setDuration(Duration.seconds(5));
            pathTransition.setPath(path);
            pathTransition.setNode(circle);
            pathTransition.setOrientation(PathTransition.OrientationType.ORTHOGONAL_TO_TANGENT);
            pathTransition.setCycleCount(2);
            pathTransition.setAutoReverse(false);
            
            // 动画结束后移除路径
            pathTransition.setOnFinished(event -> pane.getChildren().remove(path));
            
            pathTransition.play();
        });
        
        // 重置位置
        resetButton.setOnAction(e -> {
            stopAllAnimations(circle);
            circle.setTranslateX(0);
            circle.setTranslateY(0);
            circle.setX(20);
            circle.setY(20);
        });
        
        // 创建布局并添加控件
        HBox buttons = new HBox(10, linearMoveButton, pathMoveButton, resetButton);
        VBox root = new VBox(10, buttons, pane);
        root.setStyle("-fx-padding: 10;");
        
        // 创建场景并显示
        Scene scene = new Scene(root, 420, 350);
        primaryStage.setTitle("移动效果示例");
        primaryStage.setScene(scene);
        primaryStage.show();
    }
    
    // 停止节点上的所有动画
    private void stopAllAnimations(javafx.scene.Node node) {
        for (javafx.animation.Animation animation : javafx.animation.Animation.getAnimations()) {
            if (animation.getTargetNodes().contains(node)) {
                animation.stop();
            }
        }
    }
    
    public static void main(String[] args) {
        launch(args);
    }
}

        通过本章学习,可掌握 Java 桌面应用的核心交互能力,从简单按钮响应到复杂动画效果,为构建功能完整、体验流畅的 GUI 程序奠定基础。后续可结合布局管理器(如GridPaneBorderPane)和样式表(CSS)进一步优化界面设计。


网站公告

今日签到

点亮在社区的每一天
去签到