如何消除java动画闪烁

Posted

技术标签:

【中文标题】如何消除java动画闪烁【英文标题】:How to eliminate java animation flickering 【发布时间】:2012-04-09 04:39:44 【问题描述】:

我正在编写一个需要在屏幕上平滑滚动波形的 java 应用程序。我花了很长时间阅读各种教程来弄清楚如何使这个动画尽可能流畅。据我所知,我已经做了所有正常的事情来消除闪烁(绘制到屏幕外缓冲区并一步渲染,加上覆盖更新,因此屏幕不会空白),但我的动画仍然闪烁,并且屏幕好像每次更新前都被清空了。

我确信我缺少一些基本的(可能很简单),但我没有想法。我将在下面发布一个说明问题的课程。任何帮助将不胜感激。

import java.awt.*;
import javax.swing.*;

public class FlickerPanel extends JPanel implements Runnable 

    private float [] pixelMap = new float[0];

    /** Cached graphics objects so we can control our animation to reduce flicker **/
    private Image screenBuffer;
    private Graphics bufferGraphics;

    public FlickerPanel () 
        Thread t = new Thread(this);
        t.start();
    

    private float addNoise () 
        return (float)((Math.random()*2)-1);
    

    private synchronized void advance () 
        if (pixelMap == null || pixelMap.length == 0) return;
        float [] newPixelMap = new float[pixelMap.length];
        for (int i=1;i<pixelMap.length;i++) 
            newPixelMap[i-1] = pixelMap[i];
        

        newPixelMap[newPixelMap.length-1] = addNoise();     

        pixelMap = newPixelMap;
    

    public void run() 
        while (true) 
            advance();
            repaint();

            try 
                Thread.sleep(25);
             catch (InterruptedException e) 

        
    

    private int getY (float height) 
        double proportion = (1-height)/2;
        return (int)(getHeight()*proportion);
    

    public void paint (Graphics g) 

        if (screenBuffer == null || screenBuffer.getWidth(this) != getWidth() || screenBuffer.getHeight(this) != getHeight()) 
            screenBuffer = createImage(getWidth(), getHeight());
            bufferGraphics = screenBuffer.getGraphics();
        

        if (pixelMap == null || getWidth() != pixelMap.length) 
            pixelMap = new float[getWidth()];
        

        bufferGraphics.setColor(Color.BLACK);

        bufferGraphics.fillRect(0, 0, getWidth(), getHeight());

        bufferGraphics.setColor(Color.GREEN);

        int lastX = 0;
        int lastY = getHeight()/2;

        for (int x=0;x<pixelMap.length;x++) 
            int y = getY(pixelMap[x]);
            bufferGraphics.drawLine(lastX, lastY, x, y);
            lastX = x;
            lastY = y;
        

        g.drawImage(screenBuffer, 0, 0, this);
    

    public void update (Graphics g) 
        paint(g);
    

    public static void main (String [] args) 
        JFrame frame = new JFrame("Flicker test");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setContentPane(new FlickerPanel());
        frame.setSize(500,300);
        frame.setVisible(true);
    



【问题讨论】:

我找不到您的代码有任何问题。在我的桌面上看起来很流畅。附言。您可以通过仅使用一个 pixelMap 并将其用作循环缓冲区并使用同步块保护对它的访问来更有效地执行此操作。就目前而言,创建一个新结构,每次更新都应该没问题。 +1 表示sscce。它在我的平台上闪烁得非常糟糕。 玩多了,似乎有些问题可能是由于在绘画过程中修改了 pixelMap 数据结构,所以我在屏幕的不同部分之间出现撕裂。在绘制方法开始时执行 pixelMap 的 clone() 似乎可以改善情况,但闪烁仍然存在,所以我认为这不是全部答案。 【参考方案1】:
    JPanel 中,覆盖paintComponent(Graphics) 而不是paint(Graphics) 而不是调用 Thread.sleep(n) 实现一个 Swing Timer 用于重复任务或一个 SwingWorker 用于长时间运行的任务。 有关详细信息,请参阅Concurrency in Swing。

【讨论】:

是的;即使sleep() 发生在不同的线程上并且repaint() 是线程安全的,advance() 也必须同步。 好的,这是一个很好的观点,但我认为它对闪烁没有任何直接影响 - 无论我覆盖哪种方法,它看起来都是一样的。 @trashgod 我决定删除第 2 点的第一句话 “不要阻塞 EDT(事件调度线程)——当这种情况发生时,GUI 将‘冻结’。” 因为虽然是真的,但它似乎不适用于这里。 Advance 是同步的,但这可能仍然是问题的一部分,因为 pixelMap 最终可能会在绘制过程中被替换,从而导致剪切。【参考方案2】:

JPanel 默认情况下是双缓冲的,“Swing 程序应该覆盖paintComponent() 而不是覆盖paint()。”—Painting in AWT and Swing: The Paint Methods。将覆盖 paintComponent() 的 example 与覆盖 paint() 的 example 进行对比。

附录:剪切是由在每次更新时创建和复制整个图像引起的。相反,在每次迭代时在GeneralPathdraw()Shape 上累积点数,如here 所示。

【讨论】:

我已经尝试过覆盖paintComponent并将面板设置为不进行双缓冲,但这里似乎没有任何区别,闪烁仍然发生。 这个相关的example 不会闪烁。编辑:它依赖 javax.swing.Timer 在 EDT 上更新。 我已经在上面详细说明了。还要考虑 nextGaussian() 的噪音。

以上是关于如何消除java动画闪烁的主要内容,如果未能解决你的问题,请参考以下文章

java 双缓冲,消除闪烁 的问题

css3 动画硬闪烁(帧之间没有淡入淡出)

Java中用双缓冲技术消除闪烁

终端中的光标闪烁消除,如何?

如何消除TPaintBox右边缘的闪烁(例如调整大小时)

消除无样式内容的闪烁