简体   繁体   中英

Java Swing Movement Stutter

I'm experiencing stuttering while moving a block in Java Swing.

The following code will show the problem. When run, I'd like the box to move smoothly without any stuttering. Is there any way to achieve this in Swing? Or should I just move on to JavaFX?

MovementStutter.java

package movementstutter;

import javax.swing.JFrame;

public class MovementStutter {

public static void main(String[] args) {
    JFrame win = new JFrame("Stutter Demo");
    win.getContentPane().add(new GamePanel());
    win.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    win.setResizable(false);
    win.pack();
    win.setVisible(true);
}

}

GamePanel.java

package movementstutter;

import java.awt.Dimension;
import java.awt.Graphics;
import javax.swing.JPanel;

public class GamePanel extends JPanel implements Runnable {

private boolean running;
private Thread thread;
private double x;
private double y;
private double movementFactor;

public GamePanel() {
    setPreferredSize(new Dimension(640, 480));
    setFocusable(true);
    grabFocus();
    movementFactor = 1;
    running = true;
}

@Override
public void addNotify() {
    super.addNotify();
    if (thread == null) {
        thread = new Thread(this);
        thread.start();
    }
}

@Override
public void run() {
    long time = System.currentTimeMillis();
    while (running) {
        update();
        repaint();
        long sleep = 16 - (System.currentTimeMillis() - time);
        time += 16;
        if (sleep < 0) {
            sleep = 16;
        }
        try {
            Thread.sleep(sleep);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

private void update() {
    if (x < 0) {
        movementFactor = 1;
    } else if (x > 608) {
        movementFactor = -1;
    }
    x += movementFactor * 200 * 0.016;
}

@Override
public void paintComponent(Graphics g) {
    super.paintComponent(g);
    g.fillRect((int) x, (int) y, 32, 32);
}

}

You are updating your swing component in a thread that is not the event-dispatch thread. You shouldn't do that. To perform timed actions, you can use javax.swing.Timer .

For example, instead of implementing Runnable , make your panel implement ActionListener , remove the thread-related stuff, and add an action listener:

public void actionPerformed(ActionEvent e) {
    update();
    repaint();
}

Then, in the panel's constructor, you can create a Timer . A javax.swing.Timer makes sure the action is performed in the event-dispatch loop. And it also simplifies the timing management:

public GamePanel() {
    setPreferredSize(new Dimension(640, 480));
    setFocusable(true);
    grabFocus();
    movementFactor = 1;
    running = true;
    new Timer(16,this).start();
}

If you need to stop the timer at any point, then you should assign it to a field, say

Timer blockTimer = new Timer(16,this);

And then you just do

blockTimer.start();

in your constructor, and you can have a button whose action includes

blockTimer.stop();

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM