Java制作动画效果的JLabel

5

我正在创建一个基本应用程序,希望在屏幕底部创建一个JLabel,从左下角开始移动到右下角,以动画方式在设定的时间内完成,同时在中心添加一个静态图像。为了实现这个功能,我创建了一个JFrame和一个使用BorderLayout的JPanel。在BorderLayout.CENTER添加了一个带有ImageIcon的JLabel,在BorderLayout.SOUTH添加了一个JPanel。我的代码虽然匆忙编写,远非完美,但是:

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension; 
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.BorderFactory;

public class GameWindow extends JPanel{

private static JLabel mainWindow, arrowLabel, arrowBox;
protected static JFrame frame;
protected static JPanel arrows;

public static int x = 600;

public GameWindow(){
    mainWindow = new JLabel("Center");
    arrowLabel = new JLabel("Moving");
    arrows = new JPanel();
    arrows.setSize(600, 100);
    arrows.setLayout(null);
    arrowBox = new JLabel("");
    arrowBox.setBounds(0, 0, 150, 100);
    arrowBox.setPreferredSize(new Dimension(150, 100));
    arrowBox.setBorder(BorderFactory.createLineBorder(Color.black));
    arrows.add(arrowBox);
    this.setSize(600,600);
    this.setLayout(new BorderLayout());
    this.add(mainWindow, BorderLayout.CENTER);
    this.add(arrows, BorderLayout.SOUTH);
}

public static void main(String[] args)
{
    GameWindow g = new GameWindow();
    frame = new JFrame("Sword Sword Revolution");
    frame.add(g);
    frame.setSize(600,600);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setVisible(true);

    Timer t = new Timer(1000, new ActionListener(){

        @Override
        public void actionPerformed(ActionEvent e) {
            arrows.add(arrowLabel);
            arrowLabel.setBounds(x, 100, 100, 100);
            x-=50;
            arrows.repaint();
            frame.repaint();
        }

    });
    t.start();
}

中间的JLabel中的ImageIcon看起来很好,带边框的空JLabel出现在底部,但是我无法使第二个带有箭头图像的JLabel显示在屏幕上。最终我将更改为使用scheduleAtFixedRate来不断移动JLabel,但现在我甚至不能让图像显示在屏幕上。

我也明白,我很可能无法使用FlowLayout,因为我了解到它不允许您设置组件的位置。我尝试使用null layout,但使用null layout时,带边框的空JLabel不会出现。我几乎看不到边框顶部在框架底部的边缘,即使使用setLocation,我也无法使其出现在我想要的位置。

显然,我的思考过程是有缺陷的,所以任何帮助都将不胜感激。

3个回答

15

在Swing应用程序中,您使用线程的方式是错误的。您不应该尝试在后台线程中添加或删除组件,而应该使用Swing计时器在Swing事件线程上执行此操作。

另外,您说的:

我想在屏幕底部有一个滚动的JLabel

请澄清您试图实现的效果。

还有关于:

我也知道我很可能无法在这种情况下使用FlowLayout,因为我了解它不允许您设置组件的位置。 我试图使用空布局,但是使用空布局时,带边框的空JLabel不会出现。我几乎看不到边框顶部在框架底部边缘,但即使使用setLocation,我也无法将其显示在想要的位置。

不要为此情况使用null布局。 有更好的布局管理器可以帮助您以更清洁和更独立于平台的方式构建应用程序。

第3次编辑
关于:

澄清一下,在屏幕底部,我想在右下角放置一个JLabel,然后在摆动计时器中,JLabel将逐渐向左移动,直到离开屏幕。如果我可以让setLocation起作用,基本前提将是将变量x设置为600,然后每秒将x减少50,然后在屏幕的新位置上重新绘制JLabel。基本动画。

我会为屏幕底部创建一个JPanel,以便用于容纳您的JLabel或通过覆盖其paintComponent(...)方法显示图像而无需JLabel。 如果将其用作容器,则其布局应为空,但是GUI的其余部分不应使用null布局。 Swing计时器将简单地更改JLabel的位置,然后在其JPanel / 容器上调用repaint()。 如果选择后者,您将使用g.drawImage(myImage,x,y)在JPanel的paintComponent(...)方法中绘制图像,并且您的计时器将更改x和/或y并在绘图JPanel上调用repaint()

此外,您可能不想在计时器中不断添加JLabel,而仅仅是移动已经显示在GUI中的JLabel。

另外,为避免焦点问题,请勿使用KeyListener捕获按键输入,而是使用键绑定。 谷歌将为您指导这个结构的出色教程。

第4次编辑的示例:

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.EnumMap;

import javax.imageio.ImageIO;
import javax.swing.*;

@SuppressWarnings("serial")
public class AnimateExample extends JPanel {
   public static final String DUKE_IMG_PATH = 
         "https://duke.kenai.com/iconSized/duke.gif";
   private static final int PREF_W = 800;
   private static final int PREF_H = 800;
   private static final int TIMER_DELAY = 20;
   private static final String KEY_DOWN = "key down";
   private static final String KEY_RELEASE = "key release";
   public static final int TRANSLATE_SCALE = 3;
   private static final String BACKGROUND_STRING = "Use Arrow Keys to Move Image";
   private static final Font BG_STRING_FONT = new Font(Font.SANS_SERIF,
         Font.BOLD, 32);
   private EnumMap<Direction, Boolean> dirMap = 
         new EnumMap<AnimateExample.Direction, Boolean>(Direction.class);
   private BufferedImage image = null;
   private int imgX = 0;
   private int imgY = 0;
   private int bgStringX; 
   private int bgStringY; 

   public AnimateExample() {
      for (Direction dir : Direction.values()) {
         dirMap.put(dir, Boolean.FALSE);
      }
      try {
         URL imgUrl = new URL(DUKE_IMG_PATH);
         image = ImageIO.read(imgUrl);
      } catch (MalformedURLException e) {
         e.printStackTrace();
      } catch (IOException e) {
         e.printStackTrace();
      }

      new Timer(TIMER_DELAY, new TimerListener()).start();

      // here we set up our key bindings
      int condition = JComponent.WHEN_IN_FOCUSED_WINDOW;
      InputMap inputMap = getInputMap(condition);
      ActionMap actionMap = getActionMap();
      for (final Direction dir : Direction.values()) {

         // for the key down key stroke
         KeyStroke keyStroke = KeyStroke.getKeyStroke(dir.getKeyCode(), 0,
               false);
         inputMap.put(keyStroke, dir.name() + KEY_DOWN);
         actionMap.put(dir.name() + KEY_DOWN, new AbstractAction() {

            @Override
            public void actionPerformed(ActionEvent arg0) {
               dirMap.put(dir, true);
            }
         });

         // for the key release key stroke
         keyStroke = KeyStroke.getKeyStroke(dir.getKeyCode(), 0, true);
         inputMap.put(keyStroke, dir.name() + KEY_RELEASE);
         actionMap.put(dir.name() + KEY_RELEASE, new AbstractAction() {

            @Override
            public void actionPerformed(ActionEvent arg0) {
               dirMap.put(dir, false);
            }
         });
      }

      FontMetrics fontMetrics = getFontMetrics(BG_STRING_FONT);
      int w = fontMetrics.stringWidth(BACKGROUND_STRING);
      int h = fontMetrics.getHeight();

      bgStringX = (PREF_W - w) / 2;
      bgStringY = (PREF_H - h) / 2;
   }

   @Override
   public Dimension getPreferredSize() {
      return new Dimension(PREF_W, PREF_H);
   }

   @Override
   protected void paintComponent(Graphics g) {
      super.paintComponent(g);
      Graphics2D g2 = (Graphics2D) g;
      g.setFont(BG_STRING_FONT);
      g.setColor(Color.LIGHT_GRAY);
      g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
            RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
      g.drawString(BACKGROUND_STRING, bgStringX, bgStringY);

      if (image != null) {
         g.drawImage(image, imgX, imgY, this);
      }
   }

   private class TimerListener implements ActionListener {
      public void actionPerformed(java.awt.event.ActionEvent e) {
         for (Direction dir : Direction.values()) {
            if (dirMap.get(dir)) {
               imgX += dir.getX() * TRANSLATE_SCALE;
               imgY += dir.getY() * TRANSLATE_SCALE;
            }
         }
         repaint();
      };
   }

   enum Direction {
      Up(KeyEvent.VK_UP, 0, -1), Down(KeyEvent.VK_DOWN, 0, 1), Left(
            KeyEvent.VK_LEFT, -1, 0), Right(KeyEvent.VK_RIGHT, 1, 0);

      private int keyCode;
      private int x;
      private int y;

      private Direction(int keyCode, int x, int y) {
         this.keyCode = keyCode;
         this.x = x;
         this.y = y;
      }

      public int getKeyCode() {
         return keyCode;
      }

      public int getX() {
         return x;
      }

      public int getY() {
         return y;
      }

   }

   private static void createAndShowGui() {
      AnimateExample mainPanel = new AnimateExample();

      JFrame frame = new JFrame("Animate Example");
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.getContentPane().add(mainPanel);
      frame.pack();
      frame.setLocationByPlatform(true);
      frame.setVisible(true);
   }

   public static void main(String[] args) {
      SwingUtilities.invokeLater(new Runnable() {
         public void run() {
            createAndShowGui();
         }
      });
   }
}

这将创建一个如下图所示的GUI界面:

enter image description here


1
+1。在使用LayoutManager时设置位置不起作用,它是LayoutManager决定位置的。 - Robin
我之前使用了Timer,但有人告诉我使用ScheduledExecutorService更好。将其改回使用Swing计时器并不是问题,我会编辑问题。 - awestover89
如果仍然无法正常工作,则创建一个简单的可编译和可运行程序,演示您的问题,并使用所有人都可以访问的在线图像,即 sscce - Hovercraft Full Of Eels
澄清一下,在屏幕底部,我想要一个位于右下角的JLabel,然后在Swing计时器中,JLabel将逐渐向左移动,直到离开屏幕。如果我能让setLocation起作用,基本前提是将变量x设置为600,然后每秒减少50,然后在屏幕上的新位置重新绘制JLabel。基本动画。 - awestover89
1
@awestover89:请查看第3次编辑和第4次编辑,以了解我所说的内容示例,包括SSCCE大小和动画效果。 - Hovercraft Full Of Eels
显示剩余2条评论

8
三种可能性:
  • 您可以使用像SlidingLayout这样的库来创建此类过渡,只需很少的代码即可。您无法调整动画,但生活会更轻松。
  • 您可以使用诸如Universal Tween Engine之类的动画引擎手动配置并调整动画(第一个库在幕后使用此库)。使用此类引擎,您可以动画化所需的任何内容:位置、颜色、字体大小等。
  • 您可以手工编写所有内容并拥抱动画世界的地狱 :)

最终,您将能够快速创建这样的动画(这是我目前正在开发的工具,用于使用LibGDX游戏框架为Android Dev配置Eclipse项目):
enter image description here enter image description here

我制作了这些库来减轻UI动画带来的痛苦(我热爱UI设计:p)。我以开源方式发布它们(免费使用,Apache-2许可),希望它们也能帮助一些人。

如果需要帮助,每个库都有一个专门的帮助论坛。


4
我会调查一下。当我还是学生时,我讨厌使用外部库的想法。可能是因为我的自尊心想要说我所有的东西都是自己做的。然而,在我的职业生涯中,我已经学会了接受现有的库,不再重新发明轮子。我会告诉你的,谢谢。 - awestover89

1

Very simple, look at this:

javax.swing.JLabel lb = new javax.swing.JLabel();
Image image = Toolkit.getDefaultToolkit().createImage("Your animated GIF");
ImageIcon xIcon = new ImageIcon(image);
xIcon.setImageObserver(this);
lb.setIcon(xIcon);

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