JavaFX桌面應用-loading界面


上次使用JavaFX開發了一個視頻轉碼工具,當用戶點擊“啟動”按鈕開始轉碼的時候,會禁用啟動按鈕,防止多次啟動轉碼。
這種處理方式對用戶來說可能並是很友好,其實可以在啟動轉碼的時彈出一個loading界面,告訴用戶正在進行視頻轉碼。

~ JavaFX桌面應用開發系列文章傳送門 ~

  1. JavaFX桌面應用開發-HelloWorld
  2. JavaFX布局神器-SceneBuilder
  3. JavaFX讓UI更美觀-CSS樣式
  4. JavaFX桌面應用-為什么應用老是“未響應”
  5. JavaFX桌面應用-MVC模式開發,“真香”
  6. JavaFX桌面應用-loading界面 (本文)
  7. JavaFX桌面應用-表格用法
  8. JavaFX桌面應用-視頻轉碼工具

重新改造一下之前的轉碼程序,使用loading界面提示用戶視頻正在轉碼,如下圖:

針對這種通用的loading界面,可以使用JavaFX的stage開發一個通用的組件。

這里需要注意的是:

  1. loading界面沒有邊框的
  2. loading界面背景是透明的
  3. loading附着於主Stage

針對以上三點,可以分別設置loading stage的樣式及模式:

// 設置stage無任何裝飾
stage.initStyle(StageStyle.UNDECORATED);
// 設置stage背景透明
stage.initStyle(StageStyle.TRANSPARENT);
// 設置stage的模式
stage.initModality(Modality.APPLICATION_MODAL);

loading界面由兩部分組成,分別是loading動畫(ProgressIndicator)和提示信息(Label),如下圖:

所以可以采用VBox來布局(這里直接采用Java代碼布局,不采用fxml):

// message
Label adLbl = new Label(ad);
adLbl.setTextFill(Color.BLUE);

// progress
ProgressIndicator indicator = new ProgressIndicator();
indicator.setProgress(-1);
indicator.progressProperty().bind(work.progressProperty());

// pack
VBox vBox = new VBox();
vBox.setSpacing(10);
vBox.setBackground(Background.EMPTY);
vBox.getChildren().addAll(indicator, adLbl);

對於loading界面的寬度可以通過信息來計算,而loading界面的位置則設置為主stage的中心。

stage.setWidth(ad.length() * 8 + 10);
stage.setHeight(100);

// show center of parent
double x = parent.getX() + (parent.getWidth() - stage.getWidth()) / 2;
double y = parent.getY() + (parent.getHeight() - stage.getHeight()) / 2;
stage.setX(x);
stage.setY(y);

完整的loading界面代碼如下:

/**
 * @author itqn
 */
public class ProgressStage {

    private Stage stage;
    private Task<?> work;

    private ProgressStage() {
    }

    /**
     * 創建
     *
     * @param parent
     * @param work
     * @param ad
     * @return
     */
    public static ProgressStage of(Stage parent, Task<?> work, String ad) {
        ProgressStage ps = new ProgressStage();
        ps.work = Objects.requireNonNull(work);
        ps.initUI(parent, ad);
        return ps;
    }

    /**
     * 顯示
     */
    public void show() {
        new Thread(work).start();
        stage.show();
    }

    private void initUI(Stage parent, String ad) {
        stage = new Stage();
        stage.initOwner(parent);
        // style
        stage.initStyle(StageStyle.UNDECORATED);
        stage.initStyle(StageStyle.TRANSPARENT);
        stage.initModality(Modality.APPLICATION_MODAL);

        // message
        Label adLbl = new Label(ad);
        adLbl.setTextFill(Color.BLUE);

        // progress
        ProgressIndicator indicator = new ProgressIndicator();
        indicator.setProgress(-1);
        indicator.progressProperty().bind(work.progressProperty());

        // pack
        VBox vBox = new VBox();
        vBox.setSpacing(10);
        vBox.setBackground(Background.EMPTY);
        vBox.getChildren().addAll(indicator, adLbl);

        // scene
        Scene scene = new Scene(vBox);
        scene.setFill(null);
        stage.setScene(scene);
        stage.setWidth(ad.length() * 8 + 10);
        stage.setHeight(100);

        // show center of parent
        double x = parent.getX() + (parent.getWidth() - stage.getWidth()) / 2;
        double y = parent.getY() + (parent.getHeight() - stage.getHeight()) / 2;
        stage.setX(x);
        stage.setY(y);

        // close if work finish
        work.setOnSucceeded(e -> stage.close());
    }
}

loading動畫跟Task任務的進度綁定,當Task完成的時候,關閉loading界面。
這樣loading界面組件就完成了。

接下來,改造之前的視頻轉碼工具代碼,將視頻轉碼的代碼改為繼承Task,而不是Thread,這里Task不需要返回任何信息,所以泛型采用Void即可,然后重寫call方法,將耗時的業務代碼放在call中執行。

public class VideoConvertWork extends Task<Void> {

    private String ffmpeg;
    private List<TableColumnModel> modelList;
    private Consumer<String> consumer;

    public VideoConvertWork(String ffmpeg, List<TableColumnModel> modelList, Consumer<String> consumer) {
        this.ffmpeg = ffmpeg;
        this.modelList = modelList;
        this.consumer = consumer;
    }

    @Override
    protected Void call() throws Exception {
        while (true) {
            Optional<TableColumnModel> opt = modelList.stream().filter(i -> !VideoConvertHolder.has(i.getId())).findFirst();
            if (opt.isPresent()) {
                try {
                    VideoConvertHolder.add(opt.get().getId());
                    convert(opt.get());
                } catch (Exception e) {
                    e.printStackTrace();
                    Platform.runLater(() -> opt.get().setMessage(e.getMessage()));
                }
            } else {
                break;
            }
        }
        return null;
    }
}

調整“啟動”按鈕的事件處理:

public void executeConvertHandler(ActionEvent actionEvent) {
    if (model.getTableList().isEmpty()) {
        new Alert(Alert.AlertType.INFORMATION, "沒有轉碼任務,請選擇視頻進行轉碼。").show();
        return;
    }
    if (ffmpeg == null) {
        new Alert(Alert.AlertType.ERROR, "FFmpeg.exe Not Found.").show();
        return;
    }
//        ((Button) actionEvent.getSource()).setDisable(true);
//        new VideoConvertExecutor(ffmpeg, model.getTableList(), s -> Platform.runLater(() -> model.setInfo(s))).start();
    ProgressStage.of(
        App.stage,
        new VideoConvertWork(ffmpeg, model.getTableList(), s -> Platform.runLater(() -> model.setInfo(s))),
        "視頻轉碼中..."
    ).show();
}

loading界面作為通用的組件可以在任何耗時的業務場景下使用,只要將耗時的業務放在Task的call方法中執行即可。

=========================================================
關注 公眾號 “HiIT青年” 發送 “視頻轉碼工具” 獲取轉碼工具安裝包。(如果沒有收到回復,可能是你之前取消過關注。)

HiIT青年
關注公眾號,閱讀更多文章。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM