在Swing中展示动画背景

10

一个循环播放的 GIF 可以在 JLabel 中显示,也可以在 HTML(如 JEditorPane)中以格式化文本组件的形式显示,并循环播放。

但是要加载一张图片作为容器的背景来绘制,我通常会使用 ImageIO.read()Toolkit.getImage()(后者让我怀念上个千年)。这两种加载图像的方法都不会产生循环播放的图像,通常只有第一帧。

如何加载一个 动画 图像作为背景呢?

例如:

3个回答

16
为了获得一个自定义绘画的骑行(动画)GIF,一个技巧是使用 ImageIcon 加载它。虽然从问题中列出的两种方法返回的图像是静态的,但从 ImageIcon 获得的图像是动画的。
下面的代码将添加 50 个按钮,然后很快将“缩放星星”动画 GIF1 的帧作为它们的背景。 ImagePanel 将拉伸图像到面板的大小。
  1. 它基于这张图片。

Zooming Stars GIF

import java.awt.*;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import java.net.URL;

class ImagePanel extends JPanel {

    private Image image;

    ImagePanel(Image image) {
        this.image = image;
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.drawImage(image,0,0,getWidth(),getHeight(),this);
    }

    public static void main(String[] args) throws Exception {
        URL url = new URL("http://i.stack.imgur.com/iQFxo.gif");
        final Image image = new ImageIcon(url).getImage();
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                JFrame f = new JFrame("Image");
                f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                f.setLocationByPlatform(true);

                ImagePanel imagePanel = new ImagePanel(image);
                imagePanel.setLayout(new GridLayout(5,10,10,10));
                imagePanel.setBorder(new EmptyBorder(20,20,20,20));
                for (int ii=1; ii<51; ii++) {
                    imagePanel.add(new JButton("" + ii));
                }

                f.setContentPane(imagePanel);
                f.pack();
                f.setVisible(true);
            }
        });
    }
}

感谢您提供这个简洁的SSCCE,我之前不知道ImageIcon可以显示.gif动画。我一直在考虑是否应该在我的GUI中添加一个动画图片,现在我知道如果我想要添加它,这将是相当简单的。 - WilliamShatner

14

使用ImageIcon可能是最简单的方法。需要记住几件事:

  • ImageIcon(URL) 本身使用了 Toolkit.getImage(URL)。你可能更喜欢使用 Toolkit.createImage(URL) - getImage() 可能会使用缓存或共享的图像数据。

  • ImageIcon 使用 MediaTracker 来等待图像完全加载。

所以,你的问题可能不在于使用 ToolkitImageIO 是一个不同的东西),而在于你没有渲染完全加载的图像。尝试有趣的一件事是:

Image image = f.getToolkit().createImage(url);
//...
ImagePanel imagePanel = new ImagePanel(image);
imagePanel.prepareImage(image, imagePanel);
//...

我的Swing/AWT/J2D可能有点模糊,但是关键在于由于你的ImagePanel是一个ImageObserver,所以它可以异步地被通知图像信息。 Component.imageUpdate()方法应根据需要调用repaint

编辑:

如评论中所述,不需要调用prepareImage - 下面包含了一个可行的示例。关键在于覆盖的paintComponent方法调用了Graphics.drawImage,提供了ImageObserver钩子。imageUpdate方法(在java.awt.Component中实现)将不断使用ImageObserver.FRAMEBITS标志设置。

import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.Image;
import java.awt.image.ImageObserver;
import java.net.MalformedURLException;
import java.net.URL;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;

public class ImagePanel extends JPanel {

    private final Image image;

    public ImagePanel(Image image) {
        super();
        this.image = image;
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.drawImage(this.image, 0, 0, getWidth(), getHeight(), this);
    }

    public static void main(String[] args) throws MalformedURLException {
        final URL url = new URL("http://i.stack.imgur.com/iQFxo.gif");
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame f = new JFrame("Image");
                f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                f.setLocationByPlatform(true);

                Image image = f.getToolkit().createImage(url);
                ImagePanel imagePanel = new ImagePanel(image);
                imagePanel.setLayout(new GridLayout(5, 10, 10, 10));
                imagePanel.setBorder(new EmptyBorder(20, 20, 20, 20));
                for (int ii = 1; ii < 51; ii++) {
                    imagePanel.add(new JButton("" + ii));
                }

                f.setContentPane(imagePanel);
                f.pack();
                f.setVisible(true);
            }
        });
    }
}

很好的发现。测试了Toolkit.createImage(URL)作为new ImageIcon(url).getImage()的直接替代品,图片也能够循环。请注意,我没有打算调用prepareImage - Andrew Thompson
我明天会编辑我的回复,但我也注意到我创建的测试(你代码的修改版本)不需要prepareImage调用。我相信Graphics.drawImage调用提供了ImageObserver钩子... - kschneid
刚刚注意到你的编辑。我本以为这可能是我的第一个规范问答,但结果证明你的回答更好!我不确定是因为被压倒而感到恼怒,还是因为找到了一个更详细的人而感到高兴。(再考虑几分钟..)好吧,我很高兴。谢谢你的回答。 :) - Andrew Thompson

1
我成功将动态gif图像放入JPanel中,方法如下:
private JPanel loadingPanel() {
    JPanel panel = new JPanel();
    BoxLayout layoutMgr = new BoxLayout(panel, BoxLayout.PAGE_AXIS);
    panel.setLayout(layoutMgr);

    ClassLoader cldr = this.getClass().getClassLoader();
    java.net.URL imageURL   = cldr.getResource("img/spinner.gif");
    ImageIcon imageIcon = new ImageIcon(imageURL);
    JLabel iconLabel = new JLabel();
    iconLabel.setIcon(imageIcon);
    imageIcon.setImageObserver(iconLabel);

    JLabel label = new JLabel("Loading...");
    panel.add(iconLabel);
    panel.add(label);
    return panel;
}

这种方法的一些要点:
1. 图像文件在jar包内;
2. ImageIO.read() 返回一个 BufferedImage,它不更新 ImageObserver;
3. 寻找捆绑在jar文件中的图像的另一种选择是询问Java类加载器(即加载程序的代码)以获取文件。它知道东西在哪里。

因此,通过这样做,我能够在我的JPanel中获得我的动画gif,并且它像魅力一样工作。

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