【问题标题】:AWT custom rendering - capture smooth resizes and eliminate resize flickerAWT 自定义渲染 - 捕捉平滑调整大小并消除调整大小闪烁
【发布时间】:2011-07-26 02:57:40
【问题描述】:

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

结构(在 EDT 之外渲染)没有争议,因为我们的应用程序以这种方式运行并且不会被重写。该应用程序有一个布局模型和一个脚本模型,它们集成在一起并驱动渲染,因此必须在 AWT 绘制模型之外执行渲染。

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

以下 SSCCE 非常适合我们。但是,在调整帧大小时,它有两个缺点:

  • 偶尔会出现闪烁,尤其是在快速调整大小时
  • 从paint() 调用中调用resize(此处通过checkSize)的“平滑调整大小”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) { }
}

【问题讨论】:

  • +1 代表sscce;也可以考虑使用对应的adapters
  • @trashgod:具体是哪些适配器,我不知道使用适配器与使用监听器相比有什么好处 - 或者它是一种编码练习?
  • ComponentListener -> ComponentAdapter 等。减少空实现带来的混乱。
  • @trashgod:Java 中没有多重继承,所以必须使用匿名类。事实上,我们的实现使用了那些空的实现,所以它是从一些函数派生的。 :)
  • 有道理,但适配器是抽象的;具有命名子类型的组合是常见的approach。这是避免leaking this的一种方法。

标签: resize awt frame java


【解决方案1】:

这里是渲染的代码,由一个外部线程完成所有工作。它通过能够渲染任何实现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();
}

【讨论】:

  • 这看起来很整洁。我会尽快尝试(不幸的是,如果我今晚不能去的话,可能是星期一)。一个简单的问题 - Renderable 接口只是本地定义的,对吗? (似乎不存在 java.awt.Renderable 或类似的东西。)
  • 我不会使用根窗格,因为我们使用的自定义 UI 会接管一切,因此不需要 AFAICT。
  • Renderable 接口被定义为文件底部的内部类。在我看来,使用接口是最好的方法,因为它为您的实现提供了最大的灵活性。
  • 您确实指定了 No awt 组件将被添加到框架中,对吗?
  • 正确 - 除非需要实现更好的渲染。 :-)
【解决方案2】:

此答案留在这里供参考,但不是正确答案,因为它在 EDT 线程内呈现。

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

  }

希望对你有用!

另外,最后第二次编辑,我更改了逻辑三元运算以选择缩小或展开字符串。最后一次比较是不必要的,因为对于所讨论的比较,这些值可以大于、小于或等于彼此。没有另一种可能不会生成NullPointerException

由于我已经完全删除了整个方法,因此受到影响的文本不再相关。我将我对原始帖子所做的其他更改表示为 cmets。

【讨论】:

  • 另外,我不确定这段代码是否是从实际的生产代码中获取的,但将checkSize(String s) 之类的方法替换为ActionListener 可能会更有利框架本身。这将更符合 Java AWT 事件模型,并提供更一致的代码库。
  • 我再次编辑了原帖。删除了额外的MouseListener 代码,因为它与手头的问题无关。我还修复了窗口闪烁问题。这是对隐式类型转换所做的。当您在 render 方法中将窗口的宽度和高度的 int 除以 2 时,它最初将它们转换为双精度数以保存额外的 .5 它将得到除以奇数。例如,int 窗口大小 3 变为 1.5 作为双精度。将 double 值放入 int 变量会通过截断转换,切断 0.5,并在渲染中留下一条小白线。
  • 我是否可以建议您使用 Swing 组件,例如 JFrame,而不是过时的 AWT 类?
  • 您的代码没有编译,还有一个额外的} ;-),但删除它之后它可以工作!!现在我想知道为此使用 Swing 和 AWT 有什么区别,因为没有向 Frame/JFrame 添加任何组件?我想我会为此发布一个后续问题。
  • 回想起来,我应该还没有给出 +100,因为这个实现确实在 EDT 中进行了绘制,这有点破坏了模型。它是同步的,因此它可以在外部 EDT 渲染模型中工作,我会检查并回复您。
猜你喜欢
  • 2015-01-24
  • 1970-01-01
  • 2012-06-28
  • 2012-09-30
  • 1970-01-01
  • 1970-01-01
  • 2023-03-28
  • 2019-01-09
  • 1970-01-01
相关资源
最近更新 更多