上次使用JavaFX开发了一个视频转码工具,当用户点击“启动”按钮开始转码的时候,会禁用启动按钮,防止多次启动转码。
这种处理方式对用户来说可能并是很友好,其实可以在启动转码的时弹出一个loading界面,告诉用户正在进行视频转码。
重新改造一下之前的转码程序,使用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):
// messageLabel adLbl = new Label(ad);adLbl.setTextFill(Color.BLUE);// progressProgressIndicator indicator = new ProgressIndicator();indicator.setProgress(-1);indicator.progressProperty().bind(work.progressProperty());// packVBox 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 parentdouble 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 { private String ffmpeg; private List modelList; private Consumer consumer; public VideoConvertWork(String ffmpeg, List modelList, Consumer consumer) { this.ffmpeg = ffmpeg; this.modelList = modelList; this.consumer = consumer; } @Override protected Void call() throws Exception { while (true) { Optional 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方法中执行即可。
推荐阅读:
JavaFX桌面应用开发-HelloWorld JavaFX布局神器-SceneBuilder JavaFX让UI更美观-CSS样式 JavaFX桌面应用-为什么应用老是“未响应” JavaFX桌面应用-MVC模式开发,“真香” JavaFX桌面应用-视频转码工具