Java Animate JLabel

2019-01-03 03:36发布

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:

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.

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.

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

3条回答
戒情不戒烟
2楼-- · 2019-01-03 04:05

Three possibilities:

  • You can either use a library like SlidingLayout to 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 Engine to 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 :)

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 here enter image description here

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.

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

查看更多
时光不老,我们不散
3楼-- · 2019-01-03 04:14

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);
查看更多
虎瘦雄心在
4楼-- · 2019-01-03 04:16

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.

Also, what do you mean by:

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

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.

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:

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.

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.

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.

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.

Edit 4
For example:

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:

enter image description here

查看更多
登录 后发表回答