JavaFX应用程序似乎有两种:第一种使用带有节点和CSS样式的场景图,第二种使用单个画布。 但是,将这两种方法混合使用是完全合法的。 尤其是当您的应用程序必须显示大量详细信息时,您很容易最终创建成千上万个节点。 即使JavaFX的整体性能非常出色,当所有这些节点都需要样式设置时(特别是由于可视化的动态性质而需要反复进行样式设置时),您很有可能会使系统崩溃。
对我来说,这是一个顿悟,当我意识到在FlexGanttFX中保证高性能的唯一方法是对每个包含画布的单元使用ListView。 不幸的是,该框架的代码太复杂,无法在一个小博客中与您共享。因此,我写了一个小例子来说明基本概念。 下图显示了运行示例时的结果。 ListView显示的数据涵盖了我生命中的岁月,并且每年的每一天都有随机生成的值。
最重要的类称为CanvasCell 。 它是一个专门的列表视图单元,其中包含标签和画布。 标签用于显示年份,画布用于绘制图表。
import java.util.Collections;
import java.util.List;
import javafx.geometry.Pos;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.paint.CycleMethod;
import javafx.scene.paint.LinearGradient;
import javafx.scene.paint.Stop;
public class CanvasCell extends ListCell<YearEntry> {
private Label yearLabel;
private ResizableCanvas canvas;
public CanvasCell() {
/*
* Important, otherwise we will keep seeing a horizontal scrollbar.
*/
setStyle("-fx-padding: 0px;");
yearLabel = new Label();
yearLabel
.setStyle("-fx-padding: 10px; -fx-font-size: 1.2em; -fx-font-weight: bold;");
StackPane.setAlignment(yearLabel, Pos.TOP_LEFT);
/*
* Create a resizable canvas and bind its width and height to the width
* and height of the table cell.
*/
canvas = new ResizableCanvas();
canvas.widthProperty().bind(widthProperty());
canvas.heightProperty().bind(heightProperty());
StackPane pane = new StackPane();
pane.getChildren().addAll(yearLabel, canvas);
setGraphic(pane);
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
}
@Override
protected void updateItem(YearEntry entry, boolean empty) {
if (empty || entry == null) {
yearLabel.setText("");
canvas.setData(Collections.emptyList());
canvas.draw();
} else {
yearLabel.setText(Integer.toString(entry.getYear()));
canvas.setData(entry.getValues());
canvas.draw();
}
}
/*
* Canvas is normally not resizable but by overriding isResizable() and
* binding its width and height to the width and height of the cell it will
* automatically resize.
*/
class ResizableCanvas extends Canvas {
private List<Double> data = Collections.emptyList();
public ResizableCanvas() {
/*
* Make sure the canvas draws its content again when its size
* changes.
*/
widthProperty().addListener(it -> draw());
heightProperty().addListener(it -> draw());
}
@Override
public boolean isResizable() {
return true;
}
@Override
public double prefWidth(double height) {
return getWidth();
}
@Override
public double prefHeight(double width) {
return getHeight();
}
public void setData(List<Double> data) {
this.data = data;
}
/*
* Draw a chart based on the data provided by the model.
*/
private void draw() {
GraphicsContext gc = getGraphicsContext2D();
gc.clearRect(0, 0, getWidth(), getHeight());
Stop[] stops = new Stop[] { new Stop(0, Color.SKYBLUE),
new Stop(1, Color.SKYBLUE.darker().darker()) };
LinearGradient gradient = new LinearGradient(0, 0, 0, 300, false,
CycleMethod.NO_CYCLE, stops);
gc.setFill(gradient);
double availableHeight = getHeight() * .8;
double counter = 0;
for (Double value : data) {
double x = getWidth() / 365 * counter;
double barHeight = availableHeight * value / 100;
double barWidth = getWidth() / 365 + 1;
gc.fillRect(x, getHeight() - barHeight, barWidth, barHeight);
counter++;
}
}
}
}
对于数据,我们使用一个非常简单的类来存储年份和值列表。
import java.util.ArrayList;
import java.util.List;
/**
* Just some fake model object.
*/
public class YearEntry {
private int year;
public YearEntry(int year) {
this.year = year;
}
public int getYear() {
return year;
}
private List<Double> values = new ArrayList<>();
/**
* Stores the values shown in the chart.
*/
public List<Double> getValues() {
return values;
}
}
以下清单显示了主类。
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.ListView;
import javafx.stage.Stage;
public class CanvasApp extends Application {
@Override
public void start(Stage stage) throws Exception {
/*
* Create some random data for my life span.
*/
ObservableList<YearEntry> data =
FXCollections.observableArrayList();
for (int year = 1969; year < 2015; year++) {
YearEntry entry = new YearEntry(year);
for (int day = 0; day < 365; day++) {
entry.getValues().add(Math.random() * 100);
}
data.add(entry);
}
ListView<YearEntry> listView = new ListView<>(data);
listView.setCellFactory(param -> new CanvasCell());
listView.setFixedCellSize(200);
Scene scene = new Scene(listView);
stage.setTitle("Canvas Cell");
stage.setScene(scene);
stage.setWidth(600);
stage.setHeight(600);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}