JavaFX ListView 时间轴滚动动画是跳跃的(断断续续的)
JavaFX ListView Timeline Scroll Animation is Jumpy (choppy)
我正在使用 JavaFX ListView 和时间轴为列表的滚动设置动画。当滚动动画很慢时,文本会跳动(断断续续)。我试过使用 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);
}
}
如果您只想用 2000 个 Node
实例填充它,那么使用虚拟化控件是没有意义的:您首先完全破坏了使用虚拟化的几乎所有好处。
用数据填充控件(例如,在本例中,String
s)并设置 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);
}
}
我正在使用 JavaFX ListView 和时间轴为列表的滚动设置动画。当滚动动画很慢时,文本会跳动(断断续续)。我试过使用 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);
}
}
如果您只想用 2000 个 Node
实例填充它,那么使用虚拟化控件是没有意义的:您首先完全破坏了使用虚拟化的几乎所有好处。
用数据填充控件(例如,在本例中,String
s)并设置 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);
}
}