Although there are questions similar, I think mine is slightly different because of how I have my code set up. I have a JFrame within my main method. However, I only have JPanel in my constructor. I tried to make some of my variables static so that I could access them in the main method and say, for instance, if the x-coordinate of this graphic plus its width is greater than frame.getWidth().. but that won't work for some reason. I don't want to bombard anyone with code so I will just try to put the main information and if you need more, I'll update it.
package finalProj;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.geom.Ellipse2D;
public class nonaMaingamePractice extends JPanel implements ActionListener, KeyListener {
/**
*
*/
private static final long serialVersionUID = 1L;
private static Ellipse2D ellipse;
static Toolkit tools = Toolkit.getDefaultToolkit();
static int screenWidth = (int)(Math.round(tools.getScreenSize().getWidth()));
static int screenHeight = (int)(Math.round(tools.getScreenSize().getHeight()));
private static Rectangle paddleRect;
JLabel text = new JLabel("cool");
Timer timeMove = new Timer(1, this);
Timer timeBall = new Timer(10, new timeBall());
private static double x = screenWidth/2, y = (screenHeight*0.8), xx = 0, yy = 0, score = 0, Ox = screenWidth/2, Oy = screenHeight/2, Oyy = 0, width = 100, height = 30;
public nonaMaingamePractice(){
setLayout(new BorderLayout());
timeBall.start();
timeMove.start();
addKeyListener(this);
setFocusable(true);
JPanel panelNorth = makePanel();
panelNorth.setBackground(Color.CYAN);
add(panelNorth, BorderLayout.NORTH);
JLabel scoreLabel = new JLabel("Score: " + score);
panelNorth.add(scoreLabel);
}
public void paintComponent(Graphics g){
super.paintComponent(g);
g.setColor(Color.BLUE);
paddleRect = new Rectangle((int)x, (int)y, (int)width, (int)height);
ellipse = new Ellipse2D.Double(Ox, Oy+Oyy, 50, 50);
Graphics2D graphics = (Graphics2D)g;
graphics.fill(paddleRect);
graphics.fill(ellipse);
}
#Override
public void actionPerformed(ActionEvent e) {
x = x + xx;
y = y + yy;
if(x<0){
x=0;
xx=0;
}
repaint();
}
#Override
public void keyTyped(KeyEvent e) {
// TODO Auto-generated method stub
}
#Override
public void keyPressed(KeyEvent e) {
int c = e.getKeyCode();
if(c==KeyEvent.VK_RIGHT){
xx=1;
}else if(c==KeyEvent.VK_LEFT){
xx=-1;
}
}
#Override
public void keyReleased(KeyEvent e) {
xx=0;
}
protected JPanel makePanel() {
#SuppressWarnings("serial")
JPanel pane = new JPanel() {
#Override
public Dimension getPreferredSize() {
return new Dimension(100, 30);
}
};
pane.setBackground(Color.CYAN);
return pane;
}
protected class timeBall implements ActionListener{
Timer timeWhateva = new Timer(100, this);
#Override
public void actionPerformed(ActionEvent e) {
try{
System.out.println(paddleRect.getX());
if(ellipse.intersects(paddleRect)){
timeWhateva.start();
Oy+=-1;
System.out.println(ellipse.getX() + " " + ellipse.getY());
}else if(!ellipse.intersects(paddleRect)){
Oyy+=1;
}
}catch(RuntimeException NullPointerException){
System.out.println(NullPointerException.getMessage());
}
repaint();
}
}
public static void main(String[] args){
nonaMaingamePractice main = new nonaMaingamePractice();
JFrame frame = new JFrame();
frame.add(main);
frame.setVisible(true);
frame.setTitle("Project 4 game");
frame.setSize(screenWidth, screenHeight);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
Okay, so there seems to a few things that are wrong.
First, don't rely on static for cross object communication, this is a really bad idea which will come back to bite you hard. Instead, pass information to the classes which need it.
Second, I'd focus on having a single Timer (or "main-loop") which is responsible for updating the current state of the game and scheduling repaints. This is the basic concept of Model-View-Controller paradigm
The first thing I'm going to do is take your code apart completely and rebuild it...
To start with, I want some kind of interface which provides information about the current state of the game and which I can pass instances of to other parts of the game in order for them to make decisions and update the state of the game...
public interface GameView {
public boolean isKeyRightPressed();
public boolean isKeyLeftPressed();
public Dimension getSize();
public void updateState();
}
This provides information about the state of the right and left keys, the size of the view and provides some basic functionality to request that the view update it's current state
Next, we need some way to model the state of the game...
import java.awt.Rectangle;
import java.awt.geom.Ellipse2D;
public interface GameModel {
public Rectangle getPaddle();
public Ellipse2D getBall();
public void ballWasMissed();
}
So, this basically maintains information about the paddle and ball and provides a means by which the "main game loop" can provide notification about the state of the game back to the model
Next, we need to the actual "main game loop" or controller. This is responsible for updating the state of the model and updating the view...
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Ellipse2D;
public class MainLoop implements ActionListener {
private GameView gameView;
private GameModel gameModel;
private int ballYDelta = 1;
public MainLoop(GameView gameView, GameModel gameModel) {
this.gameView = gameView;
this.gameModel = gameModel;
}
#Override
public void actionPerformed(ActionEvent e) {
Rectangle paddle = gameModel.getPaddle();
Ellipse2D ball = gameModel.getBall();
// Update the paddle position...
if (gameView.isKeyLeftPressed()) {
paddle.x--;
} else if (gameView.isKeyRightPressed()) {
paddle.x++;
}
// Correct for overflow...
if (paddle.x < 0) {
paddle.x = 0;
} else if (paddle.x + paddle.width > gameView.getSize().width) {
paddle.x = gameView.getSize().width - paddle.width;
}
// Update the ball position...
Rectangle bounds = ball.getBounds();
bounds.y += ballYDelta;
if (bounds.y < 0) {
bounds.y = 0;
ballYDelta *= -1;
} else if (bounds.y > gameView.getSize().height) {
// Ball is out of bounds...
// Notify the gameView so it knows what to do when the ball goes
// out of the game view's viewable, ie update the score...
// Reset ball position to just out side the top of the view...
gameModel.ballWasMissed();
bounds.y = -bounds.height;
} else if (paddle.intersects(bounds)) {
// Put the ball to the top of the paddle
bounds.y = paddle.y - bounds.height;
// Bounce
ballYDelta *= -1;
}
ball.setFrame(bounds);
// Update the view
gameView.updateState();
}
}
This is basically where we are making decisions about the current position of the objects and updating their positions. Here we check for "out-of-bounds" positions and update their states appropriately (for example, the ball can "bounce" and change directions)
The delta values are quite small, so you might want to play around with those
And finally, we need something that pulls it all together...
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
public class NonaMaingamePractice extends JPanel implements KeyListener, GameView {
/**
*
*/
private static final long serialVersionUID = 1L;
JLabel text = new JLabel("cool");
private Timer timeBall;
private GameModel model;
private boolean init = false;
private boolean rightIsPressed;
private boolean leftIsPressed;
public NonaMaingamePractice() {
setLayout(new BorderLayout());
addKeyListener(this);
setFocusable(true);
JPanel panelNorth = makePanel();
panelNorth.setBackground(Color.CYAN);
add(panelNorth, BorderLayout.NORTH);
JLabel scoreLabel = new JLabel("Score: " + 0);
panelNorth.add(scoreLabel);
addComponentListener(new ComponentAdapter() {
#Override
public void componentResized(ComponentEvent e) {
if (getWidth() > 0 && getHeight() > 0 && !init) {
init = true;
model = new DefaultGameModel(getSize());
timeBall = new Timer(40, new MainLoop(NonaMaingamePractice.this, model));
timeBall.start();
} else if (model != null) {
model.getPaddle().y = (getHeight() - model.getPaddle().height) - 10;
}
}
});
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.BLUE);
Graphics2D graphics = (Graphics2D) g;
if (model != null) {
graphics.fill(model.getPaddle());
graphics.fill(model.getBall());
}
}
#Override
public void keyTyped(KeyEvent e) {
// TODO Auto-generated method stub
}
#Override
public void keyPressed(KeyEvent e) {
int c = e.getKeyCode();
if (c == KeyEvent.VK_RIGHT) {
rightIsPressed = true;
} else if (c == KeyEvent.VK_LEFT) {
leftIsPressed = true;
}
}
#Override
public void keyReleased(KeyEvent e) {
int c = e.getKeyCode();
if (c == KeyEvent.VK_RIGHT) {
rightIsPressed = false;
} else if (c == KeyEvent.VK_LEFT) {
leftIsPressed = false;
}
}
protected JPanel makePanel() {
#SuppressWarnings("serial")
JPanel pane = new JPanel() {
#Override
public Dimension getPreferredSize() {
return new Dimension(100, 30);
}
};
pane.setBackground(Color.CYAN);
return pane;
}
#Override
public boolean isKeyRightPressed() {
return rightIsPressed;
}
#Override
public boolean isKeyLeftPressed() {
return leftIsPressed;
}
#Override
public void updateState() {
// Maybe update the score??
repaint();
}
public static void main(String[] args) {
NonaMaingamePractice main = new NonaMaingamePractice();
JFrame frame = new JFrame();
frame.add(main);
frame.setVisible(true);
frame.setTitle("Project 4 game");
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
Related
Here is the code for displaying circles with varying radius on a panel inside a frame with a given delay rate, but the code is showing the final output not the intermediate stages i.e., the circles are not appearing one by one but all the circles are coming at once as a final output. There may be some errors related to button action listeners and panel threads. The code is taking initial circle radius and the total number of iterations (the total number of circles to be displayed), radius of each next circle gets incremented by 10.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class ControlCircle extends JFrame {
private JButton jbtEnlarge = new JButton("Start");
private JButton jbtShrink = new JButton("Stop");
private CirclePanel canvas = new CirclePanel();
private int radius = 0;
private int iter;
public ControlCircle() {
JPanel panel = new JPanel();
JPanel jp = new JPanel();
jp.setPreferredSize(new Dimension(300, 0));
panel.add(jbtEnlarge);
panel.add(jbtShrink);
this.add(jp, BorderLayout.WEST);
this.add(canvas, BorderLayout.CENTER);
this.add(panel, BorderLayout.SOUTH);
final JTextField f1 = new JTextField(8),f2 = new JTextField(8);
jp.setLayout(new FlowLayout(FlowLayout.RIGHT, 50, 30));
jp.add(new JLabel("Radius"));
jp.add(f1);
jp.add(new JLabel("Iteration"));
jp.add(f2);
f1.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
radius = Integer.parseInt(new String(f1.getText()));
}
});
f2.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
iter = Integer.parseInt(new String(f2.getText()));
}
});
jbtEnlarge.addActionListener(new EnlargeListener());
jbtShrink.addActionListener(new ShrinkListener());
}
public static void main(String[] args) {
JFrame frame = new ControlCircle();
frame.setTitle("ControlCircle");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(800, 600);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
class EnlargeListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
canvas.enlarge();
}
}
class ShrinkListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
//canvas.shrink();
}
}
class CirclePanel extends JPanel {
private int r = radius;
public void enlarge() {
//radius += 2;
repaint();
}
public void shrink() {
radius -= 2;
repaint();
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for (int i = 0; i < iter; i++) {
g.drawOval(getWidth() / 2 - r, getHeight() / 2 - r, 2 * r, 2 * r);
try {
Thread.sleep(100);
} catch (Exception exp) {
}
r = r + 10;
}
r = 0;
}
}
}
The problem you're having is far to common.
Swing is a single threaded framework. This means that all UI related interactions must occur within the context of this thread (AKA the Event Dispatching Thread).
The EDT is responsible for, amongst other things, dispatching repaint requests. If any part of your code stops this thread (block I/O, time consuming process, Thread.sleep), the EDT will be unable to process any new events.
Have a read through Concurrency in Swing for more details.
You now face two issues...
You can't block the EDT
You can't update the UI from any thread other then the EDT.
Luckily, there are a number of solutions. The simplest is using a javax.swing.Timer.
This timer triggers it's tick events within the EDT but waits within it's own thread...
import com.sun.org.apache.bcel.internal.generic.LSTORE;
import java.awt.AlphaComposite;
import java.awt.BorderLayout;
import java.awt.Composite;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Droplets {
public static void main(String[] args) {
new Droplets();
}
public Droplets() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new DropletPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
protected static final int MAX_RADIUS = 50;
protected static final int GROWTH_RATE = 1;
public class DropletPane extends JPanel {
private List<Droplet> droplets;
public DropletPane() {
droplets = new ArrayList<>(25);
addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
droplets.add(new Droplet(e.getPoint()));
}
});
Timer timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
for (Droplet droplet : droplets.toArray(new Droplet[droplets.size()])) {
droplet.grow();
if (droplet.getRadius() >= MAX_RADIUS) {
droplets.remove(droplet);
}
}
repaint();
}
});
timer.setRepeats(true);
timer.setCoalesce(true);
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
Composite comp = g2d.getComposite();
for (Droplet droplet : droplets) {
float alpha = 1f - ((float) droplet.getRadius() / (float) MAX_RADIUS);
g2d.setComposite(AlphaComposite.SrcOver.derive(alpha));
Point p = droplet.getLocation();
int radius = droplet.getRadius();
g2d.drawOval(p.x - (radius / 2), p.y - (radius / 2), radius, radius);
g2d.setComposite(comp);
}
g2d.dispose();
}
}
public class Droplet {
private Point p;
private int radius;
public Droplet(Point p) {
this.p = p;
}
public Point getLocation() {
return p;
}
public int getRadius() {
return radius;
}
public void grow() {
radius += GROWTH_RATE;
if (radius > MAX_RADIUS) {
radius = MAX_RADIUS;
}
}
}
}
Extended Example
This example will, when you click the "Start" button, create a random number of droplets at a random interval (between each droplet). You can press start multiple times and it will compound the output.
import static droplets.Droplets.MAX_RADIUS;
import java.awt.AlphaComposite;
import java.awt.BorderLayout;
import java.awt.Composite;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagLayout;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingWorker;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Droplets02 {
public static void main(String[] args) {
new Droplets02();
}
public Droplets02() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new DropletPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
protected static final int MAX_RADIUS = 50;
protected static final int GROWTH_RATE = 1;
public interface Pool {
public void addDroplet(Droplet droplet);
public Dimension getSize();
}
public class DropletPane extends JPanel implements Pool {
private List<Droplet> droplets;
private Timer timer;
public DropletPane() {
setLayout(new GridBagLayout());
JButton button = new JButton("Start");
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
new DropletWorker(DropletPane.this).execute();
}
});
add(button);
droplets = new ArrayList<>(25);
timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (!droplets.isEmpty()) {
for (Droplet droplet : droplets.toArray(new Droplet[droplets.size()])) {
droplet.grow();
if (droplet.getRadius() >= MAX_RADIUS) {
droplets.remove(droplet);
}
}
if (droplets.isEmpty()) {
((Timer) e.getSource()).stop();
}
repaint();
}
}
});
timer.setRepeats(true);
timer.setCoalesce(true);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
Composite comp = g2d.getComposite();
for (Droplet droplet : droplets) {
float alpha = 1f - ((float) droplet.getRadius() / (float) MAX_RADIUS);
g2d.setComposite(AlphaComposite.SrcOver.derive(alpha));
Point p = droplet.getLocation();
int radius = droplet.getRadius();
g2d.drawOval(p.x - (radius / 2), p.y - (radius / 2), radius, radius);
g2d.setComposite(comp);
}
g2d.dispose();
}
#Override
public void addDroplet(Droplet droplet) {
if (!timer.isRunning()) {
timer.start();
}
droplets.add(droplet);
}
}
public class Droplet {
private Point p;
private int radius;
public Droplet(Point p) {
this.p = p;
}
public Point getLocation() {
return p;
}
public int getRadius() {
return radius;
}
public void grow() {
radius += GROWTH_RATE;
if (radius > MAX_RADIUS) {
radius = MAX_RADIUS;
}
}
}
public class DropletWorker extends SwingWorker<Void, Droplet> {
private Pool pool;
public DropletWorker(Pool pool) {
this.pool = pool;
}
public Pool getPool() {
return pool;
}
protected int random(int minRange, int maxRange) {
return minRange + (int) (Math.round(Math.random() * (maxRange - minRange)));
}
#Override
protected Void doInBackground() throws Exception {
int dropCount = random(1, 100);
Pool pool = getPool();
Dimension size = pool.getSize();
for (int index = 0; index < dropCount; index++) {
Thread.sleep(random(10, 1000));
int x = random(0, size.width);
int y = random(0, size.height);
Droplet droplet = new Droplet(new Point(x, y));
publish(droplet);
}
return null;
}
#Override
protected void process(List<Droplet> chunks) {
for (Droplet droplet : chunks) {
getPool().addDroplet(droplet);
}
}
}
}
Animation Basics
You need three things to perform animation.
A Start state
A Target state
A delta or time range.
(You also need some way to store the current state)
The start and target states are self explanatory, they describe where you are now and where you want to change to.
The delta would be the amount to apply to the current state at each "time interval" (or tick) until you reach the delta.
Or
The time range would be the amount of time you want to use to move from the start state to the end state.
The delta approach is the simpler mechanism, but isn't nearly as flexible as the time range approach...
Once you have these basic elements set up, you need some kind of "tick" that is triggered at regular intervals which allows you to calculate the current state, which is either a linear movement from the start state to the target state (delta) or a progression of change of over time (time range)
A final, full working rework
Apart from you're attempt to block the EDT within the paint method and failing to following the Initial Thread requirements of Swing, the only other, significant, problem I found was your reliance on the radius and iter values.
Basically, these were never getting set UNLESS you pressed the Enter key...which I wasn't.
This example uses the code that you posted and the ideas from the first example...
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class ControlCircles extends JFrame {
private JButton jbtEnlarge = new JButton("Start");
private JButton jbtShrink = new JButton("Stop");
private CirclePanel canvas = new CirclePanel();
private JTextField f1 = new JTextField(8);
private JTextField f2 = new JTextField(8);
public ControlCircles() {
JPanel panel = new JPanel();
JPanel jp = new JPanel();
jp.setPreferredSize(new Dimension(300, 0));
panel.add(jbtEnlarge);
panel.add(jbtShrink);
this.add(jp, BorderLayout.WEST);
this.add(canvas, BorderLayout.CENTER);
this.add(panel, BorderLayout.SOUTH);
jp.setLayout(new FlowLayout(FlowLayout.RIGHT, 50, 30));
jp.add(new JLabel("Radius"));
jp.add(f1);
jp.add(new JLabel("Iteration"));
jp.add(f2);
jbtEnlarge.addActionListener(new EnlargeListener());
jbtShrink.addActionListener(new ShrinkListener());
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new ControlCircles();
frame.setTitle("ControlCircle");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(800, 600);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
class EnlargeListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
int radius = Integer.parseInt(f1.getText());
int iter = Integer.parseInt(f2.getText());
canvas.start(radius, iter);
}
}
class ShrinkListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
//canvas.shrink();
}
}
class CirclePanel extends JPanel {
private int radius;
private int iterations;
private int iteration;
private List<Integer> circles;
private Timer timer;
public CirclePanel() {
circles = new ArrayList<>(25);
timer= new Timer(100, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
iteration++;
if (iteration < iterations) {
circles.add(radius);
radius += 10;
} else {
((Timer)e.getSource()).stop();
}
repaint();
}
});
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int width = getWidth() - 1;
int height = getHeight()- 1;
g.drawRect(0, 0, width, height);
for (Integer radius : circles) {
int x = (width - radius) / 2;
int y = (height - radius) / 2;
g.drawOval(x, y, radius, radius);
}
}
public void start(int radius, int iter) {
timer.stop();
circles.clear();
this.radius = radius;
iterations = iter;
iteration = 0;
System.out.println("radius = " + radius);
System.out.println("iterations = " + iterations);
timer.start();
}
}
}
This code works based on the description of your problem by correcting the common mistakes with animation in Swing but some of your code didn't quite make sense to me (ie enlarge and shrink) so I focused on the description your provided.
The idea is to control the drawing animation on the panel used as a canvas with the buttons Start, Stop and I added Continue and Reset additional controls to better explain the idea. These buttons control the animation thread execution thus drawing circles on the drawing surface. the drawing surface I separated as inner class that has only function to draw whatever performed. Another idea that the approach is taken to draw the circles one by one incrementally until it finishes drawing thus used incremental painting.
I have used the code from the above and changed it a little to support my ideas. If you need more and usually better examples look at this article.
The code is below, I didn't polish it enough to have a production wise look and feel but for demonstration purpose only.
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
public class ControlCircle extends JFrame implements Runnable {
private JButton jbtStart = new JButton("Start");
private JButton jbtStop = new JButton("Stop");
private JButton jbtContinue = new JButton("Continue");
private JButton jbtReset = new JButton("Reset");
private CirclePanel canvas = new CirclePanel();
private JTextField f1;
private int radius = 0;
private JTextField f2;
private int iter;
protected boolean incrementalPainting;
/**
* Flag indicates that a thread is suspended
*/
private boolean suspended = false;
/**An instance of the class Thread.*/
private Thread thread = null;
public ControlCircle() {
JPanel panel = new JPanel();
JPanel jp = new JPanel();
jp.setPreferredSize(new Dimension(300, 0));
panel.add(jbtStart);
panel.add(jbtStop);
panel.add(jbtContinue);
panel.add(jbtReset);
this.add(jp, BorderLayout.WEST);
this.add(canvas, BorderLayout.CENTER);
this.add(panel, BorderLayout.SOUTH);
f1 = new JTextField(8);
f2 = new JTextField(8);
jp.setLayout(new FlowLayout(FlowLayout.RIGHT, 50, 30));
jp.add(new JLabel("Radius"));
jp.add(f1);
jp.add(new JLabel("Iteration"));
jp.add(f2);
jbtStart.addActionListener(new StartListener());
jbtStop.addActionListener(new StopListener());
jbtContinue.addActionListener(new ContinueListener());
jbtReset.addActionListener(new ResetListener());
}
public static void main(String[] args) {
JFrame frame = new ControlCircle();
frame.setTitle("ControlCircle");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(800, 600);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
class StartListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
if (thread == null) {
repaint();
startThread();
}
}
}
class StopListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
if (thread != null){
mySuspend();
}
}
}
class ContinueListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
myResume();
}
}
class ResetListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
if (thread != null) {
stopThread();
}
repaint();
}
}
/**
* my Suspend
*/
private void mySuspend() {
System.out.println("mySyspend()");
suspended = true;
}
/**
* my Resume
*/
private synchronized void myResume(){
System.out.println("myResume()");
suspended = false;
notifyAll();
}
public void run(){
System.out.println("run() - started");
Thread me = Thread.currentThread();
while (thread == me) {
radius = Integer.parseInt(f1.getText());
iter = Integer.parseInt(f2.getText());
for (int i = 0; i < iter; i++) {
if (thread == null) return;
incrementalPainting = true;
myRepaint();
try {
Thread.sleep(1000);
}
catch(InterruptedException e){}
radius += 10;
}
if(thread != null) thread = null; // exiting while
}
System.out.println("run() - exiting");
}
/**
* start Thread
*/
private void startThread(){
System.out.println("startThread()");
if(thread == null){
thread = new Thread(this);
thread.start();
}
}
/**
* stop Thread
*/
private synchronized void stopThread() {
System.out.println("stopThread()");
thread = null; // exiting from while
if (suspended) {
suspended = false;
notify();
}
}
/**
* This is called from the run method to invoke painting.
*/
private void myRepaint() {
System.out.println("myRepaint()");
incrementalPainting = true;
repaint();
synchronized (this) {
while (incrementalPainting) {
System.out.println("wait while incremental painting");
try {
wait();
} catch (InterruptedException e) {
System.out.println("interrupted");
}
}
}
suspend();
}
/**
* This method should place somewhere when run() has started. Perfectly
* when repaint() performed.
*/
private void suspend(){
System.out.println("suspend()");
synchronized (this) {
while (suspended) {
System.out.println("wait while suspended");
try {
wait();
} catch (InterruptedException e) {
System.out.println("interrupted");
}
}
}
}
public synchronized void myPaint(Graphics g) {
if (g == null){
if (incrementalPainting){
incrementalPainting = false;
notifyAll();
}
return;
}
if (incrementalPainting){
myDraw(g);
incrementalPainting = false;
notifyAll();
}
else {
myDraw(g);
}
}
public void myDraw(Graphics g){
g.drawOval(getWidth() / 2 - radius, getHeight() / 2 - radius, 2 * radius, 2 * radius);
}
protected final class CirclePanel extends JPanel {
//Offscreen buffer of this canvas
private BufferedImage backBuffer = null;
public void paintComponent (Graphics g) {
System.out.println("incrementalPainting="+incrementalPainting);
// First paint background
super.paintComponent(g);
Dimension d = this.getSize();
if (! incrementalPainting)
backBuffer = (BufferedImage) this.createImage(d.width, d.height);
Graphics2D g2 = backBuffer.createGraphics();
if (! incrementalPainting){
g2.setColor(Color.WHITE);
g2.fillRect(0,0, d.width, d.height);
}
myPaint(g2);
g.drawImage(backBuffer, 0, 0, this);
}
}
}
I have been working on this snake project, and dont really understand why the keylistener isnt actually changing the variable char key. I have some other examples of keylisteners, and they all work properly, but for some reason mine isnt working. Some help would be appreciated. Thanks a lot for the help.
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Toolkit;
import java.awt.event.KeyListener;
import java.awt.geom.Ellipse2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.RepaintManager;
public class Main extends JPanel implements Runnable {
/*
*
* SIZE OF BOARD
*
*/
static GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
static GraphicsDevice[] gs = ge.getScreenDevices();
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
private static final int DIM_WIDTH = Toolkit.getDefaultToolkit().getScreenSize().width;
private static final int DIM_HEIGHT = Toolkit.getDefaultToolkit().getScreenSize().height;
static JFrame frame = new JFrame();
static JPanel panel = new JPanel();
static Snake s = new Snake();
static Main main = new Main();
KeyListener listener = new Snake();
boolean black = true;
public Main() {
addKeyListener(listener);
}
#SuppressWarnings("deprecation")
public static void main(String[] args) {
//gs[0].setFullScreenWindow(frame);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setCursor(Cursor.CROSSHAIR_CURSOR);
frame.setSize(DIM_WIDTH, DIM_HEIGHT);
frame.add(main);
frame.setVisible(true);
(new Thread(new Main())).start();
}
// paints the panel
public void paint(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
/*
* Snake
*/
//Redraws Background
g2d.setColor(Color.black);
g2d.fillRect(0, 0, (int) screenSize.getWidth(), (int) screenSize.getHeight());
//Draws Border
g2d.setColor(Color.white);
g2d.fillRect(0,0, (int)screenSize.getWidth(), 1);
g2d.fillRect(0,0, 1, (int)screenSize.getHeight());
g2d.fillRect((int)screenSize.getWidth()-1, 1, 1, (int)screenSize.getHeight());
g2d.fillRect(0, (int)screenSize.getHeight()-86, (int)screenSize.getWidth(), 10);
//Draws Snake head
g2d.setColor(s.getColor());
g2d.fillRect(s.getX(), s.getY(), 30, 30);
}
// Creates Frame, and starts the game
#Override
public void run() {
while (!s.getIsDead()) {
move();
}
}
public void move() {
s.move();
s.death();
main.repaint();
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (Thread.interrupted()) {
return;
}
}
}
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
public class Snake implements KeyListener {
Color c = Color.green;
//Starting position of Snake
int x = 50;
int y = 50;
char key;
boolean dead = false;
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
public void move() {
x++;
}
public void death() {
if (x + 30 >= screenSize.getWidth() || y + 115 >= screenSize.getHeight() || y<=0 || x<=0) {
c = Color.red;
dead = true;
}
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public Color getColor() {
return c;
}
public boolean getIsDead() {
return dead;
}
#Override
public void keyTyped(KeyEvent e) {
// TODO Auto-generated method stub
}
#Override
public void keyPressed(KeyEvent e) {
key = 'w';
}
#Override
public void keyReleased(KeyEvent e) {
// TODO Auto-generated method stub
}
}
Change your constructor to
public Main() {
addKeyListener(listener);
setFocusable(true);
requestFocus();
}
But take a look at this question, you should not use KeyListeners.
java keylistener not called
As #azurefrog mentioned, your keyPressed method is setting key to 'w' every time. You need to use the KeyEvent passed in as a parameter to that method to get the key that was pressed. Your keyPressed method should look something like this:
#Override
public void keyPressed(KeyEvent e) {
key = e.getKeyChar();
}
I am a bit new to threading, so bear with me. All relevant classes will be below the text in one place for easier reference.
Backstory:
I created a simple pong-like game following this tutorial: http://www.edu4java.com/en/game/game1.html
Everything worked perfectly, then I made modifications to better understand how it all works. In the tutorial, there is a main method from which the animations are played continuously. According to the tutorial author, Thread.sleep(10) "...tells the processor that the thread which is being run must sleep for 10 ms, which allows the processor to execute other threads and in particular the AWT-EventQueue thread which calls the paint method."
Now, my question is this:
(Just for fun and to practice Java,) I have created a "launcher" for all the various small programs and games I make. I have yet to get the pong game to work inside the launcher. Without a main method inside the pong frame, the animation never runs. I left the main method in in the code below, so that it works. How would I go about launching the animation from somewhere other than main?
Here's the code:
The Frame and main method:
package pongGame;
import javax.swing.*;
public class PongMainGUI extends JFrame
{
private static final int WINDOW_WIDTH = 500;
private static final int WINDOW_HEIGHT = 800;
private static AnimationPanel panel;
public PongMainGUI()
{
//This line sets the title, and, since it calls the super constructor, it calls setTitle().
super("Pong!");
panel = new AnimationPanel(this);
//This method simply makes the screen appear in the center of whatever size screen you are using.
setLocationRelativeTo(null);
setSize(WINDOW_WIDTH,WINDOW_HEIGHT);
add(panel);
setVisible(true);
}
public static void main(String args[]) throws InterruptedException
{
new PongMainGUI();
while(true)
{
System.out.println("PongMainGUI");
panel.repaint();
panel.move();
Thread.sleep(10);
}
}
}
The Animation Panel:
package pongGame;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.event.MouseInputListener;
#SuppressWarnings("serial")
public class AnimationPanel extends JPanel
{
PongMainGUI frame;
Ball ballClass;
Racquet racquetClass;
boolean bool = false;
public AnimationPanel(PongMainGUI frame)
{
this.frame = frame;
addMouseListener(new MouseListener()
{
#Override
public void mouseClicked(MouseEvent arg0)
{
}
#Override
public void mouseEntered(MouseEvent arg0)
{
}
#Override
public void mouseExited(MouseEvent arg0)
{
}
#Override
public void mousePressed(MouseEvent arg0)
{
}
#Override
public void mouseReleased(MouseEvent arg0)
{
}
});
addMouseMotionListener(new MouseMotionListener()
{
#Override
public void mouseDragged(MouseEvent e)
{
}
#Override
public void mouseMoved(MouseEvent e)
{
}
});
addKeyListener(new KeyListener()
{
#Override
public void keyPressed(KeyEvent e)
{
racquetClass.keyPressed(e);
}
#Override
public void keyReleased(KeyEvent e)
{
racquetClass.keyReleased(e);
}
#Override
public void keyTyped(KeyEvent e)
{
}
});
//This is needed to ensure that the keyboard will register properly and receive focus.
setFocusable(true);
ballClass = new Ball(this);
racquetClass = new Racquet(this);
}
public void move()
{
//ballClass.moveBall();
racquetClass.moveRacquet();
}
#Override
public void paint(Graphics g)
{
System.out.println("AnimationPanel paint method");
//This method clears the panel so it appears as if the circle is moving.
super.paint(g);
//Better version of Graphics.
Graphics2D g2d = (Graphics2D) g;
//This method turns antialiasing on, which cleans up the corners.
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
ballClass.paint(g2d);
racquetClass.paint(g2d);
}
public void gameOver()
{
System.out.println("Game over method");
JOptionPane.showMessageDialog(null, "Game Over", "Game Over", JOptionPane.YES_NO_OPTION);
System.exit(ABORT);
}
}
The Ball "sprite":
package pongGame;
import java.awt.Graphics2D;
import java.awt.Rectangle;
public class Ball
{
int xCoordinate = 0;
int yCoordinate = 0;
//1 = right movement, -1 = left
int xDirection = 1;
int yDirection = 1;
private final static byte ballWidth = 30;
private final static byte ballHeight = 30;
private AnimationPanel panel;
public Ball(AnimationPanel panel)
{
this.panel = panel;
}
public void paint(Graphics2D g2d)
{
//This creates the actual circle with a specified width and height.
//Because super.paint(g) is called at the start, a new circle is created each time.
g2d.fillOval(xCoordinate, yCoordinate, ballWidth, ballHeight);
System.out.println("Ball paint method");
moveBall();
}
//What this method does is add 1 to the x and y coordinates each time it's called. However, getWidth() and getHeight() are used to determine the current panel size, not the frame size.
//Then, whatever the width and/or height is is subtracted so the circle does not completely disappear from view.
public void moveBall()
{
if (xCoordinate + xDirection < 0)
{
xDirection = 1;
}
else if (xCoordinate + xDirection > panel.getWidth() - ballWidth)
{
xDirection = -1;
}
if (yCoordinate + yDirection < 0)
{
yDirection = 1;
}
else if (yCoordinate + yDirection > panel.getHeight() - ballHeight)
{
System.out.println("Ball moveBall method");
panel.gameOver();
}
if (collision() == true)
{
yDirection = -1;
yCoordinate = panel.racquetClass.getPaddleHeight() - ballHeight;
}
xCoordinate = xCoordinate + xDirection;
yCoordinate = yCoordinate + yDirection;
}
public Rectangle getBounds()
{
return new Rectangle(xCoordinate, yCoordinate, ballWidth, ballHeight);
}
private boolean collision()
{
return panel.racquetClass.getBounds().intersects(getBounds());
}
}
And finally, the Racquet "sprite":
package pongGame;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.KeyEvent;
public class Racquet
{
private AnimationPanel panel;
private int xCoordinate = 0;
//0 = no movement, 1 is right, -1 is left.
private byte direction = 0;
//All of the following values are in pixels.
private final static byte PADDLE_OFFSET = 100;
private final static byte PADDLE_WIDTH = 120;
private final static byte PADDLE_HEIGHT = 10;
public Racquet(AnimationPanel panel)
{
this.panel = panel;
}
public void moveRacquet()
{
if (xCoordinate + direction > 0 && xCoordinate + direction < panel.getWidth()-60)
xCoordinate = xCoordinate + direction;
}
public void paint(Graphics2D g)
{
g.fillRect(xCoordinate, getPaddleHeight(), PADDLE_WIDTH, PADDLE_HEIGHT);
//move();
}
public void keyReleased(KeyEvent e)
{
direction = 0;
}
public void keyPressed(KeyEvent e)
{
if (e.getKeyCode() == KeyEvent.VK_LEFT)
direction = -1;
if (e.getKeyCode() == KeyEvent.VK_RIGHT)
direction = 1;
}
public Rectangle getBounds()
{
return new Rectangle(xCoordinate, getPaddleHeight(), PADDLE_WIDTH, PADDLE_HEIGHT);
}
public int getPaddleHeight()
{
return panel.getHeight() - PADDLE_OFFSET;
}
}
This may or may not help, but this is the code for the launcher I wanted to use to open the game:
This is the "main menu":
package GUI;
import javax.swing.*;
import painter.MainPainterGUI;
import java.awt.*;
import java.awt.event.*;
/**
* This class serves to create the launcher gui for the program.
* It extends JFrame.
* #author Jackson Murrell
*/
#SuppressWarnings("serial")
public class LauncherGUI extends JFrame implements ActionListener
{
//A couple constants that are used for sizing things.
private final short WINDOW_HEIGHT = 225;
private final short WINDOW_WIDTH = 550;
private final byte BLANK_SPACE = 25;
//Panels to use for adding in components.
JPanel textPanel, buttonPanel, mainPanel;
//Buttons for user input and selection.
JButton calculator, colorChooser, timer, exit, primeNumberTester, game, painter;
//A text label that will be used for giving the user
//instructions on the program.
JLabel textLabel;
//A constructor to create the GUI components when an object of this class is created.
public LauncherGUI()
{
//This call's the parent method's (JFrame) setTitle method.
super("Omni-program");
//These methods set various options for the JFrame.
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
setSize(WINDOW_WIDTH, WINDOW_HEIGHT);
setLocationRelativeTo(null);
textPanel = new JPanel();
buttonPanel = new JPanel();
mainPanel = new JPanel();
calculator = new JButton("Calculator");
colorChooser = new JButton("Color Chooser");
timer = new JButton("Timer");
primeNumberTester = new JButton("Prime Number Tester");
game = new JButton("Games");
exit = new JButton("Exit Launcher and Programs");
painter = new JButton("Painter");
calculator.addActionListener(this);
colorChooser.addActionListener(this);
timer.addActionListener(this);
exit.addActionListener(this);
primeNumberTester.addActionListener(this);
game.addActionListener(this);
painter.addActionListener(this);
textLabel = new JLabel("Welcome to the launcher! Click the button for the mini-program you would like to run.", 0);
textPanel.add(Box.createVerticalStrut(BLANK_SPACE));
textPanel.add(textLabel);
buttonPanel.add(calculator);
buttonPanel.add(colorChooser);
buttonPanel.add(timer);
buttonPanel.add(primeNumberTester);
buttonPanel.add(game);
buttonPanel.add(painter);
buttonPanel.add(exit);
mainPanel.setLayout(new GridLayout(2,1));
mainPanel.add(textPanel);
mainPanel.add(buttonPanel);
//mainPanel.add(Box.createVerticalStrut(BLANK_SPACE));
add(mainPanel);
//pack();
//Having this line at the end instead of the top ensures that once everything is added it is all set to be visible.
setVisible(true);
}
//This method is required since ActionListener is implemented.
//It will be used to process user input.
#Override
public void actionPerformed(ActionEvent e)
{
if (e.getSource() == calculator)
{
new CalculatorGUI();
dispose();
}
else if (e.getSource() == colorChooser)
{
new ColorChooserGUI();
dispose();
}
else if(e.getSource() == timer)
{
new TimerGUI();
dispose();
}
else if (e.getSource() == primeNumberTester)
{
new PrimeNumberTesterGUI();
dispose();
}
else if(e.getSource() == exit)
{
System.exit(0);
}
else if(e.getSource() == painter)
{
new MainPainterGUI();
dispose();
}
else if(e.getSource() == game)
{
new GameLauncherGUI();
dispose();
}
}
}
Here's the actual game launcher:
package GUI;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
import pongGame.PongMainGUI;
public class GameLauncherGUI extends JFrame implements ActionListener
{
//A couple constants that are used for sizing things.
private final short WINDOW_HEIGHT = 225;
private final short WINDOW_WIDTH = 550;
private JButton adventureGame, pong, back;
private JLabel label;
private JPanel mainPanel, buttonPanel, textPanel;
public GameLauncherGUI()
{
//This call's the parent method's (JFrame) setTitle method.
super("Omni-program");
//These methods set various options for the JFrame.
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
setSize(WINDOW_WIDTH, WINDOW_HEIGHT);
setLocationRelativeTo(null);
adventureGame = new JButton("Adventure Game (Broken)");
adventureGame.addActionListener(this);
pong = new JButton("Pong");
pong.addActionListener(this);
back = new JButton("Back");
back.addActionListener(this);
label = new JLabel("Click the button below for the game you wish to play,\nor click back to go to the previous screen.");
mainPanel = new JPanel();
buttonPanel = new JPanel();
textPanel = new JPanel();
textPanel.add(label);
buttonPanel.add(adventureGame);
buttonPanel.add(pong);
buttonPanel.add(back);
mainPanel.add(textPanel);
mainPanel.add(buttonPanel);
add(mainPanel);
//Having this line at the end instead of the top ensures that once everything is added it is all set to be visible.
setVisible(true);
}
#Override
public void actionPerformed(ActionEvent e)
{
if(e.getSource() == back)
{
new LauncherGUI();
dispose();
}
else if(e.getSource() == pong)
{
new PongMainGUI();
dispose();
}
}
}
mainis a static method like others, so you can call it from your launcher:
PongMainGUI.main(null); // launch the pong game
However, note that, in order to avoid lots of trouble, Swing components must be created from the Event Dispatch Thread, as shown in this example. So you should wrap the content of your main method inside a Runnable and launch it with SwingUtilities.invokeLater().
However (again), by doing so, your Thread.sleep(10) will run on the EDT, blocking the GUI responsiveness again. Fortunately, Swing thought of that problem and created a utility called javax.swing.Timer that runs tasks periodically on the EDT (without blocking it):
public static void main(String args[])
{
SwingUtilities.invokeLater(new Runnable(){
public void run(){
new PongMainGUI();
Timer timer = new Timer(10, new ActionListener(){
public void actionPerformed(ActionEvent e){
System.out.println("PongMainGUI");
panel.repaint();
panel.move();
}
});
timer.start();
}
});
}
This main() method will run safely in standalone, or from your launcher.
I am trying to make a program that work like this:
In Window class every time I click on the button, the method panel2 of Panel is called: first it is drawing a first circle, then a second one (after the time defined in the timer). Then, I click again on the button, and it is drawing a fist circle, then a second one then a third one. etc.
The problem is that it when I click to obtain 3 circles appearing one after the other, the two first circles drawn at the previous step (before I pressed a second time the button) stay on the screen and only the third circle is drawn when i press the button (instead of having : first circle drawn, second circle drawn, third circle drawn). I hope I am clear.
Here is a simple code:
Window
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Window extends JFrame implements ActionListener{
int h = 2;
Panel b = new Panel();
JPanel container = new JPanel();
JButton btn = new JButton("Start");
JButton bouton = new JButton();
Panel boutonPane = new Panel();
public Window(){
this.setTitle("Animation");
this.setSize(300, 300);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setLocationRelativeTo(null);
container.setBackground(Color.white);
container.setLayout(new BorderLayout());
JPanel top = new JPanel();
btn.addActionListener(this);
top.add(btn);
container.add(top);
this.setContentPane(container);
this.setVisible(true);
}
public void window2(){
this.setTitle("ADHD");
this.setSize(1000,700);
this.setLocationRelativeTo(null);
if (h < 11){
boutonPane.panel2(h);
bouton.addActionListener(this);
boutonPane.add(bouton);
this.add(boutonPane);
this.setContentPane(boutonPane);
updateWindow2();
}
this.setVisible(true);
}
public void updateWindow2(){
boutonPane.panel2(h);
this.revalidate();
this.repaint();
}
public void actionPerformed(ActionEvent e){
if ((JButton) e.getSource() == btn){
System.out.println("pressed0");
window2();
}
if ((JButton) e.getSource() == bouton){
h++;
System.out.println("pressed" + h);
updateWindow2();
}
}
public static void main(String[] args){
Window w = new Window();
}
}
Panel
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JPanel;
import javax.swing.Timer;
public class Panel extends JPanel implements ActionListener{
int m;
int u=0;
int lgi, lrgi;
int [] ta;
Timer timer1 = new Timer(300, this);
Panel(){
}
public void panel2(int n){
m=n;
ta = new int [n];
for(int it=0; it<m;it++){
ta[it]=100*it;
}
timer1.start();
}
public void paintComponent(Graphics gr){
super.paintComponent(gr);
gr.setColor(Color.red);
for(int i=0;i<m;i++){
gr.fillOval(ta[i],ta[i], 150, 150);
}
}
#Override
public void actionPerformed(ActionEvent arg0) {
if(u<m){
u++;
revalidate();
repaint();
}
}
}
Your code needs use two int values to decide how many circles to draw and when:
The first int should be the count of current circles to draw, say called, currentCirclesToDraw.
The second int will be the number of circles to draw total.
If you use a List<Ellipse2D> like I suggest, then this number will be the size of the list. So if the List is called ellipseList, then the 2nd number will be ellipseList.size().
The first variable will be incremented in the timer up to the size of the list, but no larger, and will be used by paintComponent method to decide how many circles to draw.
Key point here: the first number, the currentCirclesToDraw, must be re-set to 0 when the button is pressed. This way your paintComponent method will start out drawing 0 circles, then 1, then 2, ...
For example, the paintComponent method could look like so:
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(CIRCLE_COLOR);
for (int i = 0; i < currentCirclesToDraw && i < ellipseList.size(); i++) {
g2.fill(ellipseList.get(i));
}
}
I use the second term in the for loop conditional statement, i < currentCirclesToDraw && i < ellipseList.size() as an additional fail-safe to be sure that we don't try to draw more circles then we have in our list.
My Timer's ActionListener would increment the currentCirclesToDraw variable and call repaint. It would stop the Timer once currentCirclesToDraw reaches the size of the ellipseList:
private class TimerListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
if (currentCirclesToDraw < ellipseList.size()) {
currentCirclesToDraw++;
repaint();
} else {
// stop the Timer
((Timer)e.getSource()).stop();
}
}
}
And my button's actionPerformed method would reset currentCirclesToDraw to 0, would add a new Ellipse2D to my ellipseList (if we've not yet reached the MAX_CIRCLE_INDEX), would call repaint() to clear the JPanel, and would construct and start the Timer:
public void actionPerformed(java.awt.event.ActionEvent arg0) {
currentCirclesToDraw = 0; // this is key -- reset the index used to control how many circles to draw
if (ellipseList.size() < MAX_CIRCLE_INDEX) {
double x = (ellipseList.size()) * CIRCLE_WIDTH / Math.pow(2, 0.5);
double y = x;
double w = CIRCLE_WIDTH;
double h = CIRCLE_WIDTH;
ellipseList.add(new Ellipse2D.Double(x, y, w, h));
}
repaint(); // clear image
new Timer(TIMER_DELAY, new TimerListener()).start();
};
Edit 3/30/14
Note it all can be put together like this:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.geom.Ellipse2D;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;
/**
* http://stackoverflow.com/a/22714405/522444
* http://stackoverflow.com/questions/22712655/repaint-in-panel-method-not-updated
* #author Pete
*
*/
#SuppressWarnings("serial")
public class TimerCircles extends JPanel {
private static final int PREF_W = 1000;
private static final int PREF_H = 700;
private static final Color CIRCLE_COLOR = Color.RED;
public static final int MAX_CIRCLE_INDEX = 11;
public static final int TIMER_DELAY = 300;
public static final int CIRCLE_WIDTH = 100;
private final List<Ellipse2D> ellipseList = new ArrayList<>();
private int currentCirclesToDraw = 0;
public TimerCircles() {
add(new JButton(new ButtonAction("New Circle", KeyEvent.VK_C)));
}
#Override
public Dimension getPreferredSize() {
return new Dimension(PREF_W, PREF_H);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(CIRCLE_COLOR);
for (int i = 0; i < currentCirclesToDraw && i < ellipseList.size(); i++) {
g2.fill(ellipseList.get(i));
}
}
private class ButtonAction extends AbstractAction {
public ButtonAction(String name, int mnemonic) {
super(name);
putValue(MNEMONIC_KEY, mnemonic);
}
public void actionPerformed(java.awt.event.ActionEvent arg0) {
currentCirclesToDraw = 0; // this is key -- reset the index used to control how many circles to draw
if (ellipseList.size() < MAX_CIRCLE_INDEX) {
double x = (ellipseList.size()) * CIRCLE_WIDTH / Math.pow(2, 0.5);
double y = x;
double w = CIRCLE_WIDTH;
double h = CIRCLE_WIDTH;
ellipseList.add(new Ellipse2D.Double(x, y, w, h));
}
repaint(); // clear image
new Timer(TIMER_DELAY, new TimerListener()).start();
};
}
private class TimerListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
if (currentCirclesToDraw < ellipseList.size()) {
currentCirclesToDraw++;
repaint();
} else {
// stop the Timer
((Timer)e.getSource()).stop();
}
}
}
private static void createAndShowGui() {
TimerCircles mainPanel = new TimerCircles();
JFrame frame = new JFrame("TimerCircles");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
Here is the code for displaying circles with varying radius on a panel inside a frame with a given delay rate, but the code is showing the final output not the intermediate stages i.e., the circles are not appearing one by one but all the circles are coming at once as a final output. There may be some errors related to button action listeners and panel threads. The code is taking initial circle radius and the total number of iterations (the total number of circles to be displayed), radius of each next circle gets incremented by 10.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class ControlCircle extends JFrame {
private JButton jbtEnlarge = new JButton("Start");
private JButton jbtShrink = new JButton("Stop");
private CirclePanel canvas = new CirclePanel();
private int radius = 0;
private int iter;
public ControlCircle() {
JPanel panel = new JPanel();
JPanel jp = new JPanel();
jp.setPreferredSize(new Dimension(300, 0));
panel.add(jbtEnlarge);
panel.add(jbtShrink);
this.add(jp, BorderLayout.WEST);
this.add(canvas, BorderLayout.CENTER);
this.add(panel, BorderLayout.SOUTH);
final JTextField f1 = new JTextField(8),f2 = new JTextField(8);
jp.setLayout(new FlowLayout(FlowLayout.RIGHT, 50, 30));
jp.add(new JLabel("Radius"));
jp.add(f1);
jp.add(new JLabel("Iteration"));
jp.add(f2);
f1.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
radius = Integer.parseInt(new String(f1.getText()));
}
});
f2.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
iter = Integer.parseInt(new String(f2.getText()));
}
});
jbtEnlarge.addActionListener(new EnlargeListener());
jbtShrink.addActionListener(new ShrinkListener());
}
public static void main(String[] args) {
JFrame frame = new ControlCircle();
frame.setTitle("ControlCircle");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(800, 600);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
class EnlargeListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
canvas.enlarge();
}
}
class ShrinkListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
//canvas.shrink();
}
}
class CirclePanel extends JPanel {
private int r = radius;
public void enlarge() {
//radius += 2;
repaint();
}
public void shrink() {
radius -= 2;
repaint();
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for (int i = 0; i < iter; i++) {
g.drawOval(getWidth() / 2 - r, getHeight() / 2 - r, 2 * r, 2 * r);
try {
Thread.sleep(100);
} catch (Exception exp) {
}
r = r + 10;
}
r = 0;
}
}
}
The problem you're having is far to common.
Swing is a single threaded framework. This means that all UI related interactions must occur within the context of this thread (AKA the Event Dispatching Thread).
The EDT is responsible for, amongst other things, dispatching repaint requests. If any part of your code stops this thread (block I/O, time consuming process, Thread.sleep), the EDT will be unable to process any new events.
Have a read through Concurrency in Swing for more details.
You now face two issues...
You can't block the EDT
You can't update the UI from any thread other then the EDT.
Luckily, there are a number of solutions. The simplest is using a javax.swing.Timer.
This timer triggers it's tick events within the EDT but waits within it's own thread...
import com.sun.org.apache.bcel.internal.generic.LSTORE;
import java.awt.AlphaComposite;
import java.awt.BorderLayout;
import java.awt.Composite;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Droplets {
public static void main(String[] args) {
new Droplets();
}
public Droplets() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new DropletPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
protected static final int MAX_RADIUS = 50;
protected static final int GROWTH_RATE = 1;
public class DropletPane extends JPanel {
private List<Droplet> droplets;
public DropletPane() {
droplets = new ArrayList<>(25);
addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
droplets.add(new Droplet(e.getPoint()));
}
});
Timer timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
for (Droplet droplet : droplets.toArray(new Droplet[droplets.size()])) {
droplet.grow();
if (droplet.getRadius() >= MAX_RADIUS) {
droplets.remove(droplet);
}
}
repaint();
}
});
timer.setRepeats(true);
timer.setCoalesce(true);
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
Composite comp = g2d.getComposite();
for (Droplet droplet : droplets) {
float alpha = 1f - ((float) droplet.getRadius() / (float) MAX_RADIUS);
g2d.setComposite(AlphaComposite.SrcOver.derive(alpha));
Point p = droplet.getLocation();
int radius = droplet.getRadius();
g2d.drawOval(p.x - (radius / 2), p.y - (radius / 2), radius, radius);
g2d.setComposite(comp);
}
g2d.dispose();
}
}
public class Droplet {
private Point p;
private int radius;
public Droplet(Point p) {
this.p = p;
}
public Point getLocation() {
return p;
}
public int getRadius() {
return radius;
}
public void grow() {
radius += GROWTH_RATE;
if (radius > MAX_RADIUS) {
radius = MAX_RADIUS;
}
}
}
}
Extended Example
This example will, when you click the "Start" button, create a random number of droplets at a random interval (between each droplet). You can press start multiple times and it will compound the output.
import static droplets.Droplets.MAX_RADIUS;
import java.awt.AlphaComposite;
import java.awt.BorderLayout;
import java.awt.Composite;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagLayout;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingWorker;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Droplets02 {
public static void main(String[] args) {
new Droplets02();
}
public Droplets02() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new DropletPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
protected static final int MAX_RADIUS = 50;
protected static final int GROWTH_RATE = 1;
public interface Pool {
public void addDroplet(Droplet droplet);
public Dimension getSize();
}
public class DropletPane extends JPanel implements Pool {
private List<Droplet> droplets;
private Timer timer;
public DropletPane() {
setLayout(new GridBagLayout());
JButton button = new JButton("Start");
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
new DropletWorker(DropletPane.this).execute();
}
});
add(button);
droplets = new ArrayList<>(25);
timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (!droplets.isEmpty()) {
for (Droplet droplet : droplets.toArray(new Droplet[droplets.size()])) {
droplet.grow();
if (droplet.getRadius() >= MAX_RADIUS) {
droplets.remove(droplet);
}
}
if (droplets.isEmpty()) {
((Timer) e.getSource()).stop();
}
repaint();
}
}
});
timer.setRepeats(true);
timer.setCoalesce(true);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
Composite comp = g2d.getComposite();
for (Droplet droplet : droplets) {
float alpha = 1f - ((float) droplet.getRadius() / (float) MAX_RADIUS);
g2d.setComposite(AlphaComposite.SrcOver.derive(alpha));
Point p = droplet.getLocation();
int radius = droplet.getRadius();
g2d.drawOval(p.x - (radius / 2), p.y - (radius / 2), radius, radius);
g2d.setComposite(comp);
}
g2d.dispose();
}
#Override
public void addDroplet(Droplet droplet) {
if (!timer.isRunning()) {
timer.start();
}
droplets.add(droplet);
}
}
public class Droplet {
private Point p;
private int radius;
public Droplet(Point p) {
this.p = p;
}
public Point getLocation() {
return p;
}
public int getRadius() {
return radius;
}
public void grow() {
radius += GROWTH_RATE;
if (radius > MAX_RADIUS) {
radius = MAX_RADIUS;
}
}
}
public class DropletWorker extends SwingWorker<Void, Droplet> {
private Pool pool;
public DropletWorker(Pool pool) {
this.pool = pool;
}
public Pool getPool() {
return pool;
}
protected int random(int minRange, int maxRange) {
return minRange + (int) (Math.round(Math.random() * (maxRange - minRange)));
}
#Override
protected Void doInBackground() throws Exception {
int dropCount = random(1, 100);
Pool pool = getPool();
Dimension size = pool.getSize();
for (int index = 0; index < dropCount; index++) {
Thread.sleep(random(10, 1000));
int x = random(0, size.width);
int y = random(0, size.height);
Droplet droplet = new Droplet(new Point(x, y));
publish(droplet);
}
return null;
}
#Override
protected void process(List<Droplet> chunks) {
for (Droplet droplet : chunks) {
getPool().addDroplet(droplet);
}
}
}
}
Animation Basics
You need three things to perform animation.
A Start state
A Target state
A delta or time range.
(You also need some way to store the current state)
The start and target states are self explanatory, they describe where you are now and where you want to change to.
The delta would be the amount to apply to the current state at each "time interval" (or tick) until you reach the delta.
Or
The time range would be the amount of time you want to use to move from the start state to the end state.
The delta approach is the simpler mechanism, but isn't nearly as flexible as the time range approach...
Once you have these basic elements set up, you need some kind of "tick" that is triggered at regular intervals which allows you to calculate the current state, which is either a linear movement from the start state to the target state (delta) or a progression of change of over time (time range)
A final, full working rework
Apart from you're attempt to block the EDT within the paint method and failing to following the Initial Thread requirements of Swing, the only other, significant, problem I found was your reliance on the radius and iter values.
Basically, these were never getting set UNLESS you pressed the Enter key...which I wasn't.
This example uses the code that you posted and the ideas from the first example...
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class ControlCircles extends JFrame {
private JButton jbtEnlarge = new JButton("Start");
private JButton jbtShrink = new JButton("Stop");
private CirclePanel canvas = new CirclePanel();
private JTextField f1 = new JTextField(8);
private JTextField f2 = new JTextField(8);
public ControlCircles() {
JPanel panel = new JPanel();
JPanel jp = new JPanel();
jp.setPreferredSize(new Dimension(300, 0));
panel.add(jbtEnlarge);
panel.add(jbtShrink);
this.add(jp, BorderLayout.WEST);
this.add(canvas, BorderLayout.CENTER);
this.add(panel, BorderLayout.SOUTH);
jp.setLayout(new FlowLayout(FlowLayout.RIGHT, 50, 30));
jp.add(new JLabel("Radius"));
jp.add(f1);
jp.add(new JLabel("Iteration"));
jp.add(f2);
jbtEnlarge.addActionListener(new EnlargeListener());
jbtShrink.addActionListener(new ShrinkListener());
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new ControlCircles();
frame.setTitle("ControlCircle");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(800, 600);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
class EnlargeListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
int radius = Integer.parseInt(f1.getText());
int iter = Integer.parseInt(f2.getText());
canvas.start(radius, iter);
}
}
class ShrinkListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
//canvas.shrink();
}
}
class CirclePanel extends JPanel {
private int radius;
private int iterations;
private int iteration;
private List<Integer> circles;
private Timer timer;
public CirclePanel() {
circles = new ArrayList<>(25);
timer= new Timer(100, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
iteration++;
if (iteration < iterations) {
circles.add(radius);
radius += 10;
} else {
((Timer)e.getSource()).stop();
}
repaint();
}
});
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int width = getWidth() - 1;
int height = getHeight()- 1;
g.drawRect(0, 0, width, height);
for (Integer radius : circles) {
int x = (width - radius) / 2;
int y = (height - radius) / 2;
g.drawOval(x, y, radius, radius);
}
}
public void start(int radius, int iter) {
timer.stop();
circles.clear();
this.radius = radius;
iterations = iter;
iteration = 0;
System.out.println("radius = " + radius);
System.out.println("iterations = " + iterations);
timer.start();
}
}
}
This code works based on the description of your problem by correcting the common mistakes with animation in Swing but some of your code didn't quite make sense to me (ie enlarge and shrink) so I focused on the description your provided.
The idea is to control the drawing animation on the panel used as a canvas with the buttons Start, Stop and I added Continue and Reset additional controls to better explain the idea. These buttons control the animation thread execution thus drawing circles on the drawing surface. the drawing surface I separated as inner class that has only function to draw whatever performed. Another idea that the approach is taken to draw the circles one by one incrementally until it finishes drawing thus used incremental painting.
I have used the code from the above and changed it a little to support my ideas. If you need more and usually better examples look at this article.
The code is below, I didn't polish it enough to have a production wise look and feel but for demonstration purpose only.
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
public class ControlCircle extends JFrame implements Runnable {
private JButton jbtStart = new JButton("Start");
private JButton jbtStop = new JButton("Stop");
private JButton jbtContinue = new JButton("Continue");
private JButton jbtReset = new JButton("Reset");
private CirclePanel canvas = new CirclePanel();
private JTextField f1;
private int radius = 0;
private JTextField f2;
private int iter;
protected boolean incrementalPainting;
/**
* Flag indicates that a thread is suspended
*/
private boolean suspended = false;
/**An instance of the class Thread.*/
private Thread thread = null;
public ControlCircle() {
JPanel panel = new JPanel();
JPanel jp = new JPanel();
jp.setPreferredSize(new Dimension(300, 0));
panel.add(jbtStart);
panel.add(jbtStop);
panel.add(jbtContinue);
panel.add(jbtReset);
this.add(jp, BorderLayout.WEST);
this.add(canvas, BorderLayout.CENTER);
this.add(panel, BorderLayout.SOUTH);
f1 = new JTextField(8);
f2 = new JTextField(8);
jp.setLayout(new FlowLayout(FlowLayout.RIGHT, 50, 30));
jp.add(new JLabel("Radius"));
jp.add(f1);
jp.add(new JLabel("Iteration"));
jp.add(f2);
jbtStart.addActionListener(new StartListener());
jbtStop.addActionListener(new StopListener());
jbtContinue.addActionListener(new ContinueListener());
jbtReset.addActionListener(new ResetListener());
}
public static void main(String[] args) {
JFrame frame = new ControlCircle();
frame.setTitle("ControlCircle");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(800, 600);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
class StartListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
if (thread == null) {
repaint();
startThread();
}
}
}
class StopListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
if (thread != null){
mySuspend();
}
}
}
class ContinueListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
myResume();
}
}
class ResetListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
if (thread != null) {
stopThread();
}
repaint();
}
}
/**
* my Suspend
*/
private void mySuspend() {
System.out.println("mySyspend()");
suspended = true;
}
/**
* my Resume
*/
private synchronized void myResume(){
System.out.println("myResume()");
suspended = false;
notifyAll();
}
public void run(){
System.out.println("run() - started");
Thread me = Thread.currentThread();
while (thread == me) {
radius = Integer.parseInt(f1.getText());
iter = Integer.parseInt(f2.getText());
for (int i = 0; i < iter; i++) {
if (thread == null) return;
incrementalPainting = true;
myRepaint();
try {
Thread.sleep(1000);
}
catch(InterruptedException e){}
radius += 10;
}
if(thread != null) thread = null; // exiting while
}
System.out.println("run() - exiting");
}
/**
* start Thread
*/
private void startThread(){
System.out.println("startThread()");
if(thread == null){
thread = new Thread(this);
thread.start();
}
}
/**
* stop Thread
*/
private synchronized void stopThread() {
System.out.println("stopThread()");
thread = null; // exiting from while
if (suspended) {
suspended = false;
notify();
}
}
/**
* This is called from the run method to invoke painting.
*/
private void myRepaint() {
System.out.println("myRepaint()");
incrementalPainting = true;
repaint();
synchronized (this) {
while (incrementalPainting) {
System.out.println("wait while incremental painting");
try {
wait();
} catch (InterruptedException e) {
System.out.println("interrupted");
}
}
}
suspend();
}
/**
* This method should place somewhere when run() has started. Perfectly
* when repaint() performed.
*/
private void suspend(){
System.out.println("suspend()");
synchronized (this) {
while (suspended) {
System.out.println("wait while suspended");
try {
wait();
} catch (InterruptedException e) {
System.out.println("interrupted");
}
}
}
}
public synchronized void myPaint(Graphics g) {
if (g == null){
if (incrementalPainting){
incrementalPainting = false;
notifyAll();
}
return;
}
if (incrementalPainting){
myDraw(g);
incrementalPainting = false;
notifyAll();
}
else {
myDraw(g);
}
}
public void myDraw(Graphics g){
g.drawOval(getWidth() / 2 - radius, getHeight() / 2 - radius, 2 * radius, 2 * radius);
}
protected final class CirclePanel extends JPanel {
//Offscreen buffer of this canvas
private BufferedImage backBuffer = null;
public void paintComponent (Graphics g) {
System.out.println("incrementalPainting="+incrementalPainting);
// First paint background
super.paintComponent(g);
Dimension d = this.getSize();
if (! incrementalPainting)
backBuffer = (BufferedImage) this.createImage(d.width, d.height);
Graphics2D g2 = backBuffer.createGraphics();
if (! incrementalPainting){
g2.setColor(Color.WHITE);
g2.fillRect(0,0, d.width, d.height);
}
myPaint(g2);
g.drawImage(backBuffer, 0, 0, this);
}
}
}