Gluon mobile 5层不隐藏
Gluon mobile 5 layer does not hide
我创建了一个繁忙层,在后台 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();
但是图层没有隐藏。
====
主要玩家是单例背景class,所以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());
}
}
当您过早尝试隐藏图层时,在 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()
调用会失败
解决方法
虽然此问题已正确修复,但可能的解决方法是监听层的 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
方法有关。
你可以阅读 here for Layer::layoutChildren
:
Override this method to add the layout logic for your layer. Care should be taken to call this method in overriden methods for proper functioning of the Layer.
这意味着您必须调用super.layoutChildren()
以使图层正常工作。
@Override
public void layoutChildren() {
super.layoutChildren();
// add your own implementation
}
这是扩展 JavaFX 内置控件时的常见模式。
我创建了一个繁忙层,在后台 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();
但是图层没有隐藏。
====
主要玩家是单例背景class,所以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());
}
}
当您过早尝试隐藏图层时,在 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()
调用会失败
解决方法
虽然此问题已正确修复,但可能的解决方法是监听层的 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
方法有关。
你可以阅读 here for Layer::layoutChildren
:
Override this method to add the layout logic for your layer. Care should be taken to call this method in overriden methods for proper functioning of the Layer.
这意味着您必须调用super.layoutChildren()
以使图层正常工作。
@Override
public void layoutChildren() {
super.layoutChildren();
// add your own implementation
}
这是扩展 JavaFX 内置控件时的常见模式。