有没有人试图使用Swing来构建适当的多缓冲渲染环境,并且可以在上面添加Swing用户界面元素?
在这种情况下,我在背景上绘制了一个动画红色矩形。不需要每帧更新背景,因此我将其渲染到BufferedImage上,然后仅重画清除矩形先前位置所需的部分。请参见下面的完整代码,这扩展了以前的线程通过@trashgod给出的例子,在这里。
到目前为止,一切都很好; 动画流畅,CPU使用率低,无闪烁。
然后,我将一个JTextField添加到Jpanel(通过单击屏幕上的任何位置),然后通过在文本框中单击以将其聚焦。现在,在每次光标闪烁时清除矩形的先前位置都会失败,请参见下图。
我很好奇是否有人知道为什么会发生这种情况(摆动不是线程安全的吗?图像被异步绘制吗?)以及朝哪个方向寻找可能的解决方案。
这是在Mac OS 10.5,Java 1.6上
import java.awt.Color; import java.awt.Dimension; import java.awt.EventQueue; import java.awt.Graphics; import java.awt.GraphicsConfiguration; import java.awt.GraphicsDevice; import java.awt.GraphicsEnvironment; import java.awt.Insets; import java.awt.Rectangle; import java.awt.Transparency; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ComponentEvent; import java.awt.event.ComponentListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.image.BufferedImage; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JTextField; import javax.swing.Timer; public class NewTest extends JPanel implements MouseListener, ActionListener, ComponentListener, Runnable { JFrame f; Insets insets; private Timer t = new Timer(20, this); BufferedImage buffer1; boolean repaintBuffer1 = true; int initWidth = 640; int initHeight = 480; Rectangle rect; public static void main(String[] args) { EventQueue.invokeLater(new NewTest()); } @Override public void run() { f = new JFrame("NewTest"); f.addComponentListener(this); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.add(this); f.pack(); f.setLocationRelativeTo(null); f.setVisible(true); createBuffers(); insets = f.getInsets(); t.start(); } public NewTest() { super(true); this.setPreferredSize(new Dimension(initWidth, initHeight)); this.setLayout(null); this.addMouseListener(this); } void createBuffers() { int width = this.getWidth(); int height = this.getHeight(); GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); GraphicsDevice gs = ge.getDefaultScreenDevice(); GraphicsConfiguration gc = gs.getDefaultConfiguration(); buffer1 = gc.createCompatibleImage(width, height, Transparency.OPAQUE); repaintBuffer1 = true; } @Override protected void paintComponent(Graphics g) { int width = this.getWidth(); int height = this.getHeight(); if (repaintBuffer1) { Graphics g1 = buffer1.getGraphics(); g1.clearRect(0, 0, width, height); g1.setColor(Color.green); g1.drawRect(0, 0, width - 1, height - 1); g.drawImage(buffer1, 0, 0, null); repaintBuffer1 = false; } double time = 2* Math.PI * (System.currentTimeMillis() % 5000) / 5000.; g.setColor(Color.RED); if (rect != null) { g.drawImage(buffer1, rect.x, rect.y, rect.x + rect.width, rect.y + rect.height, rect.x, rect.y, rect.x + rect.width, rect.y + rect.height, this); } rect = new Rectangle((int)(Math.sin(time) * width/3 + width/2 - 20), (int)(Math.cos(time) * height/3 + height/2) - 20, 40, 40); g.fillRect(rect.x, rect.y, rect.width, rect.height); } @Override public void actionPerformed(ActionEvent e) { this.repaint(); } @Override public void componentHidden(ComponentEvent arg0) { // TODO Auto-generated method stub } @Override public void componentMoved(ComponentEvent arg0) { // TODO Auto-generated method stub } @Override public void componentResized(ComponentEvent e) { int width = e.getComponent().getWidth() - (insets.left + insets.right); int height = e.getComponent().getHeight() - (insets.top + insets.bottom); this.setSize(width, height); createBuffers(); } @Override public void componentShown(ComponentEvent arg0) { // TODO Auto-generated method stub } @Override public void mouseClicked(MouseEvent e) { JTextField field = new JTextField("test"); field.setBounds(new Rectangle(e.getX(), e.getY(), 100, 20)); this.add(field); repaintBuffer1 = true; } @Override public void mouseEntered(MouseEvent arg0) { // TODO Auto-generated method stub } @Override public void mouseExited(MouseEvent arg0) { // TODO Auto-generated method stub } @Override public void mousePressed(MouseEvent arg0) { // TODO Auto-generated method stub } @Override public void mouseReleased(MouseEvent arg0) { // TODO Auto-generated method stub } }
NewTest延伸JPanel; 但由于你并未在对的每次调用中绘制每个像素paintComponent(),因此你需要调用超类的方法并清除旧的绘制:
NewTest
JPanel
paintComponent()
@Override protected void paintComponent(Graphics g) { super.paintComponent(g); int width = this.getWidth(); int height = this.getHeight(); g.setColor(Color.black); g.fillRect(0, 0, width, height); ... }
附录:如你所述,在构造函数中设置背景色无需在中填充面板paintComponent(),而super.paintComponent()允许文本字段正常运行。如你所见,建议的解决方法很脆弱。相反,请简化代码并按要求进行优化。例如,你可能不需要插入,复杂的缓冲区和组件侦听器的复杂性。
附录2:请注意,super.paintComponent()调用UI委托的update()方法,“该方法将用其背景颜色填充指定的组件(如果其opaque属性为true)”。你可以使用它setOpaque(false)来避免这种情况。
动画测试
import java.awt.Color; import java.awt.Dimension; import java.awt.EventQueue; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GraphicsConfiguration; import java.awt.GraphicsEnvironment; import java.awt.Rectangle; import java.awt.Transparency; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.image.BufferedImage; import java.util.Random; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JTextField; import javax.swing.Timer; /** @see http://stackoverflow.com/questions/3256941 */ public class AnimationTest extends JPanel implements ActionListener { private static final int WIDE = 640; private static final int HIGH = 480; private static final int RADIUS = 25; private static final int FRAMES = 24; private final Timer timer = new Timer(20, this); private final Rectangle rect = new Rectangle(); private BufferedImage background; private int index; private long totalTime; private long averageTime; private int frameCount; public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { @Override public void run() { new AnimationTest().create(); } }); } private void create() { JFrame f = new JFrame("AnimationTest"); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.add(this); f.pack(); f.setLocationRelativeTo(null); f.setVisible(true); timer.start(); } public AnimationTest() { super(true); this.setOpaque(false); this.setPreferredSize(new Dimension(WIDE, HIGH)); this.addMouseListener(new MouseHandler()); this.addComponentListener(new ComponentHandler()); } @Override protected void paintComponent(Graphics g) { long start = System.nanoTime(); super.paintComponent(g); int w = this.getWidth(); int h = this.getHeight(); g.drawImage(background, 0, 0, this); double theta = 2 * Math.PI * index++ / 64; g.setColor(Color.blue); rect.setRect( (int) (Math.sin(theta) * w / 3 + w / 2 - RADIUS), (int) (Math.cos(theta) * h / 3 + h / 2 - RADIUS), 2 * RADIUS, 2 * RADIUS); g.fillOval(rect.x, rect.y, rect.width, rect.height); g.setColor(Color.white); if (frameCount == FRAMES) { averageTime = totalTime / FRAMES; totalTime = 0; frameCount = 0; } else { totalTime += System.nanoTime() - start; frameCount++; } String s = String.format("%1$5.3f", averageTime / 1000000d); g.drawString(s, 5, 16); } @Override public void actionPerformed(ActionEvent e) { this.repaint(); } private class MouseHandler extends MouseAdapter { @Override public void mousePressed(MouseEvent e) { super.mousePressed(e); JTextField field = new JTextField("test"); Dimension d = field.getPreferredSize(); field.setBounds(e.getX(), e.getY(), d.width, d.height); add(field); } } private class ComponentHandler extends ComponentAdapter { private final GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); private final GraphicsConfiguration gc = ge.getDefaultScreenDevice().getDefaultConfiguration(); private final Random r = new Random(); @Override public void componentResized(ComponentEvent e) { super.componentResized(e); int w = getWidth(); int h = getHeight(); background = gc.createCompatibleImage(w, h, Transparency.OPAQUE); Graphics2D g = background.createGraphics(); g.clearRect(0, 0, w, h); g.setColor(Color.green.darker()); for (int i = 0; i < 128; i++) { g.drawLine(w / 2, h / 2, r.nextInt(w), r.nextInt(h)); } g.dispose(); System.out.println("Resized to " + w + " x " + h); } } }