Swing Animation Running Extremely Slow

Swing animation running extremely slow

Based on this previous answer, the example below simulates a fleet of three cabs moving randomly on a rectangular grid. A javax.swing.Timer drives the animation at 5 Hz. The model and view are tightly coupled in CabPanel, but the animation may provide some useful insights. In particular, you might increase the number of cabs or lower the timer delay.

image

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;

/**
* @see https://stackoverflow.com/a/14887457/230513
* @see https://stackoverflow.com/questions/5617027
*/

public class FleetPanel extends JPanel {

private static final Random random = new Random();
private final MapPanel map = new MapPanel();
private final JPanel control = new JPanel();
private final List<CabPanel> fleet = new ArrayList<CabPanel>();
private final Timer timer = new Timer(200, null);

public FleetPanel() {
super(new BorderLayout());
fleet.add(new CabPanel("Cab #1", Hue.Cyan));
fleet.add(new CabPanel("Cab #2", Hue.Magenta));
fleet.add(new CabPanel("Cab #3", Hue.Yellow));
control.setLayout(new GridLayout(0, 1));
for (CabPanel cp : fleet) {
control.add(cp);
timer.addActionListener(cp.listener);
}
this.add(map, BorderLayout.CENTER);
this.add(control, BorderLayout.SOUTH);
}

public void start() {
timer.start();
}

private class CabPanel extends JPanel {

private static final String format = "000000";
private final DecimalFormat df = new DecimalFormat(format);
private JLabel name = new JLabel("", JLabel.CENTER);
private Point point = new Point();
private JLabel position = new JLabel(toString(point), JLabel.CENTER);
private int blocks;
private JLabel odometer = new JLabel(df.format(0), JLabel.CENTER);
private final JComboBox colorBox = new JComboBox();
private final JButton reset = new JButton("Reset");
private final ActionListener listener = new ActionListener() {

@Override
public void actionPerformed(ActionEvent e) {
int ds = random.nextInt(3) - 1;
if (random.nextBoolean()) {
point.x += ds;
} else {
point.y += ds;
}
blocks += Math.abs(ds);
update();
}
};

public CabPanel(String s, Hue hue) {
super(new GridLayout(1, 0));
name.setText(s);
this.setBackground(hue.getColor());
this.add(map, BorderLayout.CENTER);
for (Hue h : Hue.values()) {
colorBox.addItem(h);
}
colorBox.setSelectedIndex(hue.ordinal());
colorBox.addActionListener(new ActionListener() {

@Override
public void actionPerformed(ActionEvent e) {
Hue h = (Hue) colorBox.getSelectedItem();
CabPanel.this.setBackground(h.getColor());
update();
}
});
reset.addActionListener(new ActionListener() {

@Override
public void actionPerformed(ActionEvent e) {
point.setLocation(0, 0);
blocks = 0;
update();
}
});
this.add(name);
this.add(odometer);
this.add(position);
this.add(colorBox);
this.add(reset);
}

private void update() {
position.setText(CabPanel.this.toString(point));
odometer.setText(df.format(blocks));
map.repaint();
}

private String toString(Point p) {
StringBuilder sb = new StringBuilder();
sb.append(Math.abs(p.x));
sb.append(p.x < 0 ? " W" : " E");
sb.append(", ");
sb.append(Math.abs(p.y));
sb.append(p.y < 0 ? " N" : " S");
return sb.toString();
}
}

private class MapPanel extends JPanel {

private static final int SIZE = 16;

public MapPanel() {
this.setPreferredSize(new Dimension(32 * SIZE, 32 * SIZE));
this.setBackground(Color.lightGray);
}

@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
int w = this.getWidth();
int h = this.getHeight();
g2d.setColor(Color.gray);
for (int col = SIZE; col <= w; col += SIZE) {
g2d.drawLine(col, 0, col, h);
}
for (int row = SIZE; row <= h; row += SIZE) {
g2d.drawLine(0, row, w, row);
}

for (CabPanel cp : fleet) {
Point p = cp.point;
int x = SIZE * (p.x + w / 2 / SIZE) - SIZE / 2;
int y = SIZE * (p.y + h / 2 / SIZE) - SIZE / 2;
g2d.setColor(cp.getBackground());
g2d.fillOval(x, y, SIZE, SIZE);
}
}
}

public enum Hue {

Cyan(Color.cyan), Magenta(Color.magenta), Yellow(Color.yellow),
Red(Color.red), Green(Color.green), Blue(Color.blue),
Orange(Color.orange), Pink(Color.pink);
private final Color color;

private Hue(Color color) {
this.color = color;
}

public Color getColor() {
return color;
}
}

private static void display() {
JFrame f = new JFrame("Dispatch");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
FleetPanel fp = new FleetPanel();
f.add(fp);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
fp.start();
}

public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {

@Override
public void run() {
display();
}
});
}
}

Simple Java Swing Animation Lag

First and foremost, get that timer.start() out of your paintComponent method since it does not belong there, and serves no purpose other than to slow the rendering down. Instead you should call that method once, and once only.

Next, 5 mSecs may be an unrealistic timer delay. Experiment with this number, but expect to get a decent functioning somewhere near 10 to 15 mSecs.

Next, base your position change on actual time slice differences that have been measured, not on what you're hoping the timer is doing.

import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;

public class Animation extends JPanel {
private static final int TIMER_DELAY = 16;
private static final double Y_VELOCITY = 0.05;
private double dY = 0.0;
private Timer timer = new Timer(TIMER_DELAY, new TimerListener());

public Animation() {
timer.start();
}

public void paintComponent(Graphics g) {

super.paintComponent(g);

g.setColor(Color.black);
g.fillRect(50, (int) dY, 50, 50);
// timer.start();
}

private class TimerListener implements ActionListener {
private long prevTime;

@Override
public void actionPerformed(ActionEvent e) {
if (prevTime == 0L) {
repaint();
prevTime = System.currentTimeMillis();
} else {
long currentTime = System.currentTimeMillis();
long deltaTime = currentTime - prevTime;
double deltaY = Y_VELOCITY * deltaTime;
dY += deltaY;
prevTime = currentTime;
repaint();
}

}
}

public static void main(String[] args) {

Animation drawPanel = new Animation();

JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(500, 500);
frame.setVisible(true);
frame.add(drawPanel);
}
}

Swing animation flickers and makes GUI slow to respond

The slowness comes from two related problems, one simple and one more complex.

Problem #1: paint vs. repaint

From the
JComponent.paint docs:

Invoked by Swing to draw components.
Applications should not invoke paint directly, but should instead use the repaint method to schedule the component for redrawing.

So the canvas.paint() line at the end of Ball.move must go.

You want to call
Component.repaint
instead...
but just replacing the paint with repaint will reveal the second problem, which prevents the ball from even appearing.

Problem #2: Animating inside the ActionListener

The ideal ActionListener.actionPerformed method changes the program's state and returns as soon as possible, using lazy methods like repaint to let Swing schedule the actual work for whenever it's most convenient.

In contrast, your program does basically everything inside the actionPerformed method, including all the animation.

Solution: A Game Loop

A much more typical structure is to start a
javax.swing.Timer
when your GUI starts, and just let it run
"forever",
updating your simulation's state every tick of the clock.

public BounceFrame()
{
// Original code here.
// Then add:
new javax.swing.Timer(
10, // Your timeout from `addBall`.
new ActionListener()
{
public void actionPerformed(final ActionEvent ae)
{
canvas.moveBalls(); // See below for this method.
}
}
).start();
}

In your case, the most important
(and completely missing)
state is the
"Have we started yet?"
bit, which can be stored as a boolean in BallCanvas.
That's the class that should do all the animating, since it also owns the canvas and all the balls.

BallCanvas gains one field, isRunning:

private boolean isRunning = false;  // new field

// Added generic type to `balls` --- see below.
private java.util.List<Ball> balls = new ArrayList<Ball>();

...and a setter method:

public void setRunning(boolean state)
{
this.isRunning = state;
}

Finally, BallCanvas.moveBalls is the new
"update all the things"
method called by the Timer:

public void moveBalls()
{
if (! this.isRunning)
{
return;
}
for (final Ball b : balls)
{
// Remember, `move` no longer calls `paint`... It just
// updates some numbers.
b.move();
}
// Now that the visible state has changed, ask Swing to
// schedule repainting the panel.
repaint();
}

(Note how much simpler iterating over the balls list is now that the list has a proper generic type.
The loop in paintComponent could be made just as straightforward.)

Now the BounceFrame.addBall method is easy:

public void addBall()
{
Ball b = new Ball(canvas);
canvas.add(b);
this.canvas.setRunning(true);
}

With this setup, each press of the space bar adds another ball to the simulation.
I was able to get over 100 balls bouncing around on my 2006 desktop without a hint of flicker.
Also, I could exit the application using the 'X' button or Alt-F4, neither of which responded in the original version.

If you find yourself needing more performance
(or if you just want a better understanding of how Swing painting works),
see
"Painting in AWT and Swing:
Good Painting Code Is the Key to App Performance"
by Amy Fowler.

How to fix animation lags in Java?

You describe your issue pretty vaguely.

In principle, Swing isn't all that well suited for animation - speed isn't the principal problem - rather its the lack of a possibility to control when exactly the graphics are updated on screen.

Swing does not synchronize rendering with screen refresh. While it does double buffer (by default), it can (and does) still happen that the graphics are refreshed while the screen is displayed, displaying (for example) in the top half of the component frame N, while frame N+1 in the lower half (because the frame update happened while the screen refresh was somewhere in the middle of the screen).

The only solution I know of is switching to full screen mode (http://docs.oracle.com/javase/tutorial/extra/fullscreen/), where rendering may be beam synchronized. There is no solution for windowed display (to my knowledge).

Java Animation programs running jerky in Linux

Video acceleration is enabled by default in Windows, but is not enabled by default in Linux. (This has been true for many years now; I could have sworn this default was changed for recent Java releases, but evidently I was wrong.)

You can enable OpenGL to get accelerated performance:

public static void main( String[] args ) {
System.setProperty("sun.java2d.opengl", "true");

JFrame window = new JFrame();

Alternatively, you can set the property on the command line:

java -Dsun.java2d.opengl=true BouncingBall

Swing animation pause and resume

Use javax.swing.Timer to pace the animation. Typical controls that invoke the timer's start() and stop() methods are examined here. This fleet simulation, which uses such a Timer, may also be if interest.



Related Topics



Leave a reply



Submit