Java FX屏幕更新动画期间的冻结

时间:2018-02-20 14:58:47

标签: java multithreading animation javafx

你们中的任何一位FX Gurus能告诉我为什么这段代码会冻结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;
    }

}

2 个答案:

答案 0 :(得分: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);
    }
}

我尽量保持尽可能接近原始代码。对该任务更好的解决方案是使用场景图而不是画布。

答案 1 :(得分:0)

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

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