上次使用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青年” 发送 “视频转码工具” 获取转码工具安装包。
关注公众号,阅读更多文章。