JavaFX 实现 Loading 效果的组件与案例详解

JavaFX 中的 Loading 组件概述

JavaFX 提供了两个用于显示任务进度的核心组件:

  1. ProgressIndicator:以圆形动画的形式展示任务进度,适用于未定义进度的任务。
  2. ProgressBar:以水平条的形式展示任务进度,适用于可以量化进度的任务。

这两个组件可直接绑定到后台任务的 progress 属性,实时更新界面。

1. ProgressBar 的常见方法

ProgressBar 是以 水平条 的形式显示任务进度,适用于可量化的任务。

1.1 构造方法

  • ProgressBar()
    创建一个初始进度为 0 的进度条。
  • ProgressBar(double progress)
    创建一个指定初始进度的进度条。
    • 参数 progress:范围为 [0.0, 1.0],表示百分比进度。

1.2 常用方法

方法名 说明 示例
setProgress(double value) 设置进度条的进度值。value 范围为 [0.0, 1.0],-1 表示不确定的进度(类似于加载动画)。 progressBar.setProgress(0.5);
getProgress() 获取当前进度条的进度值。 double progress = progressBar.getProgress();
progressProperty() 返回一个 DoubleProperty,可以用于绑定任务的进度属性。 progressBar.progressProperty().bind(task.progressProperty());

1.3 样式方法

方法名 说明 示例
setStyle(String style) 设置进度条的 CSS 样式。可以自定义进度条的外观。 progressBar.setStyle("-fx-accent: green;");

1.4 使用示例

ProgressBar progressBar = new ProgressBar();
progressBar.setProgress(0.5); // 设置进度为50%
progressBar.setStyle("-fx-accent: blue;"); // 将进度条颜色设置为蓝色

2. ProgressIndicator 的常见方法

ProgressIndicator圆形动画 的形式显示任务进度,适用于未定义具体进度的任务(如等待加载)。

2.1 构造方法

  • ProgressIndicator()
    创建一个初始进度为 不确定状态 的指示器。
  • ProgressIndicator(double progress)
    创建一个指定初始进度的指示器。

2.2 常用方法

方法名 说明 示例
setProgress(double value) 设置指示器的进度值,value 范围为 [0.0, 1.0],-1 表示不确定的进度状态(默认值)。 progressIndicator.setProgress(-1);
getProgress() 获取当前指示器的进度值。 double progress = progressIndicator.getProgress();
progressProperty() 返回一个 DoubleProperty,可以用于绑定任务的进度属性。 progressIndicator.progressProperty().bind(task.progressProperty());

2.3 样式方法

方法名 说明 示例
setStyle(String style) 设置指示器的 CSS 样式。可以自定义指示器的外观。 progressIndicator.setStyle("-fx-accent: red;");

2.4 使用示例

ProgressIndicator progressIndicator = new ProgressIndicator();
progressIndicator.setProgress(-1); // 设置为不确定状态
progressIndicator.setStyle("-fx-accent: green;"); // 设置指示器颜色为绿色

3. 常见方法对比

功能 ProgressBar ProgressIndicator
表示形式 水平条形进度 圆形指示器
初始进度 默认 0.0 默认 -1(不确定进度)
绑定支持 支持绑定任务的 progressProperty() 支持绑定任务的 progressProperty()
适用场景 可量化的任务进度 未量化的任务(如加载等待)
进度状态 进度条会逐步增长 圆形动画逐步覆盖

4. 常见问题及解决方案

4.1 界面卡顿问题

问题原因:
在 JavaFX 中,所有 UI 更新都运行在单线程模式下。如果后台任务运行在 UI 线程,会导致界面卡顿。

解决方案:

  • 使用 Task 将后台任务与 UI 线程分离。
  • 确保耗时操作(如数据库查询)在 Taskcall 方法中执行。

4.2 动画流畅性问题

问题原因:
任务更新过于频繁,导致 UI 线程处理不过来。

解决方案:

  • 限制更新频率,例如每 10% 更新一次进度:
if (i % (NUM_TASKS / 10) == 0) {
    updateProgress(i, NUM_TASKS);
}

4.3 异常处理问题

问题原因:
任务执行时可能抛出异常,导致应用崩溃。

解决方案:

  • 在任务的 setOnFailed 方法中处理异常:
task.setOnFailed(event -> {
    Throwable exception = task.getException();
    System.err.println("Task failed: " + exception.getMessage());
});

4.4 停止任务

问题原因:
用户可能需要取消任务,但未正确处理会导致资源泄漏。

解决方案:

  • 使用 Taskcancel 方法并处理中断:
if (isCancelled()) {
    updateMessage("Task cancelled.");
    break;
}

5.完整案例示范

Maven 依赖

确保 JavaFX 的依赖正确引入(适配 JavaFX 21 和 JDK 8):


    
        org.openjfx
        javafx-controls
        21
    
    
        org.openjfx
        javafx-base
        21
    

核心代码

import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ReloadableLoadingExample extends Application {

    private final ExecutorService threadPool = Executors.newFixedThreadPool(5);

    @Override
    public void start(Stage primaryStage) {
        // 主界面组件
        ProgressBar progressBar = new ProgressBar(0);
        ProgressIndicator progressIndicator = new ProgressIndicator(0);
        Button startButton = new Button("Start Loading");
        Label statusLabel = new Label("Click 'Start Loading' to begin.");
        VBox content = new VBox(10, startButton, statusLabel, progressBar, progressIndicator);
        content.setStyle("-fx-padding: 20; -fx-alignment: center;");

        // 隐藏进度条和指示器
        progressBar.setVisible(false);
        progressIndicator.setVisible(false);

        // 遮罩层
        StackPane overlay = createOverlay(content);
        overlay.setVisible(false); // 默认隐藏遮罩层

        // 主界面
        StackPane root = new StackPane(content, overlay);
        Scene scene = new Scene(root, 400, 250);

        // 启动任务按钮
        startButton.setOnAction(event -> {
            // 禁用按钮,显示遮罩和进度条
            startButton.setDisable(true);
            overlay.setVisible(true);
            progressBar.setVisible(true);
            progressIndicator.setVisible(true);

            // 创建任务
            Task task = createLoadingTask(statusLabel);

            // 重置进度和状态
            progressBar.progressProperty().unbind();
            progressIndicator.progressProperty().unbind();
            progressBar.setProgress(0);
            progressIndicator.setProgress(0);

            // 绑定任务进度
            progressBar.progressProperty().bind(task.progressProperty());
            progressIndicator.progressProperty().bind(task.progressProperty());

            // 提交任务到线程池
            threadPool.submit(task);

            // 任务完成时的处理
            task.setOnSucceeded(e -> {
                overlay.setVisible(false);
                startButton.setDisable(false);
                progressBar.setVisible(false);
                progressIndicator.setVisible(false);
                showAlert(Alert.AlertType.INFORMATION, "Task Completed", "The task completed successfully!");
            });

            // 任务失败时的处理
            task.setOnFailed(e -> {
                overlay.setVisible(false);
                startButton.setDisable(false);
                progressBar.setVisible(false);
                progressIndicator.setVisible(false);
                showAlert(Alert.AlertType.ERROR, "Task Failed", "An error occurred: " + task.getException().getMessage());
            });

            // 任务取消时的处理
            task.setOnCancelled(e -> {
                overlay.setVisible(false);
                startButton.setDisable(false);
                progressBar.setVisible(false);
                progressIndicator.setVisible(false);
                showAlert(Alert.AlertType.WARNING, "Task Cancelled", "The task was cancelled.");
            });
        });

        primaryStage.setScene(scene);
        primaryStage.setTitle("Reloadable JavaFX Loading Example");
        primaryStage.show();
    }

    /**
     * 创建任务
     */
    private Task createLoadingTask(Label statusLabel) {
        return new Task() {
            private static final int TOTAL_WORK = 100;

            @Override
            protected Void call() throws Exception {
                for (int i = 1; i <= TOTAL_WORK; i++) {
                    if (isCancelled()) {
                        updateMessage("Task cancelled.");
                        break;
                    }
                    // 模拟任务执行
                    Thread.sleep(50);

                    // 更新进度和状态
                    updateProgress(i, TOTAL_WORK);
                    updateMessage("Progress: " + i + "/" + TOTAL_WORK);
                }
                if (!isCancelled()) {
                    updateMessage("Task completed successfully!");
                }
                return null;
            }

            @Override
            protected void updateMessage(String message) {
                // 界面线程中更新状态
                javafx.application.Platform.runLater(() -> statusLabel.setText(message));
            }
        };
    }

    /**
     * 创建遮罩层
     */
    private StackPane createOverlay(Region parent) {
        Rectangle overlayRectangle = new Rectangle();
        overlayRectangle.setFill(Color.rgb(0, 0, 0, 0.3));
        overlayRectangle.widthProperty().bind(parent.widthProperty());
        overlayRectangle.heightProperty().bind(parent.heightProperty());

        Label loadingLabel = new Label("Loading...");
        loadingLabel.setStyle("-fx-font-size: 16px; -fx-text-fill: white;");

        StackPane overlay = new StackPane(overlayRectangle, loadingLabel);
        overlay.setAlignment(Pos.CENTER);
        return overlay;
    }

    /**
     * 显示提示信息
     */
    private void showAlert(Alert.AlertType alertType, String title, String content) {
        Alert alert = new Alert(alertType);
        alert.setTitle(title);
        alert.setContentText(content);
        alert.showAndWait();
    }

    @Override
    public void stop() {
        threadPool.shutdownNow();
    }

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

你可能感兴趣的:(java,服务器,开发语言,后端,面试,系统架构,软件工程)