JavaFX - горизонтальный текст выделения

Я пытаюсь добиться эффекта, аналогичного marquee - строке длинного (в моем случае) текста, который перемещается по горизонтали ось. Мне удалось заставить его работать, но я не могу назвать его удовлетворительным.

Мой класс Controller выглядит следующим образом:

@FXML
private Text newsFeedText;

(...)
@Override
public void initialize(URL url, ResourceBundle resourceBundle) {
    TranslateTransition transition = TranslateTransitionBuilder.create()
            .duration(new Duration(7500))
            .node(newsFeedText)
            .interpolator(Interpolator.LINEAR)
            .cycleCount(Timeline.INDEFINITE)
            .build();   

    GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
    int width = gd.getDisplayMode().getWidth();

    transition.setFromX(width);
    transition.setToX(-width);
    transition.play();
}

newsFeedText привязан к некоторому текстовому источнику, который динамически обновляется, поэтому он содержит различное количество текста.

Мой код имеет как минимум два недостатка:

  • Переход идет от -width к +width; width - ширина разрешения монитора

Будут моменты, когда текст вообще не будет виден, если окно не полноэкранное. Если текст будет длиннее и newsFeedText ширина будет больше ширины разрешения монитора, то переход исчезнет "пополам" (оставаясь на экране).

  • В настоящее время Duration не зависит от ширины newsFeedText.

Теперь это не изношено, но если переходы fromX и toX будут вычисляться динамически, то это приведет к различным скоростям бегущей строки.

Как избавиться от этих недостатков?


person Maciej Dobrowolski    schedule 14.04.2014    source источник


Ответы (2)


Мне удалось это сработать, любые пересчеты могут произойти только после остановки перехода, поэтому мы не можем установить его cycleCount в Timeline.INDEFINITE. Мое требование заключалось в том, что я мог изменить текст внутри компонента, чтобы были проводки fxml:

@FXML
private Text node; // text to marquee

@FXML
private Pane parentPane; // pane on which text is placed

Код, который работает:

transition = TranslateTransitionBuilder.create()
        .duration(new Duration(10))
        .node(node)
        .interpolator(Interpolator.LINEAR)
        .cycleCount(1)
        .build();

transition.setOnFinished(new EventHandler<ActionEvent>() {
    @Override
    public void handle(ActionEvent actionEvent) {
        rerunAnimation();
    }
});

rerunAnimation();

где rerunAnimation():

private void rerunAnimation() {
    transition.stop();
    // if needed set different text on "node"
    recalculateTransition();
    transition.playFromStart();
}

и recalculateTransition() это:

private void recalculateTransition() {
    transition.setToX(node.getBoundsInLocal().getMaxX() * -1 - 100);
    transition.setFromX(parentPane.widthProperty().get() + 100);

    double distance = parentPane.widthProperty().get() + 2 * node.getBoundsInLocal().getMaxX();
    transition.setDuration(new Duration(distance / SPEED_FACTOR));
}
person Maciej Dobrowolski    schedule 07.05.2014
comment
Рассмотрите возможность пожертвования элемента управления бегущей строки ControlsFX. - person jewelsea; 07.05.2014
comment
Он работает во всей ширине окна, но что, если мне нужен тикер внутри элемента управления? Я не могу скрыть текст, в частности - person zella; 16.05.2014
comment
@jewelsea Я думаю, что мое обходное решение слишком неэффективно, но в свободное время я обязательно улучшу его и подарю решение. - person Maciej Dobrowolski; 16.05.2014
comment
Также рассмотрите возможность размещения прямоугольной формы под выделенным текстовым объектом с размером, превышающим видимую область экрана, для достижения плавной анимации. Ваша текстовая фигура должна проходить над этим прямоугольником по всему пути. И не забудьте настроить кеширование формы. - person Almaz; 17.06.2016

Вы должны быть в состоянии сделать это, слушая widthProperty вашей сцены. Вы можете либо получить к нему доступ через newsFeedText.getScene().widthProperty(), либо получить ссылку из своего основного класса и выставить ее оттуда, либо передать ее методу или конструктору для доступа в вашем классе, который объявляет newsFeedText.

Преимущество этого подхода заключается в том, что теперь ваша логика зависит от ширины вашей сцены (динамическая зависимость), а не от ширины вашего монитора (статическая зависимость). Обратите внимание, что я не проверял этот подход, но на данный момент не вижу причин (возможно, наивных), что он не должен работать.

Что касается вашей зависимости от продолжительности, вы можете решить эту проблему, выполнив какой-то расчет на основе длины текста в newsFeedText. Что-то вроде Duration.seconds(newsFeedText.get Text().length()/denominator), где denominator — это указанное вами значение (например, 7500, как в вашем коде). Это сделает вашу продолжительность динамически вычисляемой на основе длины вашего текста.

Если вы хотите работать с шириной самого newsFeedText, а не с длиной его текста, просто замените newsFeedText.getText().length() на newsFeedText.getWidth(). Убедитесь, что вы выполняете это вычисление после того, как newsFeedText был выложен, чтобы вызов для получения его ширины возвращал фактическую ширину. Вы также можете заменить вызов на любой из getPrefWidth(), getMinWidth() или getMaxWidth().

person CAG Gonzo    schedule 16.04.2014
comment
Здесь возникает другая проблема: если вы свяжете TranslateTransition#toX, вызовете TranslateTransition#play и измените размер окна, переход не изменит свой toX, пока он не будет остановлен и воспроизведен снова. В моем случае код вызывается в методе Initializable#initialize, поэтому размер моей сцены в этот момент равен нулю - person Maciej Dobrowolski; 16.04.2014
comment
Используйте ChangeListener, зарегистрированный в свойстве ширины. Там установите значения вашего перехода на новые значения, сообщаемые файлом ChangeListener. - person CAG Gonzo; 16.04.2014