这里是在给定延迟率的帧内的面板上显示不同半径的圆圈的代码,但该代码显示的是最终输出而不是中间阶段,即圆圈不是一个一个出现的,而是所有圆圈都出现了立即作为最终输出。可能存在一些与按钮操作侦听器和面板线程相关的错误。该代码采用初始圆半径和迭代总数(要显示的圆总数),下一个圆的半径增加 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;
}
}
}
您遇到的问题很常见。
Swing 是一个单线程框架。这意味着所有与 UI 相关的交互都必须发生在该线程(也称为事件调度线程)的上下文中。
EDT 负责发送重绘请求等。如果代码的任何部分停止了该线程(阻塞 I/O,耗时的过程,
Thread.sleep
),EDT 将无法处理任何新事件。
请阅读Swing 中的并发以了解更多详细信息。
您现在面临两个问题...
幸运的是,有很多解决方案。最简单的是使用
javax.swing.Timer
。
此计时器在 EDT 内触发其滴答事件,但在其自己的线程内等待...
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;
}
}
}
}
当您单击“开始”按钮时,此示例将以随机间隔(每个液滴之间)创建随机数量的液滴。您可以多次按开始键,它会复合输出。
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);
}
}
}
}
执行动画需要三件事。
(你还需要一些方法来存储当前状态)
开始状态和目标状态是不言自明的,它们描述了您现在所处的位置以及您想要更改到的位置。
增量将是在每个“时间间隔”(或刻度)应用于当前状态的量,直到达到增量。
或者
时间范围是您想要从开始状态移动到结束状态所需的时间。
增量方法是更简单的机制,但不如时间范围方法灵活......
一旦设置了这些基本元素,您就需要某种定期触发的“tick”,它允许您计算当前状态,即从开始状态到目标状态(增量)的线性移动或随时间(时间范围)变化的进展
除了您尝试在绘制方法中阻止 EDT 并且未能遵循 Swing 的 Initial Thread 要求之外,我发现的唯一其他重要问题是您对
radius
和 iter
值的依赖.
基本上,除非你按下 Enter 键,否则这些永远不会被设置……而我却没有。
此示例使用您发布的代码以及第一个示例的想法...
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();
}
}
}
此代码基于您的问题描述,通过纠正 Swing 中动画的常见错误来工作,但您的一些代码对我来说不太有意义(即
enlarge
和 shrink
),所以我重点关注您的描述提供。
这个想法是通过按钮 Start
、
Stop
控制用作画布的面板上的绘图动画,我添加了
Continue
和 Reset
附加控件以更好地解释这个想法。这些按钮控制动画线程的执行,从而在绘图表面上绘制圆圈。我将绘图表面分离为内部类,该内部类仅具有绘制执行的任何内容的功能。另一种想法是采用增量绘制的方式逐个绘制圆圈,直到完成绘制,因此使用了增量绘制。
我使用了上面的代码并对其进行了一些更改以支持我的想法。如果您需要更多且通常更好的示例,请查看这篇文章。
代码如下,我没有对其进行足够的打磨以使其具有生产明智的外观和感觉,但仅用于演示目的。
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);
}
}
}