如何降低Java中drawFrame()调用次数以实现动画慢动作?
如何降低JavaFX中drawFrame()的调用次数实现慢动作动画?
我正在学习David Eck所著的《Java入门》一书,书中GUI图形章节的嵌套矩形动画示例里,drawFrame()子例程每秒会被自动调用约60次。我希望降低该方法的调用次数,从而让矩形以慢动作移动。以下是完整代码:
package com.example.ch3movingrects; import javafx.animation.AnimationTimer; import javafx.application.Application; import javafx.scene.layout.BorderPane; import javafx.scene.Scene; import javafx.stage.Stage; import javafx.scene.canvas.Canvas; import javafx.scene.canvas.GraphicsContext; import javafx.scene.paint.Color; /** * When run as a program, this class opens a window on the screen that * shows a set of nested rectangles that seems to be moving infinitely * inward towards the center. The animation continues until the user * closes the window. */ public class ch3MovingRects extends Application { /** * Draws a set of nested rectangles. This subroutine is called 60 times per * second and is responsible for redrawing the entire drawing area. The * parameter g is used for drawing. The frameNumber starts at zero and * increases by 1 each time this subroutine is called. The parameters width * and height give the size of the drawing area, in pixels. * The sizes and positions of the rectangles that are drawn depend * on the frame number, giving the illusion of motion. */ public void drawFrame(GraphicsContext g, int frameNumber, double elapsedSeconds, int width, int height) { g.setFill(Color.WHITE); g.fillRect(0,0,width,height); // Fill drawing area with white. double inset; // Gap between edges of drawing area and the outer rectangle. double rectWidth, rectHeight; // The size of one of the rectangles. g.setStroke(Color.BLACK); // Draw the rectangle outlines in black. inset = frameNumber % 15 + 0.5; // The "+ 0.5" is a technicality to produce a sharper image. rectWidth = width - 2*inset; rectHeight = height - 2*inset; while (rectWidth >= 0 && rectHeight >= 0) { g.strokeRect(inset, inset, rectWidth, rectHeight); inset += 15; // rectangles are 15 pixels apart rectWidth -= 30; rectHeight -= 30; } } //------ Implementation details: DO NOT EXPECT TO UNDERSTAND THIS ------ public void start(Stage stage) { int width = 800; // The width of the image. You can modify this value! int height = 600; // The height of the image. You can modify this value! Canvas canvas = new Canvas(width,height); drawFrame(canvas.getGraphicsContext2D(), 0, 0, width, height); BorderPane root = new BorderPane(canvas); root.setStyle("-fx-border-width: 4px; -fx-border-color: #444"); Scene scene = new Scene(root); stage.setScene(scene); stage.setTitle("Infinte Moving Rects"); // STRING APPEARS IN WINDOW TITLEBAR! stage.show(); stage.setResizable(false); AnimationTimer anim = new AnimationTimer() { private int frameNum; private long startTime = -1; private long previousTime; public void handle(long now) { if (startTime < 0) { startTime = previousTime = now; drawFrame(canvas.getGraphicsContext2D(), 0, 0, width, height); } else if (now - previousTime > 0.95e9/60) { // The test in the else-if is to make sure that drawFrame() is // called about once every 1/60 second. It is required since // handle() can be called by the system more often than that. frameNum++; drawFrame(canvas.getGraphicsContext2D(), frameNum, (now-startTime)/1e9, width, height); previousTime = now; } } }; anim.start(); } public static void main(String[] args) { launch(); } } // end MovingRects
解决方案
1. 修改AnimationTimer的调用间隔阈值
原代码中AnimationTimer的handle方法通过now - previousTime > 0.95e9/60判断来控制每秒约60次调用。要降低调用次数,直接修改分母数值即可:
- 若要每秒30次调用,改为
0.95e9/30 - 若要每秒20次调用,改为
0.95e9/20
修改后的核心代码片段:
else if (now - previousTime > 0.95e9/30) { // 调整为每秒30帧 frameNum++; drawFrame(canvas.getGraphicsContext2D(), frameNum, (now-startTime)/1e9, width, height); previousTime = now; }
2. 使用Timeline替代AnimationTimer(更直观控制帧率)
Timeline可以直接指定关键帧间隔,无需手动计算时间差,适合固定帧率场景。替换原AnimationTimer部分的代码:
// 替换原AnimationTimer代码块 import javafx.animation.Timeline; import javafx.animation.KeyFrame; import javafx.animation.Animation; import javafx.util.Duration; // 在start方法内: int frameNum = 0; Timeline timeline = new Timeline(); timeline.setCycleCount(Animation.INDEFINITE); // 设置间隔:1000ms/30 ≈33.3ms,即每秒30帧 KeyFrame keyFrame = new KeyFrame(Duration.millis(1000/30), event -> { frameNum++; drawFrame(canvas.getGraphicsContext2D(), frameNum, frameNum/30.0, width, height); }); timeline.getKeyFrames().add(keyFrame); timeline.play();
3. 保持调用次数但减慢动画进度(可选)
如果不想降低drawFrame()调用次数,仅需动画变慢,可以修改frameNum的增长速率,比如每两次调用才让frameNum加1:
else if (now - previousTime > 0.95e9/60) { // 每两次调用才推进一次动画帧 if (frameNum % 2 == 0) { frameNum++; } drawFrame(canvas.getGraphicsContext2D(), frameNum, (now-startTime)/1e9, width, height); previousTime = now; }
这种方法本质上没有减少drawFrame()调用次数,仅通过减慢进度实现慢动作。
内容的提问来源于stack exchange,提问作者Tushar
相关产品推荐
相关产品推荐

