JavaFX桌面應用-loading介面
上次使用JavaFX開發了一個視訊轉碼工具,當用戶點選“啟動”按鈕開始轉碼的時候,會禁用啟動按鈕,防止多次啟動轉碼。
這種處理方式對使用者來說可能並是很友好,其實可以在啟動轉碼的時彈出一個loading介面,告訴使用者正在進行視訊轉碼。
~ JavaFX桌面應用開發系列文章傳送門 ~
- JavaFX桌面應用開發-HelloWorld
- JavaFX佈局神器-SceneBuilder
- JavaFX讓UI更美觀-CSS樣式
- JavaFX桌面應用-為什麼應用老是“未響應”
- JavaFX桌面應用-MVC模式開發,“真香”
- JavaFX桌面應用-loading介面 (本文)
- JavaFX桌面應用-表格用法
- JavaFX桌面應用-視訊轉碼工具
重新改造一下之前的轉碼程式,使用loading介面提示使用者視訊正在轉碼,如下圖:
針對這種通用的loading介面,可以使用JavaFX的stage開發一個通用的元件。
這裡需要注意的是:
- loading介面沒有邊框的
- loading介面背景是透明的
- 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青年” 傳送 “視訊轉碼工具” 獲取轉碼工具安裝包。
關注公眾號,閱讀更多文章。