Глюон мобильный 5 слой не скрывает - PullRequest
0 голосов
/ 01 ноября 2018

Я создал занятый слой, показывающий индикатор прогресса анимации, когда фоновый ввод-вывод занят. Слой добавлен на стекло в 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();

Но слой не скрыт.

====

Основные игроки - одноэлементный класс Background, поэтому 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());
}

}

1 Ответ

0 голосов
/ 01 ноября 2018

Существует известная проблема в 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);
    }

}

EDIT

Основная проблема связана с тем, как BusyLayer переопределяет метод Layer::layoutChildren.

Как вы можете прочитать, прочитайте здесь для Layer::layoutChildren:

Переопределите этот метод, чтобы добавить логику макета для вашего слоя. Следует позаботиться о том, чтобы вызывать этот метод в переопределенных методах для правильного функционирования Слоя.

Это означает, что вам нужно позвонить super.layoutChildren(), чтобы правильно настроить слой.

@Override
public void layoutChildren() {
    super.layoutChildren();
    // add your own implementation
}

Это обычный шаблон, когда расширенные встроенные элементы управления JavaFX.

...