在Java中显示GIF动画

16

大家好,我正在使用Java 1.6和Swing编写GUI应用程序。

我有一个弹出屏幕,在我的Swing GUI加载时应该显示一个gif动画,还要持续一小段时间。

我的弹出屏幕是一个JDialog。动画应该显示在一个添加到JDialog的JLabel上:

ImageIcon myImgIcon = getMyImgIcon();
JLabel imageLbl = new JLabel(myImgIcon);
add(imageLbl, BorderLayout.CENTER); 

现在的情况是,动画只有在GUI被加载后才会显示。我认为,在GUI加载时(这是我的应用程序中的一个繁重操作),EDT太忙了,无法运行动画。

参见如何使用线程显示动态GIF图像

现在的问题是,让GUI在不同的线程(而不是EDT)上加载是错误的,所以我不知道该如何解决这个问题。

有人有想法吗?


5
如果是用于应用程序的启动,请考虑使用闪屏界面 - Robin
4个回答

10

您只需将一些重要的任务从EDT线程中解放出来,并在另一个线程中完成它们。这样,gif动画将与其他正在运行的进程一起工作。

您还可以在单独的线程中创建应用程序界面(是的,不在EDT内),但仅限于显示界面之前。之后,您应该在EDT内进行所有更改,否则可能会遇到许多问题。

您还可以稍后在单独的线程中加载更多UI元素,只需确保将它们添加到EDT内显示的帧/容器中即可,这是最重要的事情。

以下是一个小的“重型”界面加载示例:

public static void main ( String[] args ) throws InvocationTargetException, InterruptedException
{
    // Main window

    final JFrame frame = new JFrame ();

    final JPanel panel = new JPanel ( new FlowLayout ( FlowLayout.LEFT, 5, 5 ) )
    {
        public Dimension getPreferredSize ()
        {
            Dimension ps = super.getPreferredSize ();
            ps.width = 0;
            return ps;
        }
    };
    frame.add ( new JScrollPane ( panel ) );

    frame.setSize ( 600, 500 );
    frame.setDefaultCloseOperation ( JFrame.EXIT_ON_CLOSE );
    frame.setLocationRelativeTo ( null );

    SwingUtilities.invokeAndWait ( new Runnable ()
    {
        public void run ()
        {
            frame.setVisible ( true );
        }
    } );

    // Load dialog

    final JDialog load = new JDialog ( frame );

    JPanel panel2 = new JPanel ( new BorderLayout () );
    panel2.setBorder ( BorderFactory.createEmptyBorder ( 15, 15, 15, 15 ) );
    load.add ( panel2 );

    final JProgressBar progressBar = new JProgressBar ( 0, 100 );
    panel2.add ( progressBar );

    load.setModal ( false );
    load.pack ();
    load.setLocationRelativeTo ( frame );

    SwingUtilities.invokeAndWait ( new Runnable ()
    {
        public void run ()
        {
            load.setVisible ( true );
        }
    } );

    // Heavy task (takes approx. 10 seconds + some time on buttons creation) 

    for ( int i = 0; i < 100; i++ )
    {
        Thread.sleep ( 100 );

        final JButton button = new JButton ( "Button" + i );
        final int finalI = i;

        // Updating panel and progress in EDT
        SwingUtilities.invokeLater ( new Runnable ()
        {
            public void run ()
            {
                panel.add ( button );
                button.revalidate ();
                progressBar.setValue ( finalI );
            }
        } );
    }
}

如您所见,所有界面更新操作均在EDT中完成,其他所有操作都在另一个线程中运行。

同时请注意,主线程不是EDT线程,因此我们可以立即在那里执行一些繁重的操作。

在某些情况下,无需立即显示加载的界面部分,因此您可以将它们全部添加到“繁重”操作的末尾。这将节省一些加载时间,并使初始化代码更加简单。

关于EDT的简要说明和我在答案中所说的...

...这是我在使用Swing L&F和大量基于Swing的应用程序工作三年后发现的内容。我挖掘了许多Swing源代码,并发现了许多有趣的事情,这些事情并不广为人知。

就像您所知道的-单个线程用于界面更新(在Swing中是EDT)的整个想法是为了让每个单独的组件可视化更新(及其事件)都排队,然后在该线程内一次执行它们。这主要是为防止绘画问题,因为框架内的每个组件都绘制到保留在内存中的单个图像上。绘画顺序是严格的,因此一个组件不会覆盖另一个组件在最终图像上的位置。绘画顺序取决于添加某些组件或容器到另一个容器中创建的组件树(这是您在使用Swing创建任何应用程序界面时执行的基本操作)。

总之,您必须将所有可视化更新(可能导致它们的方法/操作)保留在EDT中。任何其他操作可能在EDT之外完成-例如,您可以在EDT之外准备应用程序界面(除非您在已经可见的容器内添加/删除/移动组件)。

尽管如此,在极其罕见的情况下可能会出现一些内部问题。很久以前曾进行过有关该问题的讨论:
http://www.velocityreviews.com/forums/t707173-why-does-jdk-1-6-recommend-creating-swing-components-on-the-edt.html

简而言之:自从第6个JDK版本以来,Sun在文档中指出,甚至应该在EDT中创建Swing组件,以避免可能的问题。由于在创建组件时发生事件,这些问题可能出现在具有繁重接口创建的特定情况中。

无论如何,我认为在某些情况下,您可以在EDT之外创建界面以避免加载器/应用程序被卡死。在其他情况下,当无论应用程序是否卡死都不重要时,您应该使用EDT。由于一切都取决于您的情况,因此我无法提供更具体的信息...


嗯,谢谢!那个方法可以行得通。不过我有一个问题想问你。所有官方文档和在线教程都明确而反复地说swing组件只能在EDT上进行操作。这是我第一次看到有人说只要它们不可见就没关系。你能否解释一下为什么如果组件不可见,就可以打破EDT规则呢?谢谢! - whomaniac
@user1155122 我已在回答中添加了描述,因为它太长而无法在单个评论中完成。 - Mikle Garin
我在疯狂地在线搜索,而你是唯一一个给我关于这个主题实际有用信息的来源。谢谢! - whomaniac
@user1155122 如果你对推进Swing和Graphics感兴趣,我建议你找/购买一本名为“Filthy Rich Clients”(http://filthyrichclients.org/)的书,并从头到尾阅读。在那里,你会找到完整的Swing/Java2D描述 - Sun/Oracle文档和其他在线资源永远不会给你如此完整和有用的解释。 - Mikle Garin

3
也许你正在尝试制作一个动画,该动画只在应用程序启动时播放,而不会干扰即将发生的事件或组件。因此,您可能想尝试使用闪屏界面。请从以下链接了解更多信息:http://docs.oracle.com/javase/tutorial/uiswing/misc/splashscreen.html 在上面的链接中,它演示了一个名为SplashScreen的类的用法,该类仅派生自Frame类。所以机制是这样的:您展示一个单独的帧(闪屏界面,您的动画放在这里),并在一段时间后启动主应用程序。

我真的很想使用这个启动画面!我真的很想!但是,有一个小问题。我希望在 main() 方法退出后,我可以随时将启动画面关闭。根据文档,它会在 main() 退出时自动关闭!我该如何禁用它呢?有什么想法吗? - whomaniac
@user1155122:启动画面应该在main()函数结束时被销毁。main()函数的唯一作用是启动Swing事件分发线程,然后退出。 - Gilbert Le Blanc

2

'ImageIcon'类允许您加载gif动画。我使用 'getResource()' 加载图像。为此,我通常使用URL类传递文件路径。路径不一定需要在远程机器上,尽管名称URL可能会暗示这一点。

URL url = this.getClass().getResource(path);
Icon myImgIcon = new ImageIcon(url);
JLabel imageLbl = new JLabel(myImgIcon);
component.add(imageLbl, BorderLayout.CENTER);

path将是类文件夹内gif的路径。

参考资料: http://docs.oracle.com/javase/tutorial/uiswing/components/icon.html#getresource


this.getClass().getResource(path): - Mehdi

1

Inside your JFrame or whatever, use this :

Icon imgIcon = new ImageIcon(this.getClass().getResource("ajax-loader.gif"));
JLabel label = new JLabel(imgIcon);
label.setBounds(669, 42, 45, 15); // You can use your own values
frame.getContentPane().add(label);

来源:如何在Java Swing中显示动画GIF


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