I have one dilemma , how to realize application. I have JPanel with width 288 and height 512, then I created two objects ( images ) and drew them through paintComponent usin
Conceptually the idea is simple enough, the problem is, Swing is signal thread and NOT thread safe.
See Concurrency in Swing for more details.
This means you can run a long running or blocking operation (like a never ending loop) inside the Event Dispatching Thread, but also, you shouldn't update the UI (or properties the UI depends on) from outside the context of the EDT.
While there are a number of possible solutions to the problem, the simplest is probably to use a Swing Timer
, which provides a means to schedule a delay safely (that won't block the EDT) and which will trigger it's updates within the context of the EDT, allowing you to update the UI from within it.
See How to Use Swing Timers for more details.
Now, because you're in a OO language, you should leverage the power it provides, to me, this means encapsulation.
You have a image, you want drawn at a specific location, but whose location change be changed based on some rules, this just screams Plain Old Java Old (POJO)
Normally, I'd start with a interface
to describe the basic properties and operations, but for brevity, I've jumped straight for a class...
public class Drawable {
private int x, y;
private Color color;
public Drawable(int x, int y, Color color) {
this.x = x;
this.y = y;
this.color = color;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public Color getColor() {
return color;
}
public void update() {
x--;
if (x <= 144) {
reset();
}
}
protected void reset() {
x = 288;
y = (int) (Math.random() * (512 - 20));
}
public void paint(Graphics2D g2d) {
Graphics2D copy = (Graphics2D) g2d.create();
copy.translate(getX(), getY());
copy.setColor(getColor());
copy.drawOval(0, 0, 20, 20);
copy.dispose();
}
}
But wait, you say, it's using Color
instead of image!? Yes, I didn't have any small images at hand, besides, I need to leave you something to do ;)
Now, the animation is a sequence of updating and painting repeatedly until a desired state is reached.
In this case, you don't care about the end state so much, so you can just keep it running.
The "update" cycle is handled by a Swing Timer
, which loops over a List
of Drawable
objects, calls their update
methods and then schedules a repaint
, which triggers the JPanel
s paintComponent
where by the Drawable
objects are painted, simple
The following solution is based on my previous answer.
I add it in response to MadProgrammer's comment: "A better solution is to pool the objects for re-use".
DrawAblesProducer
produces drawable
objects on-demand. It also stores surplus object, to prevent producing too many such objects.
I post it as a separate answer because the additional functionality comes with somewhat higher complexity:
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class ImageAnimator {
private static final int W = 288, H = 512;
public ImageAnimator() {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new AnimationPane(new DrawAblesProducer()));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
public class AnimationPane extends JPanel {
private final List<Drawable> drawables;
private static final int CYCLE_TIME = 5;
private final DrawAblesProducer producer;
public AnimationPane(DrawAblesProducer producer) {
this.producer = producer;
drawables = new ArrayList<>(2);
drawables.add(producer.issue(W, H/4));
drawables.add(producer.issue(W, 3*H/4));
Timer timer = new Timer(CYCLE_TIME, e -> animate());
timer.start();
}
private void animate() {
for (Drawable drawable : new ArrayList<>(drawables)) {
drawable.update();
if(drawable.getX() == W/2) {
drawables.add(producer.issue(W)); //random Y
}else if(drawable.getX() <= 0) {
drawables.remove(drawable);
producer.retrn(drawable);
}
}
repaint();
}
@Override
public Dimension getPreferredSize() {
return new Dimension(W, H);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for (Drawable drawable : drawables ) {
g.drawImage(drawable.getImage(),drawable.getX(), drawable.getY(), null);
}
}
}
//produces `drawable` objects on-demand. stores surplus object, to prevent producing
//too many such objects
public class DrawAblesProducer {
private final Queue<Drawable> warehouse = new LinkedList<>();
public Drawable issue(int x){
return issue(x, -1);
}
public Drawable issue(int x, int y){
Drawable drawable = warehouse.poll();
if(drawable != null ) {
drawable.setX(x); drawable.setY(y);
return drawable;
}
return new Drawable(x, y);
}
public void retrn(Drawable drawable){
warehouse.add(drawable);
}
}
public static class Drawable {
//made static so image is reused for all instances
private static final Image image = image();
private int x, y;
//construct with a random y value
public Drawable(int x) {
this(x, -1);
}
public Drawable(int x, int y) {
setX(x);
setY(y);
}
public int getX() { return x; }
public void setX(int x) { this.x = x;}
public int getY() { return y; }
public void setY(int y) {
this.y = y < 0 ? randomY() : y ;
}
private int randomY() {
int iHeight = image.getHeight(null);
return iHeight + (int) (Math.random() * (H - iHeight));
}
public void update() { x--; }
public Image getImage(){ return image; }
public static Image image() {
URL url = null;
try {
url = new URL("https://dl1.cbsistatic.com/i/r/2017/09/24/b2320b25-27f3-4059-938c-9ee4d4e5cadf/thumbnail/32x32/707de8365496c85e90c975cec8278ff5/iconimg241979.png");
return ImageIO.read(url);
} catch ( IOException ex) { ex.printStackTrace(); }
return null;
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(()->new ImageAnimator());
}
}
My answer is completely based on MadProgrammer's answer (A comprehensive tutorial actually).
From what I read in the post : "Every new objects reaching x = 144 should build new ones", I think the desired implementation is slightly different:
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class ImageAnimator {
public ImageAnimator() {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new AnimationPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
public static class Drawable {
private int x;
private final int y;
private static final Image image = image();
//construct with a random y value
public Drawable(int x) {
this(x, -1);
}
public Drawable(int x, int y) {
this.x = x;
this.y = y < 0 ? (int) (Math.random() * (512 - 20)) : y;
}
public int getX() { return x; }
public int getY() { return y; }
public void update() { x--; }
public Image getImage(){ return image; }
public static Image image() {
URL url = null;
try {
url = new URL("https://dl1.cbsistatic.com/i/r/2017/09/24/b2320b25-27f3-4059-938c-9ee4d4e5cadf/thumbnail/32x32/707de8365496c85e90c975cec8278ff5/iconimg241979.png");
return ImageIO.read(url);
} catch ( IOException ex) {
ex.printStackTrace();
return null;
}
}
}
public class AnimationPane extends JPanel {
private final List<Drawable> drawables;
private static final int W = 288, H = 512, CYCLE_TIME = 5;
public AnimationPane() {
drawables = new ArrayList<>(2);
drawables.add(new Drawable(W, H/4));
drawables.add(new Drawable(W, 3*H/4));
Timer timer = new Timer(CYCLE_TIME, e -> animate());
timer.start();
}
private void animate() {
for (Drawable drawable : new ArrayList<>(drawables)) {
drawable.update();
if(drawable.getX() == W/2) {
drawables.add(new Drawable(W)); //random Y
}
if(drawable.getX() <= 0) {
drawables.remove(drawable);
}
}
repaint();
}
@Override
public Dimension getPreferredSize() {
return new Dimension(W, H);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for (Drawable drawable : drawables ) {
g.drawImage(drawable.getImage(),drawable.getX(), drawable.getY(), null);
}
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(()->new ImageAnimator());
}
}