【问题标题】:JavaFX ListView Timeline Scroll Animation is Jumpy (choppy)JavaFX ListView 时间轴滚动动画跳跃(断断续续)
【发布时间】:2017-07-29 14:21:23
【问题描述】:

我正在使用 JavaFX ListView 和 Timeline 来为列表的滚动设置动画。当滚动动画很慢时,文本会跳动(断断续续)。我尝试使用 AnimationTimer 滚动文本。在缓慢滚动期间,文本也很跳跃(断断续续)。 ListView 控件是虚拟视口特性所必需的。以下是使用 Java 版本 1.8 在我的 Mac 上重现问题的示例。

import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Orientation;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ListView;
import javafx.scene.control.ScrollBar;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.HBox;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import javafx.util.Duration;

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

public class Jumpy extends Application {

    ListView listView;
    Timeline timeline = new Timeline();
    double speed = 0.0000005;

    @Override
    public void start(Stage stage) throws Exception {
        List list = new ArrayList();
        for (int i = 0; i < 2000; i++) {
            Text text = new Text("Random line of text to show how it is choppy during scroll animation");
            text.setStyle("-fx-font-size: " + 4 + "em");
            list.add(text);
        }
        ObservableList observableList = FXCollections.observableList(list);
        listView = new ListView((observableList));
        listView.setPrefWidth(600);

        AnchorPane root = new AnchorPane();
        root.getChildren().addAll(listView, buttons());

        stage.setScene(new Scene(root));
        stage.show();
    }

    public void scroll() {
        ScrollBar scrollBar = getVerticalScrollBar();
        EventHandler scroll = new EventHandler<ActionEvent>() {
            public void handle(ActionEvent t) {
                scrollBar.setValue(scrollBar.getValue() + speed);
            }
        };

        timeline.setCycleCount(Timeline.INDEFINITE);
        timeline.setAutoReverse(false);
        KeyValue kv = new KeyValue(scrollBar.valueProperty(), 1);
        KeyFrame kf = new KeyFrame(Duration.seconds(0.017), scroll);
        timeline.getKeyFrames().add(kf);
        timeline.play();
    }

    public ScrollBar getVerticalScrollBar() {
        ScrollBar scrollBar = null;
        for (Node node : listView.lookupAll(".scroll-bar")) {
            if (node instanceof ScrollBar) {
                scrollBar = (ScrollBar) node;
                if ((scrollBar.getOrientation().compareTo(Orientation.VERTICAL)) == 0) {
                    break;
                }
            }
        }
        return scrollBar;
    }

    public HBox buttons() {
        HBox hBox = new HBox();
        Button start = new Button("start");
        start.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                scroll();
            }
        });
        Button slower = new Button("slower");
        slower.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                ScrollBar scrollBar = getVerticalScrollBar();
                EventHandler scroll = new EventHandler<ActionEvent>() {
                    public void handle(ActionEvent t) {
                        scrollBar.setValue(scrollBar.getValue() - 0.000001);
                    }
                };
                KeyFrame kf = new KeyFrame(Duration.millis(10.0D), scroll);
                timeline.getKeyFrames().add(kf);
            }

        });
        Button faster = new Button("faster");
        faster.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                ScrollBar scrollBar = getVerticalScrollBar();
                EventHandler scroll = new EventHandler<ActionEvent>() {
                    public void handle(ActionEvent t) {
                        scrollBar.setValue(scrollBar.getValue() + 0.000001);
                    }
                };
                KeyFrame kf = new KeyFrame(Duration.millis(10.0D), scroll);
                timeline.getKeyFrames().add(kf);

            }
        });
        hBox.getChildren().addAll(start, slower, faster);
        return hBox;
    }

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

【问题讨论】:

    标签: listview animation javafx scroll timeline


    【解决方案1】:

    如果您只是要使用 2000 个Node 实例来填充虚拟化控件,那么使用虚拟化控件是没有意义的:您一开始就完全破坏了使用虚拟化的所有好处。

    用数据填充控件(例如,在本例中为Strings)并设置ListView 的样式或使用单元工厂来控制值的显示方式。

    以下对我来说表现更好:

    ListView<String> listView;
    Timeline timeline = new Timeline();
    double speed = 0.0000005;
    
    @Override
    public void start(Stage stage) throws Exception {
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 2000; i++) {
            String text = "Random line of text to show how it is choppy during scroll animation";
            //  text.setStyle("-fx-font-size: " + 4 + "em");
            list.add(text);
        }
        ObservableList<String> observableList = FXCollections.observableList(list);
        listView = new ListView<String>((observableList));
        listView.setPrefWidth(600);
    
        listView.setStyle("-fx-font-size: 4em; ");
    
        AnchorPane root = new AnchorPane();
        root.getChildren().addAll(listView, buttons());
    
        stage.setScene(new Scene(root));
        stage.show();
    }
    

    在此更改之后,使用AnimationTimer 似乎仍然稍微顺畅一些。这是一个使用这种方法的示例(并删除了所有冗余代码):

    import java.util.ArrayList;
    import java.util.List;
    
    import javafx.animation.AnimationTimer;
    import javafx.animation.Timeline;
    import javafx.application.Application;
    import javafx.collections.FXCollections;
    import javafx.collections.ObservableList;
    import javafx.geometry.Orientation;
    import javafx.scene.Node;
    import javafx.scene.Scene;
    import javafx.scene.control.Button;
    import javafx.scene.control.ListView;
    import javafx.scene.control.ScrollBar;
    import javafx.scene.layout.AnchorPane;
    import javafx.scene.layout.HBox;
    import javafx.stage.Stage;
    
    public class Jumpy extends Application {
    
        ListView<String> listView;
        Timeline timeline = new Timeline();
        double increment = 2e-5 ;
        double speed = 5*increment ;
    
        AnimationTimer timer = new AnimationTimer() {
    
            private long lastUpdate = -1 ;
            private ScrollBar scrollbar ;
    
            @Override
            public void start() {
                scrollbar = getVerticalScrollBar();
                super.start();
            }
    
            @Override
            public void handle(long now) {
                if (lastUpdate < 0) {
                    lastUpdate = now ;
                    return ;
                }
    
                long elapsedNanos = now - lastUpdate ;
                double delta = speed * elapsedNanos / 1_000_000_000 ;
                scrollbar.setValue(scrollbar.getValue() + delta);
    
                lastUpdate = now ;
            }
        };
    
        @Override
        public void start(Stage stage) throws Exception {
            List<String> list = new ArrayList<>();
            for (int i = 0; i < 2000; i++) {
                String text = "Random line of text to show how it is choppy during scroll animation";
                list.add(text);
            }
            ObservableList<String> observableList = FXCollections.observableList(list);
            listView = new ListView<String>((observableList));
            listView.setPrefWidth(600);
    
            listView.setStyle("-fx-font-size: 4em; ");
    
            AnchorPane root = new AnchorPane();
            root.getChildren().addAll(listView, buttons());
    
            stage.setScene(new Scene(root));
            stage.show();
        }
    
        private ScrollBar getVerticalScrollBar() {
            ScrollBar scrollBar = null;
            for (Node node : listView.lookupAll(".scroll-bar")) {
                if (node instanceof ScrollBar) {
                    scrollBar = (ScrollBar) node;
                    if (scrollBar.getOrientation() == Orientation.VERTICAL) {
                        break;
                    }
                }
            }
            return scrollBar;
        }
    
        private HBox buttons() {
            HBox hBox = new HBox();
            Button start = new Button("start");
            start.setOnAction(event -> timer.start());
            Button slower = new Button("slower");
            slower.setOnAction(event -> speed -= increment);
            Button faster = new Button("faster");
            faster.setOnAction(event -> speed += increment);
            hBox.getChildren().addAll(start, slower, faster);
            return hBox;
        }
    
        public static void main(String[] args) {
            launch(args);
        }
    }
    

    【讨论】:

    • James_D 感谢您的详细回复。使用您的示例,我得到了同样的跳跃(断断续续)行为。顺便说一句... ListView 中的 2000 个元素是在我的应用程序中设计的,这个数字可能要大得多。我发现使用一个普通的 List 实际上是一个带有大字体的 ScrollPane 会产生性能问题。
    • @Tim 好吧,对于我发布的任何一个版本的代码来说,它看起来都很流畅。您拥有的代码肯定存在性能问题 - 假设差异是由创建这么多 Node 实例引起的似乎是合理的。
    • 再次感谢您的回复。你是对的,它好多了,但还是有点紧张。我使用带有富文本 StyleClassedTextArea 的无流 VirtualizedScrollPane 以及您的示例中的一些想法(AnimatedTimer 滚动)创建了另一个示例。这些控件的重量比 ListView 轻,并且在滚动时产生流畅的动画。感谢您的帮助!
    • @Tim Yes:flowless 也将提高标准虚拟化窗格的性能。最大的区别应该是通过避免使用 UI 节点作为数据元素来限制创建的 UI 节点的数量。
    猜你喜欢
    • 2012-01-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-27
    • 1970-01-01
    • 1970-01-01
    • 2016-12-24
    • 1970-01-01
    相关资源
    最近更新 更多