Концептуально идея достаточно проста, проблема в том, что Swing является сигнальным потоком, а НЕ потокобезопасным.
См. Параллельность в Swing для получения более подробной информации.
Это означает, что вы можете запустить длительную или блокирующую операцию (например, бесконечный цикл) в потоке диспетчеризации событий, но также вы не должны обновлять пользовательский интерфейс (или свойства, от которых зависит пользовательский интерфейс) вне контекста EDT.
Хотя существует ряд возможных решений этой проблемы, простейшим, вероятно, является использование Swing Timer
, который предоставляет средство для безопасного планирования задержки (которая не блокирует EDT) и который вызывает ее обновления в контексте EDT, что позволяет обновлять пользовательский интерфейс внутри него.
См. Как использовать Swing Timers для получения более подробной информации.
Теперь, поскольку вы говорите на ОО-языке, вы должны использовать возможности, которые он мне предоставляет, это означает инкапсуляцию.
У вас есть изображение, которое вы хотите нарисовать в определенном месте, но изменение местоположения которого может быть изменено в соответствии с некоторыми правилами, это просто кричит Plain Old Java Old (POJO)
Обычно я бы начал с interface
, чтобы описать основные свойства и операции, но для краткости я прыгнул прямо к классу ...
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();
}
}
Но подождите, вы говорите, он использует Color
вместо изображения !? Да, у меня не было маленьких картинок под рукой, кроме того, мне нужно оставить вам чем-нибудь заняться;)
Теперь анимация представляет собой последовательность обновления и рисования несколько раз, пока не будет достигнуто желаемое состояние.
В этом случае вас не очень заботит конечное состояние, поэтому вы можете просто оставить его включенным.
Цикл «обновления» обрабатывается Swing Timer
, который зацикливается на List
из Drawable
объектов, вызывает их методы update
и затем планирует repaint
, который вызывает JPanel
s paintComponent
где Drawable
объекты нарисованы, просто ? ...
public class TestPane extends JPanel {
private List<Drawable> drawables;
public TestPane() {
drawables = new ArrayList<>(2);
drawables.add(new Drawable(288, 128, Color.RED));
drawables.add(new Drawable(288, 384, Color.RED));
Timer timer = new Timer(5, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
for (Drawable drawable : drawables) {
drawable.update();
}
repaint();
}
});
timer.start();
}
@Override
public Dimension getPreferredSize() {
return new Dimension(288, 512);
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for (Drawable drawable : drawables) {
Graphics2D g2d = (Graphics2D) g.create();
drawable.paint(g2d);
g2d.dispose();
}
}
}
В целом - работающий пример ...
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
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 Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
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();
}
}
public class TestPane extends JPanel {
private List<Drawable> drawables;
public TestPane() {
drawables = new ArrayList<>(2);
drawables.add(new Drawable(288, 128, Color.RED));
drawables.add(new Drawable(288, 384, Color.RED));
Timer timer = new Timer(5, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
for (Drawable drawable : drawables) {
drawable.update();
}
repaint();
}
});
timer.start();
}
@Override
public Dimension getPreferredSize() {
return new Dimension(288, 512);
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for (Drawable drawable : drawables) {
Graphics2D g2d = (Graphics2D) g.create();
drawable.paint(g2d);
g2d.dispose();
}
}
}
}
«Есть ли более простое решение» ? Да, конечно, я всегда иду самым сложным способом, чтобы решить проблему в первую очередь ?. Во-первых, хорошая анимация сложна. Шутки в сторону. Я играю с этим искомым предметом более 20 лет, создавая хороший анимационный движок, который будет гибким, чтобы удовлетворить все возможные потребности, которые он может поставить, - почти невыполнимая задача, особенно в среде, которая на самом деле не разработана. за это.
Если вы мне не верите, вы можете взглянуть на
, это всего лишь пара примеров того, насколько сложной может быть анимация
Извините, вы будете удивлены, как часто меня спрашивают "может ли это быть проще", когда дело доходит до анимации;)
"Каждые новые объекты, достигшие x = 144, должны строить новые
Так что, видимо, я могу быть смущен этим конкретным моментом. Если это означает «добавление новых объектов после достижения 144», то это вызывает некоторые новые проблемы. Основная проблема - это одна из проблем, связанная с GC, которая вызывает замедление анимации. Конечно, мы имеем дело только с 4-6 объектами, но это одна из тех вещей, которая может вернуться к вам, если вы не будете осторожны.
Итак, я взял приведенный выше пример и внес некоторые изменения в цикл обновления. Это добавляет reusePool
, где старые объекты размещаются и могут быть использованы повторно, уменьшая накладные расходы GC на повторное создание и уничтожение недолговечных объектов.
decaying
List
просто гарантирует, что, как только объект пройдет swanPoint
, он не будет рассматриваться для повторного создания новых объектов. Конечно, вы можете поставить флаг на само POJO, но я не думаю, что это является частью ответственности POJO
public TestPane() {
drawables = new ArrayList<>(2);
reusePool = new ArrayList<>(2);
decaying = new ArrayList<>(2);
timer = new Timer(5, new ActionListener() {
private List<Drawable> spawned = new ArrayList<>(5);
@Override
public void actionPerformed(ActionEvent e) {
spawned.clear();
Iterator<Drawable> it = drawables.iterator();
int swapnPoint = getWidth() / 2;
while (it.hasNext()) {
Drawable drawable = it.next();
drawable.update();
if (drawable.getX() > 0 && drawable.getX() < swapnPoint) {
if (!decaying.contains(drawable)) {
decaying.add(drawable);
Drawable newDrawable = null;
if (reusePool.isEmpty()) {
newDrawable = new Drawable(
getWidth() - 20,
randomVerticalPosition(),
randomColor());
} else {
newDrawable = reusePool.remove(0);
newDrawable.reset(getWidth() - 20,
randomVerticalPosition(),
randomColor());
}
spawned.add(newDrawable);
}
} else if (drawable.getX() <= -20) {
System.out.println("Pop");
it.remove();
decaying.remove(drawable);
reusePool.add(drawable);
}
}
drawables.addAll(spawned);
repaint();
}
});
}
Теперь это позволит объектам перемещаться по всей ширине, создавая новые объекты, когда они проходят половину пути. Как только они выйдут за пределы визуального диапазона обзора, они будут помещены в reuse
List
, чтобы их можно было повторно использовать, когда потребуются новые объекты.
Пример выполнения ...
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
TestPane testPane = new TestPane();
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(testPane);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
testPane.start();
}
});
}
});
}
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--;
}
protected void reset(int x, int y, Color color) {
this.x = x;
this.y = y;
this.color = color;
}
public void paint(Graphics2D g2d) {
Graphics2D copy = (Graphics2D) g2d.create();
copy.translate(getX(), getY());
copy.setColor(getColor());
copy.fillOval(0, 0, 20, 20);
copy.setColor(Color.BLACK);
copy.drawOval(0, 0, 20, 20);
copy.dispose();
}
}
public class TestPane extends JPanel {
private List<Drawable> drawables;
private List<Drawable> decaying;
private List<Drawable> reusePool;
private Color[] colors = new Color[]{Color.BLUE, Color.CYAN, Color.DARK_GRAY, Color.GREEN, Color.MAGENTA, Color.ORANGE, Color.PINK, Color.RED, Color.YELLOW};
private Random rnd = new Random();
private Timer timer;
public TestPane() {
drawables = new ArrayList<>(2);
reusePool = new ArrayList<>(2);
decaying = new ArrayList<>(2);
timer = new Timer(40, new ActionListener() {
private List<Drawable> spawned = new ArrayList<>(5);
@Override
public void actionPerformed(ActionEvent e) {
spawned.clear();
Iterator<Drawable> it = drawables.iterator();
int swapnPoint = getWidth() / 2;
while (it.hasNext()) {
Drawable drawable = it.next();
drawable.update();
if (drawable.getX() > 0 && drawable.getX() < swapnPoint) {
if (!decaying.contains(drawable)) {
decaying.add(drawable);
Drawable newDrawable = null;
if (reusePool.isEmpty()) {
System.out.println("New");
newDrawable = new Drawable(
getWidth() - 20,
randomVerticalPosition(),
randomColor());
} else {
System.out.println("Reuse");
newDrawable = reusePool.remove(0);
newDrawable.reset(getWidth() - 20,
randomVerticalPosition(),
randomColor());
}
spawned.add(newDrawable);
}
} else if (drawable.getX() <= -20) {
System.out.println("Pop");
it.remove();
decaying.remove(drawable);
reusePool.add(drawable);
}
}
drawables.addAll(spawned);
repaint();
}
});
}
public void start() {
drawables.add(new Drawable(getWidth(), 128, randomColor()));
drawables.add(new Drawable(getWidth(), 384, randomColor()));
timer.start();
}
protected int randomVerticalPosition() {
return rnd.nextInt(getHeight() - 20);
}
protected Color randomColor() {
return colors[rnd.nextInt(colors.length - 1)];
}
@Override
public Dimension getPreferredSize() {
return new Dimension(288, 512);
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for (Drawable drawable : drawables) {
Graphics2D g2d = (Graphics2D) g.create();
drawable.paint(g2d);
g2d.dispose();
}
}
}
}