Если вы хотите запланировать обновления с заданным интервалом, javax.swing.Timer
предоставляет интегрированную услугу Swing. Timer
периодически выполняет свою задачу на EDT, не имея явного цикла. (Явный цикл будет блокировать EDT от обработки событий, которые бы заморозить UI я объяснил это более глубокий here.).
В конечном счете делает любой вид живописи в свинг вы все равно будете делать две вещи:
- Переопределение
paintComponent
, чтобы сделать ваш рисунок.
- Вызов
repaint
как необходимо для того, чтобы ваш чертеж стал видимым. (Качели обычно перекраиваются только тогда, когда это необходимо, например, когда окно какой-либо другой программы проходит над верхней частью компонента Swing.)
Если вы делаете эти две вещи, вы, вероятно, делаете это правильно. У Swing действительно нет API высокого уровня для анимации. Он разработан в первую очередь с учетом компонентов графического интерфейса GUI. Конечно, это может быть полезно, но вам придется писать компонент в основном с нуля, как и вы.
Painting in AWT and Swing охватывает некоторые вещи «за кулисами», если у вас нет закладки.
Вы можете посмотреть в JavaFX. Я не знаю об этом лично, но это должно быть больше ориентировано на анимацию.
Как часть оптимизации, одна вещь, которую можно сделать, это нарисовать на отдельном изображении, а затем нарисовать изображение на панели в paintComponent
. Это особенно полезно, если картина длинная: реплики могут быть запланированы системой, поэтому это сохраняется, когда это происходит под контролем.
Если вы не рисуете изображение, вам нужно будет построить модель с объектами и нарисовать все из них каждый раз внутри paintComponent
.
Вот пример рисования к изображению:
import javax.swing.*;
import java.awt.*;
import java.awt.image.*;
import java.awt.event.*;
/**
* Holding left-click draws, and
* right-clicking cycles the color.
*/
class PaintAnyTime {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
new PaintAnyTime();
}
});
}
Color[] colors = {Color.red, Color.blue, Color.black};
int currentColor = 0;
BufferedImage img = new BufferedImage(256, 256, BufferedImage.TYPE_INT_ARGB);
Graphics2D imgG2 = img.createGraphics();
JFrame frame = new JFrame("Paint Any Time");
JPanel panel = new JPanel() {
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
// Creating a copy of the Graphics
// so any reconfiguration we do on
// it doesn't interfere with what
// Swing is doing.
Graphics2D g2 = (Graphics2D) g.create();
// Drawing the image.
int w = img.getWidth();
int h = img.getHeight();
g2.drawImage(img, 0, 0, w, h, null);
// Drawing a swatch.
Color color = colors[currentColor];
g2.setColor(color);
g2.fillRect(0, 0, 16, 16);
g2.setColor(Color.black);
g2.drawRect(-1, -1, 17, 17);
// At the end, we dispose the
// Graphics copy we've created
g2.dispose();
}
@Override
public Dimension getPreferredSize() {
return new Dimension(img.getWidth(), img.getHeight());
}
};
MouseAdapter drawer = new MouseAdapter() {
boolean rButtonDown;
Point prev;
@Override
public void mousePressed(MouseEvent e) {
if (SwingUtilities.isLeftMouseButton(e)) {
prev = e.getPoint();
}
if (SwingUtilities.isRightMouseButton(e) && !rButtonDown) {
// (This just behaves a little better
// than using the mouseClicked event.)
rButtonDown = true;
currentColor = (currentColor + 1) % colors.length;
panel.repaint();
}
}
@Override
public void mouseDragged(MouseEvent e) {
if (prev != null) {
Point next = e.getPoint();
Color color = colors[currentColor];
// We can safely paint to the
// image any time we want to.
imgG2.setColor(color);
imgG2.drawLine(prev.x, prev.y, next.x, next.y);
// We just need to repaint the
// panel to make sure the
// changes are visible
// immediately.
panel.repaint();
prev = next;
}
}
@Override
public void mouseReleased(MouseEvent e) {
if (SwingUtilities.isLeftMouseButton(e)) {
prev = null;
}
if (SwingUtilities.isRightMouseButton(e)) {
rButtonDown = false;
}
}
};
PaintAnyTime() {
// RenderingHints let you specify
// options such as antialiasing.
imgG2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
imgG2.setStroke(new BasicStroke(3));
//
panel.setBackground(Color.white);
panel.addMouseListener(drawer);
panel.addMouseMotionListener(drawer);
Cursor cursor =
Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR);
panel.setCursor(cursor);
frame.setContentPane(panel);
frame.pack();
frame.setResizable(false);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
Если подпрограмма давно работает и перерисовки может произойти одновременно, двойной буферизации может также использоваться , Рисование выполняется с изображением, которое отделено от отображаемого. Затем, когда процедура рисования выполняется, ссылки на изображения меняются местами, поэтому обновление является бесшовным.
Обычно вы должны использовать двойную буферизацию для игры. Двойная буферизация предотвращает показ изображения в частичном состоянии. Это может произойти, если, например, вы использовали фоновый поток для игрового цикла (вместо Timer
), и в результате перерисовки игра делала картину. Без двойной буферизации такая ситуация может привести к мерцанию или разрыву.
Компоненты Swing по умолчанию буферизуются по умолчанию, поэтому, если весь ваш рисунок происходит на EDT, вам не нужно писать логику двойной буферизации самостоятельно. Свинг уже делает это.
Вот несколько более сложный пример, который показывает затянувшуюся задачу и буфер подкачку:
import java.awt.*;
import javax.swing.*;
import java.awt.image.*;
import java.awt.event.*;
import java.util.*;
/**
* Left-click to spawn a new background
* painting task.
*/
class DoubleBuffer {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
new DoubleBuffer();
}
});
}
final int width = 640;
final int height = 480;
BufferedImage createCompatibleImage() {
GraphicsConfiguration gc =
GraphicsEnvironment
.getLocalGraphicsEnvironment()
.getDefaultScreenDevice()
.getDefaultConfiguration();
// createCompatibleImage creates an image that is
// optimized for the display device.
// See http://docs.oracle.com/javase/8/docs/api/java/awt/GraphicsConfiguration.html#createCompatibleImage-int-int-int-
return gc.createCompatibleImage(width, height, Transparency.TRANSLUCENT);
}
// The front image is the one which is
// displayed in the panel.
BufferedImage front = createCompatibleImage();
// The back image is the one that gets
// painted to.
BufferedImage back = createCompatibleImage();
boolean isPainting = false;
final JFrame frame = new JFrame("Double Buffer");
final JPanel panel = new JPanel() {
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
// Scaling the image to fit the panel.
Dimension actualSize = getSize();
int w = actualSize.width;
int h = actualSize.height;
g.drawImage(front, 0, 0, w, h, null);
}
};
final MouseAdapter onClick = new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
if (!isPainting) {
isPainting = true;
new PaintTask(e.getPoint()).execute();
}
}
};
DoubleBuffer() {
panel.setPreferredSize(new Dimension(width, height));
panel.setBackground(Color.WHITE);
panel.addMouseListener(onClick);
frame.setContentPane(panel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
void swap() {
BufferedImage temp = front;
front = back;
back = temp;
}
class PaintTask extends SwingWorker<Void, Void> {
final Point pt;
PaintTask(Point pt) {
this.pt = pt;
}
@Override
public Void doInBackground() {
Random rand = new Random();
synchronized(DoubleBuffer.this) {
Graphics2D g2 = back.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
RenderingHints.VALUE_STROKE_PURE);
g2.setBackground(new Color(0, true));
g2.clearRect(0, 0, width, height);
// (This computes pow(2, rand.nextInt(3) + 7).)
int depth = 1 << (rand.nextInt(3) + 7);
float hue = rand.nextInt(depth);
int radius = 1;
int c;
// This loop just draws concentric circles,
// starting from the inside and extending
// outwards until it hits the outside of
// the image.
do {
int rgb = Color.HSBtoRGB(hue/depth, 1, 1);
g2.setColor(new Color(rgb));
int x = pt.x - radius;
int y = pt.y - radius;
int d = radius * 2;
g2.drawOval(x, y, d, d);
++radius;
++hue;
c = (int) (radius * Math.cos(Math.PI/4));
} while (
(0 <= pt.x - c) || (pt.x + c < width)
|| (0 <= pt.y - c) || (pt.y + c < height)
);
g2.dispose();
back.flush();
return (Void) null;
}
}
@Override
public void done() {
// done() is completed on the EDT,
// so for this small program, this
// is the only place where synchronization
// is necessary.
// paintComponent will see the swap
// happen the next time it is called.
synchronized(DoubleBuffer.this) {
swap();
}
isPainting = false;
panel.repaint();
}
}
}
Процедура картины просто предназначена нарисовать мусор, который занимает много времени:
Я не буду смотреть в JavaFX прямо сейчас, так как я быстро работаю, но спасибо. Я делаю эти две вещи. Кроме того, окно должно быть перекрашено каждый раз, когда симуляция обновляется, вроде, поэтому на данный момент я в порядке, не используя таймер. – corazza
Это ужасно медленно, если вы не делаете двойную буферизацию вручную, FYI –
@ DanielMurphy Конечно, есть оптимизации, которые можно сделать, но это не вопрос. Обратите внимание, что все компоненты Swing по умолчанию имеют двойной буферизатор, поэтому более элегантная оптимизация заключается в том, чтобы сделать рисунок пользовательского изображения для изображения и нарисовать изображение на панели в режиме перерисовки. Нет необходимости дублировать буфер. – Radiodef