我尝试渲染很多节点,达到一定数量后,滚动变得非常慢。
因此,我使用虚拟化方法,类似于 TableView。由于我有复杂的布局要求(例如,有些节点可以与其他节点相邻,有些需要保持在自己的行上),TableView 不适合。
我已经创建了自己的组件,但是子组件的布局不起作用。滚动时,可以从BorderPane看到浅红色背景,但标签背景和标签文本根本不渲染。
有什么想法吗?
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class VirtualNodesDemo extends Application {
@Override
public void start(Stage primaryStage) {
int virtualNodes = 100;
double nodeWidth = 100;
double nodeHeight = 100;
StackPane root = new StackPane();
Pane content = new Pane() {
@Override
protected void layoutChildren() {
System.out.println("content.layoutChildren() ...");
getChildren().clear();
ScrollPane scrollPane = (ScrollPane) getParent().getParent().getParent();
double viewportHeight = scrollPane.getViewportBounds().getHeight();
double scrollTop = scrollPane.getVvalue() * (getPrefHeight() - viewportHeight);
double y = 0;
for (int i = 0; i < virtualNodes; i++) {
if (y + nodeHeight > scrollTop && y < scrollTop + viewportHeight) {
Pane pane = createRealNode(i);
pane.resizeRelocate(10, y, nodeWidth, nodeHeight);
pane.layout();
getChildren().add(pane);
} else {
break;
}
y += nodeHeight + 10;
}
setPrefHeight(y);
}
private Pane createRealNode(int i) {
BorderPane borderPane = new BorderPane();
borderPane.setManaged(false);
Label label = new Label("Node " + i);
label.setStyle("-fx-background-color: #aaf;");
borderPane.setCenter(label);
borderPane.setStyle("-fx-background-color: #faa;");
return borderPane;
}
};
ScrollPane scrollPane = new ScrollPane(content);
scrollPane.setFitToWidth(true);
scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.ALWAYS);
scrollPane.vvalueProperty().addListener(observable -> content.layout());
root.getChildren().add(scrollPane);
Scene scene = new Scene(root, 800, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
当如下更改createRealNode时,标签和标签文本将按预期显示。但是,我需要能够使用 BorderPane(以及相关的,如 StackPane、GridPane),因为我需要渲染的节点非常大并且有很多子窗格/子窗格。
private Pane createRealNode(int i) {
Label label = new Label("Node " + i);
label.setStyle("-fx-background-color: #aaf;");
Pane p = new Pane();
p.setManaged(false);
p.getChildren().add(label);
return p;
}
我同意所有提到的评论。 不要重新发明轮子!!
应对虚拟化,并不像你想象的那么简单。考虑改变对项目进行建模(数据对象)的方法,然后您可以使用 ListView 来使用单元工厂来渲染节点。
为了快速解决您遇到的问题,请在将窗格添加到子窗格后添加
pane.applyCss();
。这将解决您的问题,并且标签将显示在 BorderPane 中。但正如我所说,这是非常错误的做法。
Pane pane = createRealNode(i);
pane.resizeRelocate(10, y, nodeWidth, nodeHeight);
pane.layout();
getChildren().add(pane);
pane.applyCss();
您可以像我上面提到的使用 ListView 方法想到类似下面的内容:(只是为了给您一些如何处理的想法)
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class VirtualNodesDemo_Update extends Application {
private final static String CSS = "data:text/css," +
"""
.list-view .list-cell{
-fx-background-color: transparent !important;
}
""";
double nodeWidth = 100;
double nodeHeight = 100;
@Override
public void start(Stage primaryStage) {
ObservableList<DataDetails> data = FXCollections.observableArrayList();
for(int i=0;i<100;i++){
data.add(new DataDetails("Node "+i, "#aaf", "#faa", nodeWidth, nodeHeight));
}
ListView<DataDetails> listView = new ListView<>();
listView.setItems(data);
listView.setMaxWidth(Double.MAX_VALUE);
listView.setMaxHeight(Double.MAX_VALUE);
listView.setCellFactory(param -> new CustomListCell());
StackPane root = new StackPane();
root.getChildren().add(listView);
Scene scene = new Scene(root, 800, 600);
scene.getStylesheets().add(CSS);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
class CustomListCell extends ListCell<DataDetails>{
BorderPane pane;
Label label;
public CustomListCell(){
/* Build the structure as you like...*/
pane = new BorderPane();
label = new Label();
pane.setCenter(label);
}
@Override
protected void updateItem(DataDetails item, boolean empty) {
super.updateItem(item, empty);
if(item!=null && !empty){
label.setText(item.getTitle());
pane.setPrefSize(item.getWidth(), item.getHeight());
pane.setMaxSize(item.getWidth(), item.getHeight());
label.setStyle("-fx-background-color: "+item.getLabelBg());
pane.setStyle("-fx-background-color: "+item.getDataBg());
setGraphic(pane);
}else{
setGraphic(null);
}
}
}
class DataDetails{
String title;
String labelBg;
String dataBg;
double width;
double height;
/* Build your data object with all the details that you need to render.. */
public DataDetails(String title, String labelBg, String dataBg, double width, double height) {
this.title = title;
this.labelBg = labelBg;
this.dataBg = dataBg;
this.width = width;
this.height = height;
}
public String getTitle() {
return title;
}
public String getLabelBg() {
return labelBg;
}
public String getDataBg() {
return dataBg;
}
public double getWidth() {
return width;
}
public double getHeight() {
return height;
}
}
}