AWT自定义渲染 - 捕获平滑调整大小并消除调整大小闪烁

时间:2011-07-26 02:57:41

标签: resize awt frame java

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

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

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

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

  • 偶尔有闪烁,特别是在快速调整大小时
  • 通过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) { }
}

2 个答案:

答案 0 :(得分:3)

以下是使用外部线程执行所有工作的代码。它通过能够呈现实现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();
}

答案 1 :(得分:2)

此答案留待此处参考,但不是正确答案,因为它在EDT主题内呈现。

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

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