如何在Swing中调整组件大小时避免闪烁?

3

如果我运行以下示例,右侧的JSplitPane会闪烁。是否有避免这种情况的方法?

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;

public class FlickerTest
{
    int width = 1;

    private void create()
    {
        final JFrame f = new JFrame("JSplitPane");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        JPanel p1 = new JPanel();
        p1.setPreferredSize(new Dimension(100, 300));

        JPanel p2 = new JPanel();
        p2.setPreferredSize(new Dimension(0,0));
        p2.setBackground(Color.gray);

        JSplitPane jsp = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, p1, p2);
        jsp.setSize(new Dimension(400, 800));

        Timer timer = new Timer(1, new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                width++;

                if (width == 2)
                {
                    try
                    {
                        Thread.sleep(1500);
                    }
                    catch (Exception ex)
                    {
                    }
                }

                int frameWidth = f.getWidth() + width;
                Dimension d    = new Dimension(frameWidth, f.getHeight());
                f.setSize(d);

                if (width > 20)
                {
                    Timer t = (Timer) e.getSource();
                    t.stop();
                }

            }
        });

        f.add(jsp);
        f.pack();
        f.setLocationRelativeTo(null);
        f.setVisible(true);
        timer.start();
    }

    public static void main(String[] args) throws Exception
    {
        new FlickerTest().create();
    }
}

1
在我的电脑上,使用这段代码没有闪烁。 - toto2
顺便提一下,我是在Mac OSX上运行这个程序的。 - John Thorhauer
嗯,也许这是一个Mac的问题。等我有机会时,我会在Windows上再试一次。谢谢。 - John Thorhauer
是的,我的操作系统是Windows。但你应该尽量遵守Swing线程模型,虽然我不确定这是否是导致你遇到问题的原因。 - toto2
应该补充说明的是,我从未看到闪烁,运行的是Windows 7和Java 1.6.0-b105。无论哪个版本的代码都没有闪烁,但原始版本会“跳跃”变得非常宽,而我发布的版本则逐像素增加。 - Andrew Thompson
我的问题解决方案是在Swing中使用setSize(150, 150)来限制大小不超过150。原问题链接:http://stackoverflow.com/questions/24209666/rendering-with-swing-flickers-when-mouse-over-added-solution?noredirect=1#comment37392309_24209666 - Ubaby
7个回答

2

1
对我来说,将noerasebackround设置为true起作用了:
System.setProperty("sun.awt.noerasebackground", "true");

1
不要在计时器中使用Thread.sleep()。这会阻止EDT响应事件和绘制操作。

2
sleep调用与闪烁无关,因为它仅在事件的第一次触发时调用,而不是在后续事件上调用。 - John Thorhauer
1
也许是,也许不是,重点是你不应该在EDT上使用Thead.sleep()。 - camickr

1

这个技巧可以提高Win7+Aero的重绘率:将resizable设置为null,并提供自己的resize hook。虽然不是完美的,但仍然比较好。请查看我的示例:

enter image description here

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

class ResizeHookDemo extends JDialog {
  private final static int width = 580, height = 350;
  private final JFileChooser fc;
  private java.awt.geom.GeneralPath gp;

  public ResizeHookDemo() {
    super((JDialog)null, "Choose File", true);

    fc = new JFileChooser() {

     @Override
     public void paint(Graphics g) {
       super.paint(g);
       int w = getWidth();
       int h = getHeight();
       g.setColor(new Color(150, 150, 150, 200));
       g.drawLine(w-7, h, w, h-7);
       g.drawLine(w-11, h, w, h-11);
       g.drawLine(w-15, h, w, h-15);

       gp = new java.awt.geom.GeneralPath();      
       gp.moveTo(w-17, h);
       gp.lineTo(w, h-17);
       gp.lineTo(w, h);
       gp.closePath();
     }

    };
    fc.addActionListener(new ActionListener() {

      public void actionPerformed(ActionEvent e) {
        if (e.getActionCommand().equals("CancelSelection")) {
          setVisible(false);
          // action...
        }
        else if (e.getActionCommand().equals("ApproveSelection")) {
          setVisible(false);
          // action...
        }
      }
    });

    MouseInputListener resizeHook = new MouseInputAdapter() {
      private Point startPos = null;

      public void mousePressed(MouseEvent e) {
        if (gp.contains(e.getPoint())) 
          startPos = new Point(getWidth()-e.getX(), getHeight()-e.getY());
      }

      public void mouseReleased(MouseEvent mouseEvent) {
        startPos = null;
      }

      public void mouseMoved(MouseEvent e) {
        if (gp.contains(e.getPoint()))
          setCursor(Cursor.getPredefinedCursor(Cursor.SE_RESIZE_CURSOR));
        else
          setCursor(Cursor.getDefaultCursor());
      }

      public void mouseDragged(MouseEvent e) {
        if (startPos != null) {

          int dx = e.getX() + startPos.x;
          int dy = e.getY() + startPos.y;

          setSize(dx, dy);
          repaint();
        }
      }         
    };

    fc.addMouseMotionListener(resizeHook);
    fc.addMouseListener(resizeHook);
    fc.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 20));
    add(fc);

    setResizable(false);

    setMinimumSize(new Dimension(width, height));
    setDefaultCloseOperation(HIDE_ON_CLOSE);
    setLocationRelativeTo(null);
  }

  public static void main(String args[]) {
    System.out.println("Starting demo...");
    SwingUtilities.invokeLater(new Runnable() {

      @Override
      public void run() {
        new ResizeHookDemo().setVisible(true);
      }
    });
  }
}

1

这个对你有用吗?

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;

public class FlickerTest
{
    int width = 1;

    private void create()
    {
        final JFrame f = new JFrame("JSplitPane");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        JPanel p1 = new JPanel();
        p1.setPreferredSize(new Dimension(100, 300));

        JPanel p2 = new JPanel();
        p2.setPreferredSize(new Dimension(0,0));
        p2.setBackground(Color.gray);

        JSplitPane jsp = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, p1, p2);
        jsp.setSize(new Dimension(400, 800));

        Timer timer = new Timer(1, new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                width++;

                int frameWidth = f.getWidth() + width;
                if (width>1502) {
                    frameWidth = f.getWidth() + width - 1500;
                }
                Dimension d    = new Dimension(frameWidth, f.getHeight());
                f.setSize(d);
                if (width > 1520)
                {
                    Timer t = (Timer) e.getSource();
                    t.stop();
                }

            }
        });

        f.add(jsp);
        f.pack();
        //f.setLocationRelativeTo(null);
        f.setVisible(true);
        timer.start();
    }

    public static void main(String[] args) throws Exception
    {
        new FlickerTest().create();
    }
}

顺便说一句 - 我们中的许多人没有宽1500(+因为UI从屏幕中间开始)像素的显示器。


不,这并没有任何影响。 - John Thorhauer
@John Thorhauer 工作正常,没有像你的例子 Andrew Thompson 一样冻结调整大小。+1 - mKorbel

1

几个建议:

  • 使用30毫秒的延迟而不是1毫秒。 30毫秒可以让您获得平滑的30帧每秒,这已经足够了。
  • 使用setBounds而不是setSize。 不确定是否有区别,但可以更好地控制坐标。
  • 在您的Timer中,不要调用sleep(),而是在计时器上设置初始延迟
  • 删除对setPreferredSize(0, 0)的调用

public static class FlickerTest {

    int width = 1;

    private void create() {
        final JFrame f = new JFrame("JSplitPane");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        JPanel p1 = new JPanel();
        p1.setPreferredSize(new Dimension(100, 300));

        JPanel p2 = new JPanel();
        p2.setBackground(Color.gray);

        JSplitPane jsp = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, p1, p2);
        jsp.setSize(new Dimension(400, 800));

        Timer timer = new Timer(30, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                width++;


                int frameWidth = f.getWidth() + width;
                Dimension d = new Dimension(frameWidth, f.getHeight());
                f.setBounds(f.getX(), f.getY(), frameWidth, f.getHeight());
                //f.setSize(frameWidth, f.getHeight());

                if (width > 20) {
                    Timer t = (Timer) e.getSource();
                    t.stop();
                }

            }
        });

        f.add(jsp);
        f.pack();
        f.setLocationRelativeTo(null);
        f.setVisible(true);
        timer.setInitialDelay(1500);
        timer.start();
    }

}

1
"30毫秒为每秒30帧提供了流畅的画面,尝试使用“33 1/3 FPS”(每秒33.3帧)." - Andrew Thompson
1
+1 我开始在大约30毫秒或33⅓赫兹的频率下看到平滑的结果。 :-) - trashgod
计时器从1变为30似乎有很大帮助。 - John Thorhauer

-1

非常抱歉 - 我匆忙回答了你的问题。是的,在Swing中不应该使用update。

为了补救,我找到了一篇由一位OS X Swing程序员撰写的博客文章,在第二段中写道,“在Mac上编程调整大小会导致Java闪烁”。

http://explodingpixels.wordpress.com/2008/09/28/the-heads-up-display-hud/

既然在我这里工作的旧XP机器上运行代码时没有出现闪烁,看来上面说的还是正确的。


我覆盖了JPanels和SplitPane上的update()调用,但问题仍然存在。顺便说一下,我在MAC OSX上运行此程序,不确定是否有影响。 - John Thorhauer
@john,@hover,我的原始答案是错误的,所以我已经更改了它。 - Paul

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接