Java 动画 JLabel

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/12545744/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-31 09:19:24  来源:igfitidea点击:

Java Animate JLabel

javaswinganimationtimerawt

提问by awestover89

So I am creating a basic application that I want to have a JLabel at the bottom of the screen that starts at the left bottom corner and moves, animation style, to the right bottom corner in a set time, and a static image in the center. To do this, I created a JFrame with a JPanel using BorderLayout. There is a JLabel with an ImageIcon added to BorderLayout.CENTER and a JPanel at BorderLayout.SOUTH. My code, while hastily written and far from pretty, is:

所以我正在创建一个基本的应用程序,我希望在屏幕底部有一个 JLabel,它从左下角开始,在设定的时间内移动动画样式到右下角,中间有一个静态图像. 为此,我使用 BorderLayout 创建了一个带有 JPanel 的 JFrame。有一个 JLabel 和一个 ImageIcon 添加到 BorderLayout.CENTER 和一个 JPanel 在 BorderLayout.SOUTH。我的代码虽然写得匆忙而且远非漂亮,但它是:

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();
}

}

}

The ImageIcon in the center JLabel appears fine, and the empty JLabel with a border appears at the bottom, but I cannot get the second JLabel with the arrow image to show up on screen. Eventually I will change to scheduleAtFixedRate to continuously move the JLabel, but right now I can't even get the image to appear on screen.

中心 JLabel 中的 ImageIcon 显示正常,底部出现带边框的空 JLabel,但我无法在屏幕上显示带有箭头图像的第二个 JLabel。最终我将更改为 scheduleAtFixedRate 以连续移动 JLabel,但现在我什至无法让图像出现在屏幕上。

I also understand that I will most likely not be able to use FlowLayout for this, as I understand it does not allow you to set the location of your components. I tried using null layout, but with null layout the empty JLabel with a border does not appear. I can barely make out the top of the border at the bottom edge of the frame, but even with setLocation I cannot get it to appear where I want it to.

我也知道我很可能无法为此使用 FlowLayout,因为我知道它不允许您设置组件的位置。我尝试使用空布局,但使用空布局时,不会出现带边框的空 JLabel。我几乎看不出框架底部边缘的边框顶部,但即使使用 setLocation 我也无法让它出现在我想要的地方。

Obviously, my thought process is flawed, so any help would be appreciated.

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

回答by Hovercraft Full Of Eels

Your use of threading is all wrong for Swing applications. You should not be trying to add or remove components in a background thread but instead should use a Swing Timer to do this on the Swing event thread.

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

Also, what do you mean by:

另外,你的意思是:

I want to have a scrolling JLabel at the bottom of the screen

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

Please clarify the effect you're trying to achieve.

请说明您要达到的效果。

Also regarding,

还有关于,

I also understand that I will most likely not be able to use FlowLayout for this, as I understand it does not allow you to set the location of your components. I tried using null layout, but with null layout the empty JLabel with a border does not appear. I can barely make out the top of the border at the bottom edge of the frame, but even with setLocation I cannot get it to appear where I want it to.

我也知道我很可能无法为此使用 FlowLayout,因为我知道它不允许您设置组件的位置。我尝试使用空布局,但使用空布局时,不会出现带边框的空 JLabel。我几乎看不出框架底部边缘的边框顶部,但即使使用 setLocation 我也无法让它出现在我想要的地方。

No, don't use null layout for this situation. There are much better layout managers that can help you build your application in a cleaner more platform-independent manner.

不,不要在这种情况下使用空布局。有更好的布局管理器可以帮助您以更清晰、更独立于平台的方式构建应用程序。

Edit 3
Regarding:

编辑 3
关于:

To clarify, at the bottom of the screen I want a JLabel at the far right corner, then in the swing timer, the JLabel will gradually move to the left until it leaves the screen. If I could get setLocation to work, the basic premise would be to have a variable x set to 600, and then every second decrement x by say 50 and then redraw the JLabel at the new location on the screen. Basic animation.

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

I would create a JPanel for the bottom of the screen for the purposes of either holding your JLabel or displaying the image without a JLabel by overriding its paintComponent(...)method. If you use it as a container, then yes, its layout should be null, but the rest of the GUI should not be using null layout. The Swing Timer would simply change the JLabel's location and then call repaint()on its JPanel/container. If you go the latter route, you would draw the image in the JPanel's paintComponent(...)method using g.drawImage(myImage, x, y), and your timer would change x and/or y and call repaint()on the drawing JPanel.

我会为屏幕底部创建一个 JPanel,以便通过覆盖其paintComponent(...)方法来保持 JLabel 或在没有 JLabel 的情况下显示图像。如果您将它用作容器,那么是的,它的布局应该为空,但 GUI 的其余部分不应使用空布局。Swing Timer 将简单地更改 JLabel 的位置,然后调用repaint()其 JPanel/容器。如果你走后一条路线,你会在 JPanel 的paintComponent(...)方法中使用绘制图像g.drawImage(myImage, x, y),你的计时器会改变 x 和/或 y 并调用repaint()绘图 JPanel。

Also, you likely do not want to keep adding a JLabel in your timer but rather simply moving the JLabel that's already displayed in the GUI.

此外,您可能不想继续在计时器中添加 JLabel,而只想移动已在 GUI 中显示的 JLabel。

Also, to avoid focus issues, don't use a KeyListener to capture keystroke input but rather use Key Bindings. Google will direct you to a great tutorial on this construct.

此外,为了避免焦点问题,不要使用 KeyListener 来捕获击键输入,而是使用 Key Bindings。Google 将引导您阅读有关此结构的精彩教程。

Edit 4
For example:

编辑 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();
         }
      });
   }
}

Which will create this GUI:

这将创建此 GUI:

enter image description here

在此处输入图片说明

回答by Aurelien Ribon

Three possibilities:

三种可能:

  • You can either use a library like SlidingLayoutto create such transition with very few lines of code. You won't be able to tweak the animation but your life will be easier.

  • You can use an animation engine like the Universal Tween Engineto configure everything by hand and tweak the animation as much as you want (the first lib uses this one under the hood). Using such engine, you can animate what you want: positions, colors, font size, ...

  • You can code everything by hand and embrace the hell that is the animation world :)

  • 您可以使用像SlidingLayout这样的库来创建这样的过渡,只需很少的代码行。您将无法调整动画,但您的生活会更轻松。

  • 您可以使用像Universal Tween Engine这样的动画引擎来手动配置所有内容并根据需要调整动画(第一个库在幕后使用了这个)。使用这样的引擎,您可以为您想要的内容制作动画:位置、颜色、字体大小……

  • 您可以手动编写所有代码并拥抱动画世界的地狱:)

In the end, you'll be able to quickly create animations like these (it's a tool I'm currently working on, used to configure eclipse projects for android dev using the LibGDX game framework):
enter image description hereenter image description here

最后,您将能够快速创建这样的动画(这是我目前正在开发的一个工具,用于使用 LibGDX 游戏框架为 android dev 配置 eclipse 项目):
在此处输入图片说明在此处输入图片说明

I made these libraries to ease the pain that is UI animation (I love UI design :p). I released them open-source (free to use, license apache-2), hoping they may help some people too.

我制作这些库是为了减轻 UI 动画的痛苦(我喜欢 UI 设计:p)。我将它们开源(免费使用,许可 apache-2),希望它们也能帮助到一些人。

If you need help, there is a dedicated help forum for each library.

如果您需要帮助,每个图书馆都有专门的帮助论坛。

回答by Fernando Rossato

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);