简体   繁体   中英

Java GUI fading animation without graphics

I'm making a game using Java GUIs, and I'm trying to do some fading animations. I know that it is much easier using Java graphics, but for this, I'm using JLabels and Swing so I can't really do that. The fading animation I'm trying to do is where I change the background color of a JLabel to go from one color to the next in a gradual manner, so the way I tried to do it was to create an array of colors and have a TimerTask set the background of the JLabel to each color one by one.

TimerTask task = new TimerTask() {
    @Override
    public void run() {
        setBackground(c);
    }
};
tasks[i] = task;

This is my TimerTask. The color c is a color I put here as my example for a color that is partly between my original and final colors. For example, if I was trying to fade from black to white, the program would ideally first set the color to a dark grey, then a medium grey, then a lighter grey, then eventually the final white color. I tested out those TimerTasks, and they didn't seem to have any issues, so I try to execute them one by one using a Timer, but it doesn't seem to execute every time.

Timer timer = new Timer();
for (int i = 0; i < f; i++) {
    timer.schedule(tasks[i], delay);
}

What usually ends up happening is that the colors fade partially, and the JLabel just ends up stuck on some color that is halfway between the original and final colors, as if not all tasks that were scheduled were completed. What am I doing wrong, and is it even sensible trying to do animations with JLabels? Thanks in advance!


EDIT: I'm sorry that I wasn't very clear with my description earlier. Perhaps it may be easier if I provided some parts of my original code, and I'll try to explain what I try to have it do bit by bit.

/** Create array of colors **/
Color[] animationColors = new Color[f];      // f is the number of frames
// find difference between old and new values of r, g, b
int diffR = newColor.getRed() - oldColor.getRed();
int diffG = newColor.getGreen() - oldColor.getGreen();
int diffB = newColor.getBlue() - oldColor.getBlue();
// fill array with colors
for (int i = 0; i < f; i++) {
    int newR = (int)(diffR * (i + 1) / f) + oldColor.getRed();
    int newG = (int)(diffG * (i + 1) / f) + oldColor.getGreen();
    int newB = (int)(diffB * (i + 1) / f) + oldColor.getBlue();
    Color c = new Color(newR, newG, newB);
    animationColors[i] = c;
}

/** Set new background after each delay **/
int delay = (int)(t / f);         // t is the time that the animation will last in total
Timer timer = new Timer();
TimerTask task = new TimerTask() {
    @Override
    public void run() {
        animationCount++;
        if (animationCount == animationColors.length) {
            animationCount = 0;
            timer.cancel();
        } else {
            setBackground(animationColors[animationCount]);
        }
    }
};
timer.schedule(task, delay, delay);

First, I try to do the fading animation by creating an array of the colors that the JLabel will be set to after each short time interval. I designated a number of frames that the animation will take place over, and at frame 1, the color of the JLabel will be set to animationColors at position 1, the color at frame 2 will be set to animationColors at position 2 etc. Each frame is a gradual fade from the first color to the final color, which for a fade from black to white will start with a darker grey, then a medium grey, a lighter grey, then eventually white.

The problem here is that quite often, the color of the tile does not fade completely. Sometimes, the color that it fades to ends up being the original color instead of what I set to be the final color. Could the issue be with the way I'm writing the timer, or is using a timer in a GUI application a bad idea in general? I read the responses to this question so far and I didn't quite understand a few of them so if you could explain them more that would be appreciated too.

Many Swing components don't deal well with translucency, especially when it is changing.

Here is an example that draws the label as a BufferedImage that can then be used as the icon of another label. Using this approach loses a lot of the functionality of a standard label, but might suffice.

import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import java.net.*;

public class FadingLabel {

    private JComponent ui = null;
    private BufferedImage fadeImage;
    private BufferedImage clearImage;
    private JLabel opaqueLabel;
    private JLabel fadeLabel;

    FadingLabel() {
        try {
            initUI();
        } catch (MalformedURLException ex) {
            ex.printStackTrace();
        }
    }

    public final void initUI() throws MalformedURLException {
        if (ui!=null) return;

        ui = new JPanel(new BorderLayout(4,4));
        ui.setBorder(new EmptyBorder(4,4,4,4));
        URL url = new URL("https://i.stack.imgur.com/F0JHK.png");
        initialiseImages(url, "Fade me!");
        fadeLabel = new JLabel(new ImageIcon(fadeImage));
        ui.add(fadeLabel);

        ActionListener fadeListener = new ActionListener() {

            float transparency;
            float difference = .1f;

            @Override
            public void actionPerformed(ActionEvent e) {
                transparency += difference;
                if (transparency>=1f) {
                    difference = -.01f;
                    transparency = 1f;
                }
                if (transparency<=0f) {
                    difference = .01f;
                    transparency = 0f;
                }
                fadeImage(transparency);
                fadeLabel.repaint();
            }
        };
        Timer timer = new Timer(30, fadeListener);
        timer.start();
    }

    private void fadeImage(float transparency) {
        Dimension d = opaqueLabel.getSize();
        fadeImage.setData(clearImage.getData());
        Graphics2D g = fadeImage.createGraphics();
        Composite composite = AlphaComposite.getInstance(AlphaComposite.SRC, transparency);
        g.setComposite(composite);
        opaqueLabel.paint(g);
    }

    private void initialiseImages(URL iconURL, String text) {
        ImageIcon ii = new ImageIcon(iconURL);
        opaqueLabel = new JLabel(text, ii, SwingConstants.LEADING);
        opaqueLabel.setForeground(Color.BLUE);
        opaqueLabel.setSize(opaqueLabel.getPreferredSize());
        Dimension d = opaqueLabel.getSize();
        clearImage = new BufferedImage(d.width, d.height, BufferedImage.TYPE_INT_ARGB);
        fadeImage = new BufferedImage(d.width, d.height, BufferedImage.TYPE_INT_ARGB);
        Graphics g = fadeImage.getGraphics();
        opaqueLabel.paint(g);
    }

    public JComponent getUI() {
        return ui;
    }

    public static void main(String[] args) {
        Runnable r = () -> {
            try {
                UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
            } catch (Exception useDefault) {
            }
            FadingLabel o = new FadingLabel();

            JFrame f = new JFrame(o.getClass().getSimpleName());
            f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            f.setLocationByPlatform(true);

            f.setContentPane(o.getUI());
            f.pack();
            f.setMinimumSize(f.getSize());

            f.setVisible(true);
        };
        SwingUtilities.invokeLater(r);
    }
}

@AndrewThompson, @TheUltimateMonkey said no Graphics, but It's a good answer, but going towards no Graphics, use this:

try {

    JFrame frm = new JFrame("Fading Label");
    JLabel lbl = new JLabel("The background is changing!");

    lbl.setOpaque(true);
    lbl.setBackground(new Color(0, 0, 0));

    frm.add(lbl);
    frm.setDefaultCloseOperation(3);
    frm.pack();
    frm.setVisible(true);

    for(int a = 0; a < 256; a++) {

        lbl.setBackground(new Color(a, a, a));
        Thread.sleep(10);

    }

}
catch(Exception e) {}

But of you want to try java.util.Timer , then use this:

try {

    JFrame frm = new JFrame("Fading Label");
    JLabel lbl = new JLabel("The background is changing!");

    lbl.setOpaque(true);
    lbl.setBackground(new Color(0, 0, 0));

    frm.add(lbl);
    frm.setDefaultCloseOperation(3);
    frm.pack();
    frm.setVisible(true);

    Timer tmr = new Timer();
    tmr.schedule(new TimerTask() {

        @Override
        public void run() {

            int a = lbl.getBackground().getRed();
            lbl.setBackground(new Color(a + 1, a + 1, a + 1));
            if(a == 254) System.exit(0); //This prevents the app from crashing, you can do something else to fix this.

        }

    }, 10, 10);

}
catch(Exception e) {}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM