英文:
Optimizing Pagination rendering
问题
在查看 Pagination
时,涉及到渲染复杂页面的问题。API 示例,et al.,通常会指定一个 pageFactory
,每次调用它时都会简单地构建一个新的控件。确实,在分页时,下面的示例 进行性能分析 时,内存压力很小,会有大量新的实例被迅速回收。如果不断增加的复杂性改变了情况,我可以怎么做?
import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.Pagination;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
/** @see https://stackoverflow.com/q/76359690/230513 */
public class PaginationSample extends Application {
private static final int N = 100;
private record Item(String name, Color color) {
private static final Random r = new Random();
public static Item ofRandom() {
var s = (char) ('A' + r.nextInt(26))
+ String.valueOf(r.nextInt(900_000) + 100_000);
var c = Color.rgb(r.nextInt(255), r.nextInt(255), r.nextInt(255));
return new Item(s, c);
}
}
@Override
public void start(Stage stage) {
stage.setTitle("PaginationTStreamest");
List<Item> items = Stream.generate(Item::ofRandom)
.limit(N).collect(Collectors.toList());
var pagination = new Pagination(items.size(), 0);
pagination.setPageFactory((i) -> createItemPane(items.get(i)));
stage.setScene(new Scene(pagination));
stage.show();
pagination.requestFocus();
}
private StackPane createItemPane(Item item) {
var pane = new StackPane();
pane.setPadding(new Insets(16));
var label = new Label(item.name, new Rectangle(320, 240, item.color));
label.setTextFill(item.color.invert());
label.setStyle("-fx-font-family: serif; -fx-font-size: 36;");
label.setContentDisplay(ContentDisplay.CENTER);
var button = new Button("Button");
StackPane.setAlignment(button, Pos.BOTTOM_RIGHT);
button.setOnAction((e) -> {
System.out.println(item);
});
pane.getChildren().addAll(label, button);
return pane;
}
public static void main(String[] args) {
launch(args);
}
}
英文:
While looking at Pagination
, the question of rendering complex pages arose. The API example, et al., typically specify a pageFactory
that simply constructs a new control each time it is called. Indeed, profiling the example below while paging showed minimal memory pressure, with a flurry of new instances that were promptly collected. What can I do if growing complexity changes the picture?
import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.Pagination;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
/** @see https://stackoverflow.com/q/76359690/230513 */
public class PaginationSample extends Application {
private static final int N = 100;
private record Item(String name, Color color) {
private static final Random r = new Random();
public static Item ofRandom() {
var s = (char) ('A' + r.nextInt(26))
+ String.valueOf(r.nextInt(900_000) + 100_000);
var c = Color.rgb(r.nextInt(255), r.nextInt(255), r.nextInt(255));
return new Item(s, c);
}
}
@Override
public void start(Stage stage) {
stage.setTitle("PaginationTStreamest");
List<Item> items = Stream.generate(Item::ofRandom)
.limit(N).collect(Collectors.toList());
var pagination = new Pagination(items.size(), 0);
pagination.setPageFactory((i) -> createItemPane(items.get(i)));
stage.setScene(new Scene(pagination));
stage.show();
pagination.requestFocus();
}
private StackPane createItemPane(Item item) {
var pane = new StackPane();
pane.setPadding(new Insets(16));
var label = new Label(item.name, new Rectangle(320, 240, item.color));
label.setTextFill(item.color.invert());
label.setStyle("-fx-font-family: serif; -fx-font-size: 36;");
label.setContentDisplay(ContentDisplay.CENTER);
var button = new Button("Button");
StackPane.setAlignment(button, Pos.BOTTOM_RIGHT);
button.setOnAction((e) -> {
System.out.println(item);
});
pane.getChildren().addAll(label, button);
return pane;
}
public static void main(String[] args) {
launch(args);
}
}
答案1
得分: 5
在这种情况下,分析是正确的做法,但优化渲染器的准备工作可能是值得的。与ListView
和TableView
一样,Pagination
使轻量级渲染变得容易:
-
构建最少数量的可重用显示对象。
-
在呼叫渲染时,_更新_现有对象以产生变化。
在下面的RenderPane
中,init()
建立了一个单一的窗格、图表和按钮,而页面工厂的回调调用的fill()
方法则为每个页面准备了特定Item
的渲染器详细信息。请注意,_单一_图表和数据收集就足够了;监听图表将自动响应变化进行更新。
在注释中提供的_ad hoc_构造函数和工厂调用可用于比较。效益是可测量的,表明它应该能够很好地扩展。
相关的图表示例可以在这里看到,其他Pagination
主题可以在这里找到。
注意事项:正如下面的评论中@Slaw所指出的,PaginationSkin
对从一页到下一页的切换进行了动画处理。对于仅有一个视图组件的情况,动画是可用的但不太吸引人。此外,动画可能会干扰后续的更新。可能需要的一些可能的调整包括:
-
禁用组件的动画,如下例所示:
chart.setAnimated(false);
-
安排组件的更新以在动画之后进行,如下所示:
public Pane fill(int i, Item item) { Platform.runLater(() -> { … }); return pane; }
PaginationTest.java
import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.chart.PieChart;
import javafx.scene.control.Button;
import javafx.scene.control.Pagination;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
/** @see https://stackoverflow.com/a/76359691/230513 */
public class PaginationTest extends Application {
private static final int N = 100;
private record Item(String name, Color color) {
private static final Random r = new Random();
public static Item ofRandom() {
var s = (char) ('A' + r.nextInt(26))
+ String.valueOf(r.nextInt(900_000) + 100_000);
var c = Color.rgb(r.nextInt(255), r.nextInt(255), r.nextInt(255));
return new Item(s, c);
}
}
@Override
public void start(Stage stage) {
stage.setTitle("PaginationTest");
List<Item> items = Stream.generate(Item::ofRandom)
.limit(N).collect(Collectors.toList());
var pagination = new Pagination(items.size(), 0);
var renderer = new RenderPane();
pagination.setPageFactory((i) -> renderer.fill(items.get(i)));
// pagination.setPageFactory((i) -> {
// var renderer = new RenderPane(items.get(i));
// return renderer.pane;
// });
stage.setScene(new Scene(pagination));
stage.show();
pagination.requestFocus();
}
private final static class RenderPane {
private final StackPane pane = new StackPane();
private final PieChart chart = new PieChart();
private final Circle circle = new Circle(10);
private final Button button = new Button("Details", circle);
public RenderPane() {
init();
}
// public RenderPane(Item item) {
// init();
// fill(item);
// }
private void init() {
pane.setPadding(new Insets(16));
StackPane.setAlignment(button, Pos.BOTTOM_RIGHT);
chart.setAnimated(false);
chart.getData().add(0, new PieChart.Data("Red", 0));
chart.getData().add(1, new PieChart.Data("Green", 0));
chart.getData().add(2, new PieChart.Data("Blue", 0));
chart.getStylesheets().add("PaginationTest.css");
button.setGraphic(circle);
pane.getChildren().addAll(chart, button);
}
public Pane fill(Item item) {
chart.setTitle(item.name);
chart.getData().get(0).setPieValue(item.color.getRed());
chart.getData().get(1).setPieValue(item.color.getGreen());
chart.getData().get(2).setPieValue(item.color.getBlue());
circle.setFill(item.color);
button.setOnAction((e) -> {
System.out.println(item);
});
return pane;
}
}
public static void main(String[] args) {
launch(args);
}
}
PaginationTest.css
PaginationTest.css
.default-color0.chart-pie { -fx-pie-color: #FF0000; }
.default-color1.chart-pie { -fx-pie-color: #00FF00; }
.default-color2.chart-pie { -fx-pie-color: #0000FF; }
<details>
<summary>英文:</summary>
Profiling is the correct thing to do in this case, but optimizing the renderer's preparation is probably warranted. Like `ListView` and `TableView`, `Pagination` makes [_flyweight_][1] rendering easy:
- Construct the minimal number of reusable display objects.
- When called to render, _update_ existing objects to effect a change.
In the `RenderPane` below, `init()` establishes a single pane, chart and button, while the `fill()` method invoked by the page factory's callback prepares the renderer with details of a specific `Item` for each page. Note that a _single_ chart and data collection suffices; the listening chart will update itself in response to the change.
An _ad hoc_ constructor and factory invocation in comments may be used for comparison. The benefit is measurable, suggesting it should scale well.
A related chart example is seen [here][2], and additional `Pagination` topics are addressed [here][3].
**Caveats**: As noted in comments by @Slaw below, the [`PaginationSkin`][4] animates the transition from one page to the next. With just a single view component, the animation is serviceable but less appealing. Moreover, the animation may interfere with subsequent updates. Among possible accommodations that may be required:
- Disable the component's animation, shown in the example below:
chart.setAnimated(false);
- Schedule the the component's update to follow the animation.
public Pane fill(int i, Item item) {
Platform.runLater(() -> {
…
});
return pane;
[![Complex page][5]][5]
`PaginationTest.java`
import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.chart.PieChart;
import javafx.scene.control.Button;
import javafx.scene.control.Pagination;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
/** @see https://stackoverflow.com/a/76359691/230513 */
public class PaginationTest extends Application {
private static final int N = 100;
private record Item(String name, Color color) {
private static final Random r = new Random();
public static Item ofRandom() {
var s = (char) ('A' + r.nextInt(26))
+ String.valueOf(r.nextInt(900_000) + 100_000);
var c = Color.rgb(r.nextInt(255), r.nextInt(255), r.nextInt(255));
return new Item(s, c);
}
}
@Override
public void start(Stage stage) {
stage.setTitle("PaginationTest");
List<Item> items = Stream.generate(Item::ofRandom)
.limit(N).collect(Collectors.toList());
var pagination = new Pagination(items.size(), 0);
var renderer = new RenderPane();
pagination.setPageFactory((i) -> renderer.fill(items.get(i)));
// pagination.setPageFactory((i) -> {
// var renderer = new RenderPane(items.get(i));
// return renderer.pane;
// });
stage.setScene(new Scene(pagination));
stage.show();
pagination.requestFocus();
}
private final static class RenderPane {
private final StackPane pane = new StackPane();
private final PieChart chart = new PieChart();
private final Circle circle = new Circle(10);
private final Button button = new Button("Details", circle);
public RenderPane() {
init();
}
// public RenderPane(Item item) {
// init();
// fill(item);
// }
private void init() {
pane.setPadding(new Insets(16));
StackPane.setAlignment(button, Pos.BOTTOM_RIGHT);
chart.setAnimated(false);
chart.getData().add(0, new PieChart.Data("Red", 0));
chart.getData().add(1, new PieChart.Data("Green", 0));
chart.getData().add(2, new PieChart.Data("Blue", 0));
chart.getStylesheets().add("PaginationTest.css");
button.setGraphic(circle);
pane.getChildren().addAll(chart, button);
}
public Pane fill(Item item) {
chart.setTitle(item.name);
chart.getData().get(0).setPieValue(item.color.getRed());
chart.getData().get(1).setPieValue(item.color.getGreen());
chart.getData().get(2).setPieValue(item.color.getBlue());
circle.setFill(item.color);
button.setOnAction((e) -> {
System.out.println(item);
});
return pane;
}
}
public static void main(String[] args) {
launch(args);
}
}
`PaginationTest.css`
PaginationTest.css
.default-color0.chart-pie { -fx-pie-color: #FF0000; }
.default-color1.chart-pie { -fx-pie-color: #00FF00; }
.default-color2.chart-pie { -fx-pie-color: #0000FF; }
[1]: https://en.wikipedia.org/wiki/Flyweight_pattern
[2]: https://stackoverflow.com/a/53579013/230513
[3]: https://stackoverflow.com/q/13872273/230513
[4]: https://github.com/openjdk/jfx/blob/f8c8a8a1f98902ab6c7923a9b0897106ea957ba8/modules/javafx.controls/src/main/java/javafx/scene/control/skin/PaginationSkin.java#L86
[5]: https://i.stack.imgur.com/sbQST.png
</details>
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论