AWT自定义渲染 – 捕获平滑resize并消除resize闪烁

我已经看了几个月了,到目前为止这是我提出的最好的。

由于我们的应用程序以这种方式运行并且不会被重写,因此结构(在EDT之外呈现)不值得辩论。 该应用程序具有布局模型和脚本模型,它们是集成和驱动渲染,因此渲染必须在AWT绘制模型之外执行。

我想要达到的是执行自定义渲染的最佳和可靠的方法。

以下SSCCE对我们来说效果很好。 但是,在帧大小调整期间,它有两个缺点:

  • 偶尔有闪烁,特别是在快速resize时
  • 从paint()调用调用resize(通过checkSize)调用的“smooth resize”hack仅适用于扩展。 缩小框架时,通常不会渲染,直到释放鼠标按钮
  • 此外,但在这里不太明显,它偶尔会抛出IllegalStateExceptions – 可以简单地捕获/忽略这些吗?

同样有用的是输入这是否是在EDT之外发生的自定义渲染路径的最佳方法。 我尝试过最多,并做了相当广泛的研究。 这种组合(后备缓冲图像,双缓冲策略)似乎效果最好。

import java.awt.Color; import java.awt.Dimension; import java.awt.Frame; import java.awt.Graphics; import java.awt.Insets; import java.awt.Toolkit; import java.awt.event.ComponentEvent; import java.awt.event.ComponentListener; import java.awt.event.MouseEvent; import java.awt.event.MouseMotionListener; import java.awt.image.BufferStrategy; public class SmoothResize extends Frame implements ComponentListener, MouseMotionListener { public SmoothResize() { addComponentListener(this); addMouseMotionListener(this); } private boolean sizeChanged = false; private Dimension old = new Dimension(0, 0); private synchronized void checkSize(String source) { int width = getWidth(); int height = getHeight(); if (old.width == width && old.height == height) return; sizeChanged = true; String type = (old.width > width && old.height > height) ? "shrink" : (old.width < width && old.height < height) ? "expand" : "resize"; System.out.println(source + " reports " + type + ": "+getWidth()+", "+getHeight()); old.setSize(width, height); } public void componentResized(ComponentEvent arg0) { checkSize("componentResized"); } public void mouseMoved(MouseEvent e) { checkSize("mouseMoved"); } public void paint(Graphics g) { checkSize("paint"); } public void update(Graphics g) { paint(g); } public void addNotify() { super.addNotify(); createBufferStrategy(2); } private synchronized void render() { BufferStrategy strategy = getBufferStrategy(); if (strategy==null || !sizeChanged) return; sizeChanged = false; // Render single frame do { // The following loop ensures that the contents of the drawing buffer // are consistent in case the underlying surface was recreated do { System.out.println("render"); Graphics draw = strategy.getDrawGraphics(); Insets i = getInsets(); int w = getWidth()-i.left-i.right; int h = getHeight()-i.top-i.bottom; draw.setColor(Color.YELLOW); draw.fillRect(i.left, i.top+(h/2), w/2, h/2); draw.fillRect(i.left+(w/2), i.top, w/2, h/2); draw.setColor(Color.BLACK); draw.fillRect(i.left, i.top, w/2, h/2); draw.fillRect(i.left+(w/2), i.top+(h/2), w/2, h/2); draw.dispose(); // Repeat the rendering if the drawing buffer contents // were restored } while (strategy.contentsRestored()); // Display the buffer strategy.show(); // Repeat the rendering if the drawing buffer was lost } while (strategy.contentsLost()); } public static void main(String[] args) { Toolkit.getDefaultToolkit().setDynamicLayout(true); System.setProperty("sun.awt.noerasebackground", "true"); SmoothResize srtest = new SmoothResize(); //srtest.setIgnoreRepaint(true); srtest.setSize(100, 100); srtest.setVisible(true); while (true) { srtest.render(); } } public void componentHidden(ComponentEvent arg0) { } public void componentMoved(ComponentEvent arg0) { } public void componentShown(ComponentEvent arg0) { } public void mouseDragged(MouseEvent e) { } } 

下面是使用外部Thread执行所有工作的代码。 它通过能够渲染实现Renderable接口的任何东西来实现这一点。 我用Swing和AWT( JFrameFrame )进行了测试,它没有闪烁。 注意,如果您实现到JRootPane并将该窗格设置为JFrame的根窗格,它会闪烁。 这与组件的缓冲方式有关,如果您希望如何使用它,则可以修复。

如果这仍然不是你想要的,那就说吧,我会再给它一次。 这实际上很有趣,因为我已经完成了任何Java GUI工作。

无论如何,你走了:

 import java.awt.Color; import java.awt.Dimension; import java.awt.Frame; import java.awt.Graphics; import java.awt.Toolkit; import javax.swing.JFrame; public class SmoothResize extends Frame implements Renderable { public static void main(String[] args) { Toolkit.getDefaultToolkit().setDynamicLayout(true); System.setProperty("sun.awt.noerasebackground", "true"); SmoothResize srtest = new SmoothResize(); RenderThread renderThread = new RenderThread(srtest); renderThread.start(); srtest.setSize(100, 100); srtest.setVisible(true); } public SmoothResize() { } public void addNotify() { super.addNotify(); createBufferStrategy(2); } @Override public Dimension getSize() { return new Dimension(getWidth(), getHeight()); } @Override public Graphics acquireGraphics() { return this.getGraphics(); } } class RenderThread extends Thread { Renderable target; Dimension last_size = new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE); public RenderThread(Renderable d) { if (d == null) { throw new NullPointerException("Drawable target cannot be null."); } target = d; } @Override public void run() { while (true) { render(false); } } private synchronized void render(boolean force) { Dimension size; do { size = target.getSize(); if (size == null) { return; } Graphics draw = target.acquireGraphics(); if (draw == null) { return; } draw.setPaintMode(); int w = (int) (((double) (size.width)) / 2 + 0.5); int h = (int) (((double) (size.height)) / 2 + 0.5); draw.setColor(Color.YELLOW); draw.fillRect(0, h, w, h); draw.fillRect(w, 0, w, h); draw.setColor(Color.BLACK); draw.fillRect(0, 0, w, h); draw.fillRect(w, h, w, h); draw.dispose(); // Repeat the rendering if the target changed size } while (!size.equals(target.getSize())); } } interface Renderable { public Graphics acquireGraphics(); public Dimension getSize(); } 

这个答案留待这里作为参考,但不是正确答案,因为它在EDT线程中呈现。

这是一个有效的解决方案! :D基本上问题是在缩小后释放鼠标之前,ComponentResized没有被适当调用。 此外,由于paint和checkSize方法是同步的,因此在极少数情况下它们可以相互排斥。 修复是覆盖Frame类中的validate方法。 如果Frame更改状态(包括收缩和增长),则始终会调用此方法。 所以我们只需要检查validation中的大小,我们实际上可以完全忘记使用ComponentResized方法。

所以,这里是按原样编译的工作代码。 我更改了一些变量名称以提高我的个人可读性。

 import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Insets; import java.awt.Toolkit; import java.awt.image.BufferStrategy; import java.awt.Frame; public class SmoothResize extends Frame { public static void main(String[] args) { Toolkit.getDefaultToolkit().setDynamicLayout(true); System.setProperty("sun.awt.noerasebackground", "true"); SmoothResize srtest = new SmoothResize(); //srtest.setIgnoreRepaint(true); srtest.setSize(100, 100); srtest.setVisible(true); } public SmoothResize() { render(); } private Dimension old_size = new Dimension(0, 0); private Dimension new_size = new Dimension(0, 0); public void validate() { super.validate(); new_size.width = getWidth(); new_size.height = getHeight(); if (old_size.equals(new_size)) { return; } else { render(); } } public void paint(Graphics g) { validate(); } public void update(Graphics g) { paint(g); } public void addNotify() { super.addNotify(); createBufferStrategy(2); } protected synchronized void render() { BufferStrategy strategy = getBufferStrategy(); if (strategy == null) { return; } // Render single frame do { // The following loop ensures that the contents of the drawing buffer // are consistent in case the underlying surface was recreated do { Graphics draw = strategy.getDrawGraphics(); Insets i = getInsets(); int w = (int)(((double)(getWidth() - i.left - i.right))/2+0.5); int h = (int)(((double)(getHeight() - i.top - i.bottom))/2+0.5); draw.setColor(Color.YELLOW); draw.fillRect(i.left, i.top + h, w,h); draw.fillRect(i.left + w, i.top, w,h); draw.setColor(Color.BLACK); draw.fillRect(i.left, i.top, w, h); draw.fillRect(i.left + w, i.top + h, w,h); draw.dispose(); // Repeat the rendering if the drawing buffer contents // were restored } while (strategy.contentsRestored()); // Display the buffer strategy.show(); // Repeat the rendering if the drawing buffer was lost } while (strategy.contentsLost()); } } 

我希望这对你有用!

另外,最后一次编辑,我更改了逻辑三元操作以选择缩小或扩展String。 最后的比较是不必要的,因为对于所讨论的比较,值可以大于,小于或等于彼此。 没有其他可能不会生成NullPointerException

受影响的文本不再相关,因为我已经完全删除了整个方法。 我指出我对原帖作为评论所做的其他更改。