我创建了一个忙碌的图层,其中显示了后台IO忙碌时的动画进度指示器。将该层添加到Gluon mobile 4的玻璃窗格中:
BusyLayer extends Layer { ...
root = new FlowPane(new ProgressIndicator());
MobileApplication.getInstance().getGlassPane().getLayers().add(this);
DH2FX extends MobileApplication { ...
addLayerFactory("Busy", () -> new BusyLayer());
...
showLayer("Busy");
...
hideLayer("Busy");
在Gluon 5中,getLayers已被删除,并且根据迁移指南的层可以直接显示:
BusyLayer extends Layer { ...
root = new FlowPane(new ProgressIndicator());
DH2FX extends MobileApplication { ...
BusyLayer busyLayer = new BusyLayer();
...
busyLayer.show();
...
busyLayer.hide();
但是该层未被隐藏。
====
主要参与者是单例背景类,因此BusyLayer仅显示一次:
class BackgroundActivity {
private final AtomicInteger busyAtomicInteger = new AtomicInteger(0);
BusyLayer busyLayer = new BusyLayer();
private long time;
public BackgroundActivity() {
busyLayer.setOnShowing(e -> {
time = System.currentTimeMillis();
System.out.println("Showing busyLayer");
});
busyLayer.setOnShown(e -> {
System.out.println("busyLayer shown in: " + (System.currentTimeMillis() - time) + " ms");
});
busyLayer.setOnHiding(e -> System.out.println("hiding layer at " + (System.currentTimeMillis() - time) + " ms"));
}
void start() {
if (busyAtomicInteger.getAndIncrement() == 0) {
busyLayer.show();
}
}
void done() {
if (busyAtomicInteger.decrementAndGet() == 0) {
busyLayer.hide();
}
}
void failure(Throwable t) {
t.printStackTrace();
failure();
}
void failure() {
done();
}
}
protected final BackgroundActivity backgroundActivity = new BackgroundActivity();
使用CompletableFutures这样的代码执行异步任务:
// Hours
backgroundActivity.start();
CompletableFuture.supplyAsync( () -> entryService().getHours(calendarPickerForHessian))
.exceptionally( e -> { backgroundActivity.failure(e); return null; } )
.thenAcceptAsync( (Hour[] hours) -> {
Platform.runLater( () -> {
refreshHours(hours);
backgroundActivity.done();
});
});
// ProjectTotals
backgroundActivity.start();
CompletableFuture.supplyAsync( () -> entryService().getProjectTotals(calendarPickerForHessian) )
.exceptionally( e -> { backgroundActivity.failure(e); return null; } )
.thenAcceptAsync( (LinkedHashMap<Integer, Double> projectTotals) -> {
Platform.runLater( () -> {
refreshProjectTotals(projectTotals);
backgroundActivity.done();
});
});
// DayTotals
backgroundActivity.start();
CompletableFuture.supplyAsync( () -> entryService().getDayTotals(calendarPickerForHessian))
.exceptionally( e -> { backgroundActivity.failure(e); return null; } )
.thenAcceptAsync( (SortedMap<String, Double> dayTotals) -> {
Platform.runLater( () -> {
refreshDayTotals(dayTotals);
backgroundActivity.done();
});
});
当然还有BusyLayer本身:
public class BusyLayer extends Layer {
public BusyLayer() {
root = new StackPane(new ProgressIndicator());
root.setAlignment(Pos.CENTER);
root.getStyleClass().add("semitransparent7");
getChildren().add(root);
}
private final StackPane root;
@Override
public void layoutChildren() {
root.setVisible(isShowing());
if (!isShowing()) {
return;
}
GlassPane glassPane = MobileApplication.getInstance().getGlassPane();
root.resize(glassPane.getWidth(), glassPane.getHeight());
resizeRelocate(0, 0, glassPane.getWidth(), glassPane.getHeight());
}
}
答案 0 :(得分:1)
当您尝试过早隐藏层 时,Charm 5.0上存在一个已知问题。
显示图层时,需要花费一些时间来进行渲染布局,即使没有动画过渡,显示图层的时间与最终显示的时间之间也要间隔几毫秒。
如果在显示图层之前调用Layer::hide
,则该呼叫将退出,该图层不会被隐藏。
一个简单的测试如下:
private long time;
BusyLayer busyLayer = new BusyLayer();
busyLayer.setOnShowing(e -> {
time = System.currentTimeMillis();
System.out.println("Showing busyLayer");
});
busyLayer.setOnShown(e -> {
System.out.println("busyLayer shown in: " + (System.currentTimeMillis() - time) + " ms");
});
busyLayer.setOnHiding(e -> System.out.println("hiding layer at " + (System.currentTimeMillis() - time) + " ms"));
busyLayer.show();
现在假设您有一个耗时一秒的漫长任务:
PauseTransition p = new PauseTransition(Duration.seconds(1));
p.setOnFinished(f -> busyLayer.hide());
p.play();
然后该图层将按预期隐藏。
但是如果任务要快得多并且需要几毫秒的时间:
PauseTransition p = new PauseTransition(Duration.seconds(0.01));
p.setOnFinished(f -> busyLayer.hide());
p.play();
该图层可能尚未显示,并且hide()
调用将失败。
解决方法
虽然此问题已正确解决,但可能的解决方法是侦听Layer的LifecycleEvent.SHOWN
事件,并执行类似的操作:
private BooleanProperty shown = new SimpleBooleanProperty();
BusyLayer busyLayer = new BusyLayer();
busyLayer.setOnShowing(e -> shown.set(false));
busyLayer.setOnShown(e -> shown.set(true));
busyLayer.show();
PauseTransition p = new PauseTransition(taskDuration);
p.setOnFinished(f -> {
if (shown.get()) {
// layer was shown, hide it
busyLayer.hide();
} else {
// layer is not shown yet, wait until it does, and hide
shown.addListener(new InvalidationListener() {
@Override
public void invalidated(Observable observable) {
if (shown.get()) {
busyLayer.hide();
shown.removeListener(this);
}
}
});
}
});
p.play();
修改
我要添加一个可能的BusyLayer
实现:
class BusyLayer extends Layer {
private final GlassPane glassPane = MobileApplication.getInstance().getGlassPane();
private final StackPane root;
private final double size = 150;
public BusyLayer() {
root = new StackPane(new ProgressIndicator());
root.setStyle("-fx-background-color: white;");
getChildren().add(root);
setBackgroundFade(0.5);
}
@Override
public void layoutChildren() {
super.layoutChildren();
root.setVisible(isShowing());
if (!isShowing()) {
return;
}
root.resize(size, size);
resizeRelocate((glassPane.getWidth() - size)/2, (glassPane.getHeight()- size)/2, size, size);
}
}
编辑
主要问题与BusyLayer
如何覆盖Layer::layoutChildren
方法有关。
您可以阅读Layer::layoutChildren
的{{3}}读物:
重写此方法可为您的图层添加布局逻辑。为了使Layer正常运行,应小心在重写的方法中调用此方法。
这意味着您必须调用super.layoutChildren()
才能使图层正常工作。
@Override
public void layoutChildren() {
super.layoutChildren();
// add your own implementation
}
这是扩展JavaFX内置控件时的常用模式。