JavaFX TimeLine против текстовой анимации холста - PullRequest
1 голос
/ 14 марта 2019

Я пытался написать эффективный элемент управления 'Label' для визуализации текста с вертикальной прокруткой (выделение / тикер).Я написал 2 версии (свободно основанные на некоторых шаблонах, которые я видел здесь), чтобы попытаться улучшить производительность, но я надеюсь, что кто-то может подсказать мне лучший путь, поскольку обе они используют около 4-9% ЦП.

Спецификация ПК: (Intel i7-7660U @ 2,50 ГГц) / 16 ГБ ОЗУ / графическая карта: Intel® Iris (TM) Plus Graphics 640 / Win10.

Хотя это было бы приемлемо, если бы это было все, что я хотел, чтобы мое приложение делало, они будут использоваться в существующем крупном и сложном приложении, и их можно было бы увидеть более чем в 30 раз вместе с остальными подробностями.views.

Первая версия, которую я попробовал, использует TimeLine и наиболее близка к тому, как я хочу, чтобы она выглядела.Его все еще нужно доработать, чтобы текст появлялся / исчезал по желанию, но он достаточно близок для бенчмаркинга.

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.CacheHint;
import javafx.scene.Scene;
import javafx.scene.layout.Border;
import javafx.scene.layout.BorderStroke;
import javafx.scene.layout.BorderStrokeStyle;
import javafx.scene.layout.BorderWidths;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import javafx.util.Duration;

public class TickerLabelTester extends Application {



    public static void main(String[] args) {
        Application.launch(args);
    }

    @Override
    public void start(Stage stage) {
        List<String> list1 = new ArrayList<String>(
                Arrays.asList("Name 1",
                        "Name 2"));
        List<String> list2 = new ArrayList<String>(
                Arrays.asList("Name 3",
                        "Name 4"));

        VBox root = new VBox(2.0);
        root.setStyle("-fx-background-color:orange;");
        for (int i = 0; i < 30; i++) {
            TickerLabel tickerLabel = new TickerLabel();
            if (i % 2 == 0) {
                tickerLabel.setStrings(list1);
            } else {
                tickerLabel.setStrings(list2);
            }
            HBox hBox = new HBox(tickerLabel);
            hBox.setBorder(new Border(new BorderStroke(Color.BLACK,
                    BorderStrokeStyle.SOLID, CornerRadii.EMPTY, BorderWidths.DEFAULT)));
            root.getChildren().add(hBox);
        }
        root.setPrefSize(250, 700);
        Scene scene = new Scene(root);

        stage.setScene(scene);
        stage.setTitle("Scrolling Strings");
        stage.show();


    }

    class TickerLabel extends Pane {
        private Timeline timeline;
        private Text text;
        private List<String> strings = new ArrayList<String>();

        public TickerLabel() {
            Rectangle clip = new Rectangle();
            this.layoutBoundsProperty().addListener((observable, oldValue, newValue) -> {
                clip.setWidth(newValue.getWidth());
                clip.setHeight(newValue.getHeight());
            });
            this.setClip(clip);
            text = new Text();
            this.getChildren().add(text);
            text.setCache(true);
            text.setCacheHint(CacheHint.SPEED);
            timeline = new Timeline();
            timeline.setCycleCount(Timeline.INDEFINITE);
        }

        public void setStrings(List<String> strings) {
            this.strings = strings;
            rerunTransition();
        }

        private void rerunTransition() {
            timeline.stop();
            if (strings.size() > 0) {
                recalculateTranslateTransition();
                timeline.playFromStart();
            }
        }

        private void recalculateTranslateTransition() {
            Duration duration = Duration.ZERO;
            double startPos = text.getLayoutBounds().getHeight() + 8;
            for (String string : strings) {
                KeyValue initKeyValue = new KeyValue(text.translateYProperty(), startPos);
                KeyValue initTextKeyValue = new KeyValue(text.textProperty(), string);
                KeyFrame initFrame = new KeyFrame(duration, initKeyValue, initTextKeyValue);
                timeline.getKeyFrames().add(initFrame);
                duration = Duration.seconds(duration.toSeconds() + 2);
                KeyValue endKeyValue = new KeyValue(text.translateYProperty(), 0);
                KeyFrame endFrame = new KeyFrame(duration, endKeyValue);
                timeline.getKeyFrames().add(endFrame);
            }
        }

    }

}

Вторая версия использует Canvas и GraphicsContext.Я не использовал JavaFX Canvas раньше, и я надеялся, что это может быть похоже на Swing / AWT, где я мог бы использовать некоторую закадровую буферизацию для повышения производительности, к сожалению, эта версия менее производительна, чем TimeLine.Перед переключением на AnimationTimer, показанный ниже, я пытался использовать свой собственный класс Task / Service для выполнения рендеринга, но это было менее производительным.Надеюсь, я что-то упускаю из-за того, как следует использовать GraphicsContext?

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

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.scene.CacheHint;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.layout.Border;
import javafx.scene.layout.BorderStroke;
import javafx.scene.layout.BorderStrokeStyle;
import javafx.scene.layout.BorderWidths;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;

public class TickerCanvasLabelTester extends Application {



    public static void main(String[] args) {
        Application.launch(args);
    }

    @Override
    public void start(Stage stage) {

        List<String> list1 = new ArrayList<String>(
                Arrays.asList("Name 1",
                        "Name 2"));
        List<String> list2 = new ArrayList<String>(
                Arrays.asList("Name 3",
                        "Name 4"));

        VBox root = new VBox(2.0);
        root.setStyle("-fx-background-color:orange;");
        for (int i = 0; i < 30; i++) {
            TickerLabel tickerLabel = new TickerLabel();
            if (i % 2 == 0) {
                tickerLabel.setStrings(list1);
            } else {
                tickerLabel.setStrings(list2);
            }
            HBox hBox = new HBox(tickerLabel);
            hBox.setBorder(new Border(new BorderStroke(Color.BLACK,
                    BorderStrokeStyle.SOLID, CornerRadii.EMPTY, BorderWidths.DEFAULT)));
            root.getChildren().add(hBox);
        }
        root.setPrefSize(250, 700);
        Scene scene = new Scene(root);

        stage.setScene(scene);
        stage.setTitle("Scrolling Strings");
        stage.show();


    }

    class TickerLabel extends Pane {
        private List<String> strings = new ArrayList<String>();
        private int stringPointer = 0;
        private Canvas canvas;
        private GraphicsContext gc;
        private AnimationTimer at;
        private String string;

        public TickerLabel() {
            Rectangle clip = new Rectangle();
            this.layoutBoundsProperty().addListener((observable, oldValue, newValue) -> {
                clip.setWidth(newValue.getWidth());
                clip.setHeight(newValue.getHeight());
            });
            this.setClip(clip);
            canvas = new Canvas();
            canvas.setCache(true);
            canvas.setCacheHint(CacheHint.SPEED);
            canvas.setWidth(100);
            canvas.setHeight(20);
            gc = canvas.getGraphicsContext2D();
            string = "";

            at = new AnimationTimer() {
                private long lastUpdate = 0;
                private double i = canvas.getHeight() + 2;

                @Override
                public void handle(long now) {
                    // limit how often this is called
                    if (now - lastUpdate >= 200_000_000) {
                        if (i >= -2) {
                            gc.clearRect(0, 0, canvas.getWidth(), canvas.getHeight());
                            gc.strokeText(string, 0, i);
                            i--;

                        } else {
                            i = canvas.getHeight();
                            selectNextString();
                        }
                        lastUpdate = now;
                    }
                }
            };
            this.getChildren().add(canvas);

        }

        public void setStrings(List<String> strings) {
            this.strings = strings;
            at.stop();
            selectNextString();
            at.start();
        }

        private void selectNextString() {
            if (strings.size() > 0) {
                string = strings.get(stringPointer);
                if (stringPointer >= strings.size() - 1) {
                    stringPointer = 0;
                } else {
                    stringPointer++;
                }
            }
        }
    }

}

Заранее благодарим за любую помощь.

...