JavaFX - 调整简单应用程序的大小导致 "flickering" 由于 canvas clearing/repaint
JavaFX - resizing simple application causes "flickering" due to canvas clearing/repaint
我有一个非常简单的项目,它基本上在 JavaFX 场景 Canvas 的中间绘制一个黑色圆圈,并且每 50 毫秒增长一次。
这是我的控制器:
public class PrimaryController {
public StackPane theRootPane;
public CanvasPane theCanvasPane;
int i = 1;
public void initialize() {
theCanvasPane = new CanvasPane(500, 300);
theRootPane.getChildren().addAll(theCanvasPane);
Timeline theTimeline =
new Timeline(new KeyFrame(Duration.millis(50), actionEvent -> UpdateCanvas(i++)));
theTimeline.setCycleCount(Timeline.INDEFINITE);
theTimeline.play();
}
private void UpdateCanvas(int diameter) {
theCanvasPane.DrawCircleAtCenterOfCanvas(diameter);
}
这是我的 Canvas 窗格 class:
public class CanvasPane extends Pane {
private final Canvas theCanvas;
private GraphicsContext theGC;
public CanvasPane(double width, double height) {
setWidth(width);
setHeight(height);
theCanvas = new Canvas(width, height);
theGC = theCanvas.getGraphicsContext2D();
getChildren().add(theCanvas);
theCanvas.widthProperty().bind(this.widthProperty());
theCanvas.heightProperty().bind(this.heightProperty());
theCanvas.widthProperty().addListener(observable -> RedrawCanvas());
theCanvas.heightProperty().addListener(observable -> RedrawCanvas());
}
private void RedrawCanvas() {
ClearCanvas();
}
private void ClearCanvas() {
theGC.clearRect(0, 0, theCanvas.widthProperty().doubleValue(), theCanvas.heightProperty().doubleValue());
}
public void DrawCircleAtCenterOfCanvas(int diameter) {
double centreX = theCanvas.widthProperty().doubleValue() / 2;
double centreY = theCanvas.heightProperty().doubleValue() / 2;
theGC.fillOval(centreX - diameter / 2.0, centreY - diameter / 2.0, diameter, diameter);
}
}
最后,这是我的应用程序 class 和我的 .fxml
public class App extends Application {
private static Scene scene;
@Override
public void start(Stage stage) throws IOException {
scene = new Scene(loadFXML("primary"));
stage.setScene(scene);
//stage.setResizable(false);
stage.show();
}
private static Parent loadFXML(String fxml) throws IOException {
FXMLLoader fxmlLoader = new FXMLLoader(App.class.getResource(fxml + ".fxml"));
return fxmlLoader.load();
}
public static void main(String[] args) {
launch();
}
}
primary.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.StackPane?>
<StackPane fx:id="theRootPane" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.xxx.PrimaryController" />
它工作正常,直到我调整 window 的大小,此时,通过清除 canvas 重新绘制它,然后在 canvas 上绘制新的更大的圆圈。当调整表单大小时,canvas 的这种“清除”表现为闪烁。
执行此操作的更好方法是什么?在学习 Java 并进入 UI 和动画之后,我正在使用 JavaFX。我在想 canvas 是 不是 要走的路...
如有任何建议,我们将不胜感激。
在 ChangeListener
中更新 canvas 可能比在 InvalidationListener
中更新更好,这样可以减少重绘。无论哪种方式,您都应该:
确保在 canvas 大小更改时重新绘制圆(使用当前代码,一旦 canvas 更改大小,您就清除 canvas,但是在下一个关键帧之前不要重新绘制圆圈,所以你最终会在中间有一些空白 canvases):
public class CanvasPane extends Pane {
private final Canvas theCanvas;
private GraphicsContext theGC;
private int currentDiameter ;
public CanvasPane(double width, double height) {
setWidth(width);
setHeight(height);
theCanvas = new Canvas(width, height);
theGC = theCanvas.getGraphicsContext2D();
getChildren().add(theCanvas);
theCanvas.widthProperty().bind(this.widthProperty());
theCanvas.heightProperty().bind(this.heightProperty());
theCanvas.widthProperty().addListener((obs, oldWidth, newWidth) -> redrawCanvas());
theCanvas.heightProperty().addListener((obs, oldHeight, newHeight) -> redrawCanvas());
}
public void increaseDiameter() {
currentDiameter++;
redrawCanvas();
}
private void redrawCanvas() {
clearCanvas();
drawCircleAtCenterOfCanvas();
}
private void clearCanvas() {
theGC.clearRect(0, 0, theCanvas.widthProperty().doubleValue(), theCanvas.heightProperty().doubleValue());
}
public void drawCircleAtCenterOfCanvas() {
currentDiameter = currentDiameter ;
double centreX = theCanvas.widthProperty().doubleValue() / 2;
double centreY = theCanvas.heightProperty().doubleValue() / 2;
theGC.fillOval(centreX - currentDiameter / 2.0, centreY - currentDiameter / 2.0, currentDiameter, currentDiameter);
}
}
和
public class PrimaryController {
@FXML
private StackPane theRootPane;
@FXML
private CanvasPane theCanvasPane;
public void initialize() {
theCanvasPane = new CanvasPane(500, 300);
theRootPane.getChildren().addAll(theCanvasPane);
Timeline theTimeline =
new Timeline(new KeyFrame(Duration.millis(50), actionEvent -> updateCanvas()));
theTimeline.setCycleCount(Timeline.INDEFINITE);
theTimeline.play();
}
private void updateCanvas() {
theCanvasPane.increaseDiameter();
}
}
或者(对于这个例子可能更好,也更容易)使用 Circle
而不是 canvas:
public class PrimaryController {
@FXML
private StackPane theRootPane;
private Circle circle ;
public void initialize() {
circle = new Circle();
theRootPane.getChildren().addAll(circle);
Timeline theTimeline =
new Timeline(new KeyFrame(Duration.millis(50), actionEvent -> updateCircle()));
theTimeline.setCycleCount(Timeline.INDEFINITE);
theTimeline.play();
}
private void updateCircle() {
circle.setRadius(circle.getRadius()+0.5);
}
}
我有一个非常简单的项目,它基本上在 JavaFX 场景 Canvas 的中间绘制一个黑色圆圈,并且每 50 毫秒增长一次。
这是我的控制器:
public class PrimaryController {
public StackPane theRootPane;
public CanvasPane theCanvasPane;
int i = 1;
public void initialize() {
theCanvasPane = new CanvasPane(500, 300);
theRootPane.getChildren().addAll(theCanvasPane);
Timeline theTimeline =
new Timeline(new KeyFrame(Duration.millis(50), actionEvent -> UpdateCanvas(i++)));
theTimeline.setCycleCount(Timeline.INDEFINITE);
theTimeline.play();
}
private void UpdateCanvas(int diameter) {
theCanvasPane.DrawCircleAtCenterOfCanvas(diameter);
}
这是我的 Canvas 窗格 class:
public class CanvasPane extends Pane {
private final Canvas theCanvas;
private GraphicsContext theGC;
public CanvasPane(double width, double height) {
setWidth(width);
setHeight(height);
theCanvas = new Canvas(width, height);
theGC = theCanvas.getGraphicsContext2D();
getChildren().add(theCanvas);
theCanvas.widthProperty().bind(this.widthProperty());
theCanvas.heightProperty().bind(this.heightProperty());
theCanvas.widthProperty().addListener(observable -> RedrawCanvas());
theCanvas.heightProperty().addListener(observable -> RedrawCanvas());
}
private void RedrawCanvas() {
ClearCanvas();
}
private void ClearCanvas() {
theGC.clearRect(0, 0, theCanvas.widthProperty().doubleValue(), theCanvas.heightProperty().doubleValue());
}
public void DrawCircleAtCenterOfCanvas(int diameter) {
double centreX = theCanvas.widthProperty().doubleValue() / 2;
double centreY = theCanvas.heightProperty().doubleValue() / 2;
theGC.fillOval(centreX - diameter / 2.0, centreY - diameter / 2.0, diameter, diameter);
}
}
最后,这是我的应用程序 class 和我的 .fxml
public class App extends Application {
private static Scene scene;
@Override
public void start(Stage stage) throws IOException {
scene = new Scene(loadFXML("primary"));
stage.setScene(scene);
//stage.setResizable(false);
stage.show();
}
private static Parent loadFXML(String fxml) throws IOException {
FXMLLoader fxmlLoader = new FXMLLoader(App.class.getResource(fxml + ".fxml"));
return fxmlLoader.load();
}
public static void main(String[] args) {
launch();
}
}
primary.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.StackPane?>
<StackPane fx:id="theRootPane" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.xxx.PrimaryController" />
它工作正常,直到我调整 window 的大小,此时,通过清除 canvas 重新绘制它,然后在 canvas 上绘制新的更大的圆圈。当调整表单大小时,canvas 的这种“清除”表现为闪烁。
执行此操作的更好方法是什么?在学习 Java 并进入 UI 和动画之后,我正在使用 JavaFX。我在想 canvas 是 不是 要走的路...
如有任何建议,我们将不胜感激。
在 ChangeListener
中更新 canvas 可能比在 InvalidationListener
中更新更好,这样可以减少重绘。无论哪种方式,您都应该:
确保在 canvas 大小更改时重新绘制圆(使用当前代码,一旦 canvas 更改大小,您就清除 canvas,但是在下一个关键帧之前不要重新绘制圆圈,所以你最终会在中间有一些空白 canvases):
public class CanvasPane extends Pane { private final Canvas theCanvas; private GraphicsContext theGC; private int currentDiameter ; public CanvasPane(double width, double height) { setWidth(width); setHeight(height); theCanvas = new Canvas(width, height); theGC = theCanvas.getGraphicsContext2D(); getChildren().add(theCanvas); theCanvas.widthProperty().bind(this.widthProperty()); theCanvas.heightProperty().bind(this.heightProperty()); theCanvas.widthProperty().addListener((obs, oldWidth, newWidth) -> redrawCanvas()); theCanvas.heightProperty().addListener((obs, oldHeight, newHeight) -> redrawCanvas()); } public void increaseDiameter() { currentDiameter++; redrawCanvas(); } private void redrawCanvas() { clearCanvas(); drawCircleAtCenterOfCanvas(); } private void clearCanvas() { theGC.clearRect(0, 0, theCanvas.widthProperty().doubleValue(), theCanvas.heightProperty().doubleValue()); } public void drawCircleAtCenterOfCanvas() { currentDiameter = currentDiameter ; double centreX = theCanvas.widthProperty().doubleValue() / 2; double centreY = theCanvas.heightProperty().doubleValue() / 2; theGC.fillOval(centreX - currentDiameter / 2.0, centreY - currentDiameter / 2.0, currentDiameter, currentDiameter); } }
和
public class PrimaryController { @FXML private StackPane theRootPane; @FXML private CanvasPane theCanvasPane; public void initialize() { theCanvasPane = new CanvasPane(500, 300); theRootPane.getChildren().addAll(theCanvasPane); Timeline theTimeline = new Timeline(new KeyFrame(Duration.millis(50), actionEvent -> updateCanvas())); theTimeline.setCycleCount(Timeline.INDEFINITE); theTimeline.play(); } private void updateCanvas() { theCanvasPane.increaseDiameter(); } }
或者(对于这个例子可能更好,也更容易)使用
Circle
而不是 canvas:public class PrimaryController { @FXML private StackPane theRootPane; private Circle circle ; public void initialize() { circle = new Circle(); theRootPane.getChildren().addAll(circle); Timeline theTimeline = new Timeline(new KeyFrame(Duration.millis(50), actionEvent -> updateCircle())); theTimeline.setCycleCount(Timeline.INDEFINITE); theTimeline.play(); } private void updateCircle() { circle.setRadius(circle.getRadius()+0.5); } }