【问题标题】:Java FX Screen Updates Freezing during AnimationJava FX 屏幕更新在动画期间冻结
【发布时间】:2018-07-30 23:35:33
【问题描述】:

你们中的任何一位 FX 大师能告诉我为什么这段代码似乎会冻结 FX 屏幕更新吗?循环继续在动画线程中运行,但一段时间后屏幕停止更新。

顺便说一句,我知道以这种方式使用 Thread.sleep 可能会让一些人感到不安,但这是为学生在入门课程中编写的代码,允许他们在不进行任何事件处理的情况下创建动画。

学生的练习是转换动画,使其反弹一个由 100 个随机方向移动的球组成的数组。转换后,冻结往往比单球早得多。

提前致谢!

这是主要的类...

package week6code;

import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.paint.Color;
import javafx.stage.Stage;

/**
 * Bouncing Balls exercise starter
 *
 * @author Sam Scott
 */
public class BouncingBalls extends Application {

    /**
     * Sets up the stage and starts the main thread. Your drawing code should
     * NOT go here.
     *
     * @param stage The first stage
     */
    @Override
    public void start(Stage stage) {
        stage.setTitle("Bouncing Balls!"); // window title here
        Canvas canvas = new Canvas(400, 300); // canvas size here
        Group root = new Group();
        Scene scene = new Scene(root);
        root.getChildren().add(canvas);
        stage.setScene(scene);
        stage.show();
        GraphicsContext gc = canvas.getGraphicsContext2D();

        // This code starts a "thread" which will run your animation
        Thread t = new Thread(() -> animate(gc));
        t.start();
    }

    /**
     * Animation thread. This is where you put your animation code.
     *
     * @param gc The drawing surface
     */
    public void animate(GraphicsContext gc) {
        // YOUR CODE HERE!

        // intial positions and speeds
        Ball ball = new Ball(100, 50, -1, -1, 10, Color.RED);

        while (true) // loop forever
        {
            // draw screen 
            gc.setFill(Color.YELLOW);
            gc.fillRect(0, 0, 400, 300);
            ball.draw(gc);

            // moving
            ball.moveOneStep();

            // bouncing
            if (ball.getX() <= 0 || ball.getX() >= 400 - (ball.getSize() - 1)) {
                ball.bounceX();
            }
            if (ball.getY() <= 0 || ball.getY() >= 300 - (ball.getSize() - 1)) {
                ball.bounceY();
            }

            // pause
            pause(1000 / 60);
        }

    }

    /**
     * Use this method instead of Thread.sleep(). It handles the possible
     * exception by catching it, because re-throwing it is not an option in this
     * case.
     *
     * @param duration Pause time in milliseconds.
     */
    public static void pause(int duration) {
        try {
            Thread.sleep(duration);
        } catch (InterruptedException ex) {
        }
    }

    /**
     * Exits the app completely when the window is closed. This is necessary to
     * kill the animation thread.
     */
    @Override
    public void stop() {
        System.exit(0);
    }

    /**
     * Launches the app
     *
     * @param args unused
     */
    public static void main(String[] args) {
        launch(args);
    }
}

这是 Ball 类。

package week6solutions;

import javafx.scene.canvas.GraphicsContext;
import javafx.scene.paint.Color;

/**
 * An example of an object that can draw and move itself.
 *
 * @author Sam Scott
 */
public class Ball {

    private double x, y, xSpeed, ySpeed;
    private final int size;
    private final Color c;

    /**
     * Creates a Ball instance.
     *
     * @param x Initial x position (left)
     * @param y Initial y position (top)
     * @param xSpeed Number of pixels to move horizontally in each step
     * (negative for left, positive for right)
     * @param ySpeed Number of pixels to move vertically in each step (negative
     * for up, positive for down)
     * @param size Diameter of ball
     * @param c Color of ball
     */
    public Ball(double x, double y, double xSpeed, double ySpeed, int size, Color c) {
        this.x = x;
        this.y = y;
        this.xSpeed = xSpeed;
        this.ySpeed = ySpeed;
        this.size = size;
        this.c = c;
    }

    /**
     * Increment x and y using the values of xSpeed and ySpeed
     */
    public void moveOneStep() {
        x += xSpeed;
        y += ySpeed;
    }

    /**
     * Reverses the x direction by multiplying it by -1
     */
    public void bounceX() {
        xSpeed *= -1;
    }

    /**
     * Reverses the y direction by multiplying it by -1
     */
    public void bounceY() {
        ySpeed *= -1;
    }

    /**
     * Draw the ball in its current location on a Graphics object
     *
     * @param g The GraphicsContext object to draw on
     */
    public void draw(GraphicsContext g) {
        g.setFill(c);
        g.fillOval((int) Math.round(x), (int) Math.round(y), size, size);
    }

    /**
     * @return the current x location
     */
    public double getX() {
        return x;
    }

    /**
     * @return the current y location
     */
    public double getY() {
        return y;
    }

    /**
     * @return the size of the ball
     */
    public int getSize() {
        return size;
    }

}

【问题讨论】:

  • 最好的方法是使用JavaFX'sAnimation类的东西。我的经验法则是,当任务长时间运行但与GUI 无关时,使用Thread。当任务长时间运行并使用GUI时,使用Animation
  • 感谢您迄今为止的回复,但正如我所说,我不是在寻找“正确”的方式来做到这一点。我之所以选择这种方法,是因为这对我的学生目前正在接受教育是正确的(他们对循环和学习基本的 OO 感到满意,但还没有接触过事件驱动的编程)。但我很困惑为什么它似乎会在一段时间后冻结 FX 屏幕更新。
  • 刚刚运行了您的代码。它在我的机器上运行良好。您经历冻结需要多长时间?
  • 哦,也许你的问题是由于没有使用Platform.runlater();Thread更新GUI

标签: java multithreading animation javafx


【解决方案1】:

主要问题是 JavaFX 不是线程安全的 (https://docs.oracle.com/javafx/2/threads/jfxpub-threads.htm)。 这就是冻结的原因。对 UI 的每次访问、修改或绘制都应该在 UI 线程上完成。 这可以通过Platform.runLater() 完成。

Platform.runLater(() -> {
  /*your code here*/
});

【讨论】:

    【解决方案2】:

    我仍然不明白,如果正确的方法如此微不足道,而且实际上与您的代码非常接近,那么您为什么坚持教以错误的方式执行此操作。 只需用 AnimationTimer 替换所有线程。这是您更新的主要代码。其余的就和以前一样。

    package week6code;
    
    import javafx.animation.AnimationTimer;
    import javafx.application.Application;
    import javafx.scene.Group;
    import javafx.scene.Scene;
    import javafx.scene.canvas.Canvas;
    import javafx.scene.canvas.GraphicsContext;
    import javafx.scene.paint.Color;
    import javafx.stage.Stage;
    
    /**
     * Bouncing Balls exercise starter
     *
     * @author Sam Scott
     */
    public class BouncingBallsDoneRight extends Application {
    
        /**
         * Sets up the stage and starts the main thread. Your drawing code should
         * NOT go here.
         *
         * @param stage The first stage
         */
        @Override
        public void start(Stage stage) {
            stage.setTitle("Bouncing Balls!"); // window title here
            Canvas canvas = new Canvas(400, 300); // canvas size here
            Group root = new Group();
            Scene scene = new Scene(root);
            root.getChildren().add(canvas);
            stage.setScene(scene);
            stage.show();
            GraphicsContext gc = canvas.getGraphicsContext2D();
    
            // This code starts an AnimationTimer which will run your animation
            AnimationTimer at = new AnimationTimer() {          
                Ball ball = new Ball(100, 50, -1, -1, 10, Color.RED);
                @Override
                public void handle(long arg0) {
                    // draw screen 
                    gc.setFill(Color.YELLOW);
                    gc.fillRect(0, 0, 400, 300);
                    ball.draw(gc);
    
                    // moving
                    ball.moveOneStep();
    
                    // bouncing
                    if (ball.getX() <= 0 || ball.getX() >= 400 - (ball.getSize() - 1)) {
                        ball.bounceX();
                    }
                    if (ball.getY() <= 0 || ball.getY() >= 300 - (ball.getSize() - 1)) {
                        ball.bounceY();
                    }
                }
            };
            at.start();
        }
    
        /**
         * Launches the app
         *
         * @param args unused
         */
        public static void main(String[] args) {
            launch(args);
        }
    }
    

    我尽量与您的原始代码保持一致。该任务的更好解决方案是使用场景图而不是画布。

    【讨论】:

    • 没错,它适用于这个例子,因为所有的动画状态变量都包裹在 Ball 对象中,但在课程的早期(当他们还没有接触到 OO 时)我不这么认为将是可能的。它也不再是如何使用循环的好例子。有时当你试图教学生解决问题并逐步培养学生时,你必须小心你的方式,以免不必要的细节压倒他们,有时你必须允许他们做让纯粹主义者感到不安的事情。这不是一门 FX 课程,我只是用 FX 给他们一些有趣的例子。
    • 作为老师,我理解 Sam 尽可能简单的原因。不幸的是,没有教过真正初学者的经验的有经验的程序员无法理解这个原因。
    猜你喜欢
    • 1970-01-01
    • 2020-11-23
    • 2014-10-05
    • 1970-01-01
    • 2018-02-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-08-02
    相关资源
    最近更新 更多