在 Java 的不同位置显示数组中的标签

Display labels from array in different locations in Java

我正在尝试使数组的元素以随机时间段显示在不同位置(具有淡入和淡出效果)。 到目前为止我所做的是制作一系列文本标签。进行了过渡。但我不知道如何创建一个 for 循环,它将在 JFrame 的不同位置显示其他标签。而且他们不应该同时出现,而是一个接一个出现。

请帮忙?

这是我的代码:

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;

public class FadingLabel {

  private int alpha = 255;
  private int increment = -5;
  public JLabel label = new JLabel("Fading Label");
  public JLabel label2 = new JLabel("Fading Label 2");
  public JLabel label3 = new JLabel("Fading Label 3");
  public JLabel label4 = new JLabel("Fading Label 4");
  JLabel labels[] = new JLabel[]{ label, label2, label3 };

  Dimension size = label.getPreferredSize();

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

      public void run() {
        new FadingLabel().makeUI();
      }
    });
  }

  public void makeUI() {
    new Timer(80, new ActionListener() {

      public void actionPerformed(ActionEvent e) {
        for (int i = 0; i <= 3; i++){
          alpha += increment;
          if (alpha >= 255) {
            alpha = 255;
            increment = -increment;
          }
          if (alpha <= 0) {
            try {
              Thread.sleep(100);
            } catch (InterruptedException interruptedException) {
              interruptedException.printStackTrace();
            }
            alpha = 0;
            increment = -increment;
          }
          label3.setForeground(new Color(0, 0, 0, alpha));
          label3.setLocation(50,60);
        }
      }
    }).start();

    JFrame frame = new JFrame();
    frame.add(labels[2]);
    frame.setPreferredSize(new Dimension(700,500));
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.pack();
    frame.setLocationRelativeTo(null);
    frame.setVisible(true);
  }
}

动画难,好的动画更难

步进动画(就像您所做的那样)不是特别有效并且可能会受到中断(来自 OS 或系统的其他部分)并且难以缩放。

相反,您应该以“持续时间”为基础的动画为目标。如果在给定时间段内发生某些事情,您可以更轻松地丢掉无法渲染的帧。

一个难以解决的概念是您不能在 GUI 的“主线程”中执行长 运行 或阻塞操作,但您也不能从“主线程”外部更新 UI(在 Swing 中,这称为事件调度线程)。

因此,相反,您需要某种方式来监视每个标签,并在它淡出时开始下一个标签淡入。这就是好的观察者模式(又名侦听器)的用武之地。

import java.awt.AlphaComposite;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.time.Duration;
import java.time.Instant;
import java.util.EventListener;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.border.EmptyBorder;

public class Main {

    public static void main(String[] args) {
        new Main();
    }

    public Main() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame();
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        private FadableLabel[] labels = new FadableLabel[]{
            new FadableLabel("A long time ago"),
            new FadableLabel("in a galaxy far, far, away..."),
            new FadableLabel("It is a period of civil war."),
            new FadableLabel("Rebel spaceships striking from a hidden base,"),
            new FadableLabel("have won their first victory against the evil Galactic Empire"),
            new FadableLabel("During the battle,"),
            new FadableLabel("Rebel spies managed to steal secret plans to the Empire's ultimate weapon,"),
            new FadableLabel("the Death Star")
        };

        private int labelIndex = -1;

        public TestPane() {
            setBorder(new EmptyBorder(50, 50, 50, 50));

            setLayout(new GridBagLayout());
            GridBagConstraints gbc = new GridBagConstraints();
            gbc.gridx = 0;
            gbc.gridy = 0;
            for (FadableLabel label : labels) {
                label.setAlpha(0);
                add(label, gbc);
            }
        }

        @Override
        public void addNotify() {
            super.addNotify();
            EventQueue.invokeLater(new Runnable() {
                @Override
                public void run() {
                    nextLabel();
                }
            });
        }

        @Override
        public void removeNotify() {
            super.removeNotify();
        }

        protected void nextLabel() {
            labelIndex++;
            if (labelIndex >= labels.length) {
                return;
            }

            FadableLabel label = labels[labelIndex];
            label.addFadableLableListener(new FadableLableListener() {
                @Override
                public void didFadeLabelIn(FadableLabel label) {
                    Timer timer = new Timer(1000, new ActionListener() {
                        @Override
                        public void actionPerformed(ActionEvent e) {
                            label.fadeOut();
                        }
                    });
                    timer.setRepeats(false);
                    timer.start();
                }

                @Override
                public void didFadeLabelOut(FadableLabel label) {
                    label.removeFadableLableListener(this);
                    nextLabel();
                }
            });
            label.fadeIn();
        }

    }

    public interface FadableLableListener extends EventListener {

        public void didFadeLabelIn(FadableLabel label);

        public void didFadeLabelOut(FadableLabel label);
    }

    public class FadableLabel extends JLabel {

        private float alpha = 1.0f;
        private Timer fadeTimer;
        private FadeRange fadeRange;

        private Instant fadeStartedAt;
        private Duration desiredFadeTime = Duration.ofMillis(1000);

        public FadableLabel() {
            super();
        }

        public FadableLabel(String text) {
            super(text);
        }

        public void addFadableLableListener(FadableLableListener listener) {
            listenerList.add(FadableLableListener.class, listener);
        }

        public void removeFadableLableListener(FadableLableListener listener) {
            listenerList.remove(FadableLableListener.class, listener);
        }

        public float getAlpha() {
            return alpha;
        }

        public void setAlpha(float alpha) {
            this.alpha = alpha;
            repaint();
        }

        protected void fireDidFadeOut() {
            FadableLableListener[] listeners = listenerList.getListeners(FadableLableListener.class);
            if (listeners.length == 0) {
                return;
            }

            for (FadableLableListener listener : listeners) {
                listener.didFadeLabelOut(this);
            }
        }

        protected void fireDidFadeIn() {
            FadableLableListener[] listeners = listenerList.getListeners(FadableLableListener.class);
            if (listeners.length == 0) {
                return;
            }

            for (FadableLableListener listener : listeners) {
                listener.didFadeLabelIn(this);
            }
        }

        protected void stopFadeTimer() {
            if (fadeTimer != null) {
                fadeStartedAt = null;
                fadeTimer.stop();
            }
        }

        protected void startFadeTimer() {
            if (fadeRange == null) {
                throw new RuntimeException("Fade range can not be null when starting animation");
            }
            fadeStartedAt = Instant.now();
            fadeTimer = new Timer(5, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    Duration runTime = Duration.between(fadeStartedAt, Instant.now());
                    double progress = Math.min(1d, Math.max(0d, runTime.toMillis() / (double) desiredFadeTime.toMillis()));
                    setAlpha(fadeRange.valueAt(progress));
                    if (progress >= 1.0) {
                        stopFadeTimer();
                        if (getAlpha() >= 1.0) {
                            fireDidFadeIn();
                        } else {
                            fireDidFadeOut();
                        }
                    }
                }
            });
            fadeTimer.start();
        }

        public void fadeIn() {
            stopFadeTimer();
            if (alpha < 1.0) {
                fadeRange = new FadeRange(alpha, 1.0f);
                startFadeTimer();
            }
        }

        public void fadeOut() {
            stopFadeTimer();
            if (alpha > 0.0) {
                fadeRange = new FadeRange(alpha, 0);
                startFadeTimer();
            }
        }

        @Override
        protected void paintComponent(Graphics g) {
            Graphics2D g2d = (Graphics2D) g.create();
            g2d.setComposite(AlphaComposite.SrcOver.derive(alpha));
            super.paintComponent(g2d);
            g2d.dispose();
        }

        protected class FadeRange {

            private float from;
            private float to;

            public FadeRange(float from, float to) {
                this.from = from;
                this.to = to;
            }

            public float getFrom() {
                return from;
            }

            public float getTo() {
                return to;
            }

            public float getDistance() {
                return getTo() - getFrom();
            }

            public float valueAt(double progress) {
                double distance = getDistance();
                double value = distance * progress;
                value += getFrom();
                return (float) value;
            }

        }

    }
}

现在,你的下一个问题。一个糟糕的方法可能是使用“绝对”或“空”布局

更好的选择是使用 GridBagLayout(可能还有 EmptyLayout)之类的东西,然后随机化 GridBagConstraintsInsets。或者,您可以创建自己的布局管理器来为您完成这项工作

请参阅 Absolute Positioning Graphic JPanel Inside JFrame Blocked by Blank Sections and 了解一些想法

一个克雷的例子

当我说动画“很难”并且它会很快变得复杂时,我不是在开玩笑。为此,我为自己编写了一个动画库,它可以完成我一直在做的所有事情。

https://github.com/RustyKnight/SuperSimpleSwingAnimationFramework

所以,这是一个基于上述库的示例,它在随机范围内移动标签,同时它已经淡出 in/out。如果没有上面的库,这种工作会很多。

import java.awt.AlphaComposite;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.time.Duration;
import java.util.EventListener;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
import org.kaizen.animation.Animatable;
import org.kaizen.animation.AnimatableAdapter;
import org.kaizen.animation.DefaultAnimatableDuration;
import org.kaizen.animation.curves.AnimationCurve;
import org.kaizen.animation.curves.Curves;
import org.kaizen.animation.ranges.AnimatableRange;
import org.kaizen.animation.ranges.FloatAnimatableRange;
import org.kaizen.animation.ranges.FloatRange;
import org.kaizen.animation.ranges.PointAnimatableRange;
import org.kaizen.animation.ranges.PointRange;

public class Main {

    public static void main(String[] args) {
        new Main();
    }

    public Main() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame();
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        private String[] textValues = new String[] {
            "A long time ago",
            "in a galaxy far, far, away...",
            "It is a period of civil war.",
            "Rebel spaceships striking from a hidden base,",
            "have won their first victory against the evil Galactic Empire",
            "During the battle,",
            "Rebel spies managed to steal secret plans to the Empire's ultimate weapon,",
            "the Death Star"
        };

        private int labelIndex = -1;
        // You'd need two if you wanted to do cross fades
        private FadableLabel label;

        private Random rnd = new Random();
        // The desired duration of the animation, 1 second for fade in,
        // 1 second for fade out and 1 second for delay between swicthing state
        private Duration desiredDuration = Duration.ofSeconds(3);
        // The desired animation curve (ease in/out)
        private AnimationCurve curve = Curves.SINE_IN_OUT.getCurve();
        // The movement animator
        private DefaultAnimatableDuration animator;

        public TestPane() {
            setLayout(null);
            label = new FadableLabel();
            label.setAlpha(0);
            add(label);
            label.addFadableLableListener(new FadableLableListener() {
                @Override
                public void didFadeLabelIn(FadableLabel label) {
                    Timer timer = new Timer(1000, new ActionListener() {
                        @Override
                        public void actionPerformed(ActionEvent e) {
                            label.fadeOut();
                        }
                    });
                    timer.setRepeats(false);
                    timer.start();
                }

                @Override
                public void didFadeLabelOut(FadableLabel label) {
                    nextText();
                }
            });
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(800, 400);
        }

        @Override
        public void addNotify() {
            super.addNotify();
            EventQueue.invokeLater(new Runnable() {
                @Override
                public void run() {
                    nextText();
                }
            });
        }

        @Override
        public void removeNotify() {
            stopAnimation();
            super.removeNotify();
        }

        protected void stopAnimation() {
            if (animator != null) {
                animator.stop();
            }
        }

        protected void nextText() {
            stopAnimation();
            labelIndex++;
            if (labelIndex >= textValues.length) {
                return;
            }

            String text = textValues[labelIndex];
            label.setText(text);
            label.setSize(label.getPreferredSize());

            // Randomise the from and to locations
            Point from = new Point(rnd.nextInt(getWidth() - label.getSize().width), rnd.nextInt(getHeight() - label.getSize().height));
            Point to = new Point(rnd.nextInt(getWidth() - label.getSize().width), rnd.nextInt(getHeight() - label.getSize().height));

            // Generate the range
            PointRange range = new PointRange(from, to);

            // Setup an animatable range of the PointRange
            animator = new PointAnimatableRange(range, desiredDuration, curve, new AnimatableAdapter<Point>() {
                @Override
                public void animationChanged(AnimatableRange<Point> animatable) {
                    label.setLocation(animatable.getValue());
                }
            });
            label.setLocation(from);
            // Make it so
            label.fadeIn();
            animator.start();
        }

    }

    public interface FadableLableListener extends EventListener {
        public void didFadeLabelIn(FadableLabel label);
        public void didFadeLabelOut(FadableLabel label);
    }

    public class FadableLabel extends JLabel {

        private FloatAnimatableRange animator;
        private AnimationCurve curve = Curves.SINE_IN_OUT.getCurve();
        private Duration desiredDuration = Duration.ofSeconds(1);

        private float alpha = 1.0f;

        public FadableLabel() {
            super();
        }

        public FadableLabel(String text) {
            super(text);
        }

        public void addFadableLableListener(FadableLableListener listener) {
            listenerList.add(FadableLableListener.class, listener);
        }

        public void removeFadableLableListener(FadableLableListener listener) {
            listenerList.remove(FadableLableListener.class, listener);
        }

        public float getAlpha() {
            return alpha;
        }

        public void setAlpha(float alpha) {
            this.alpha = alpha;
            repaint();
        }

        protected void fireDidFadeOut() {
            FadableLableListener[] listeners = listenerList.getListeners(FadableLableListener.class);
            if (listeners.length == 0) {
                return;
            }

            for (FadableLableListener listener : listeners) {
                listener.didFadeLabelOut(this);
            }
        }

        protected void fireDidFadeIn() {
            FadableLableListener[] listeners = listenerList.getListeners(FadableLableListener.class);
            if (listeners.length == 0) {
                return;
            }

            for (FadableLableListener listener : listeners) {
                listener.didFadeLabelIn(this);
            }
        }

        protected void stopFadeTimer() {
            if (animator != null) {
                animator.stop();
            }
        }

        protected void startFadeTimer(FloatRange range, AnimationListener animationListener) {
            stopFadeTimer();
            animator = new FloatAnimatableRange(range, desiredDuration, curve, new AnimatableAdapter<Float>() {
                @Override
                public void animationChanged(AnimatableRange<Float> animatable) {
                    alpha = animatable.getValue();
                    repaint();
                }

                @Override
                public void animationCompleted(Animatable animator) {
                    if (animationListener != null) {
                        animationListener.animationCompleted();
                    }
                }

            });
            animator.start();
        }

        public void fadeIn() {
            stopFadeTimer();
            startFadeTimer(new FloatRange(alpha, 1f), new AnimationListener() {
                @Override
                public void animationCompleted() {
                    fireDidFadeIn();
                }
            });
        }

        public void fadeOut() {
            stopFadeTimer();
            startFadeTimer(new FloatRange(alpha, 0f), new AnimationListener() {
                @Override
                public void animationCompleted() {
                    fireDidFadeOut();
                }
            });
        }

        @Override
        protected void paintComponent(Graphics g) {
            Graphics2D g2d = (Graphics2D) g.create();
            g2d.setComposite(AlphaComposite.SrcOver.derive(alpha));
            super.paintComponent(g2d);
            g2d.dispose();
        }

        protected interface AnimationListener {
            public void animationCompleted();
        }

    }
}

该库基于 Netbeans,将源代码提取到其他 IDE.

并不难

基于 Swing 的“延迟后执行”

made up silly solution, @MadProgrammer. added Thread.sleep(ms); in removeFadeableLabelListener. it works but i believe there is much brighter and smart solution. could you show please how to use delay timer for such task?

永远不要在事件调度线程中使用 Thread.sleep。这将导致一连串的问题,基本上会让你的程序看起来像被冻结了(因为,本质上,它是)

相反,您需要熟悉通过 API 提供给您的机制。您可以使用 SwingWorker,但更简单的解决方案可能是仅使用非重复 Swing Timer,如上所示。

import java.awt.EventQueue;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.time.Duration;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.border.EmptyBorder;

public class Main {

    public static void main(String[] args) {
        new Main();
    }

    public Main() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame();
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        public TestPane() {
            JButton btn = new JButton("Click me");
            btn.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    btn.setText("...");
                    SwingHelper.after(Duration.ofSeconds(1), new Runnable() {
                        @Override
                        public void run() {
                            btn.setEnabled(false);
                            btn.setText("Don't do that");
                        }
                    });
                }
            });
            setBorder(new EmptyBorder(10, 10, 10, 10));
            setLayout(new GridBagLayout());
            add(btn);
        }

    }

    public class SwingHelper {
        public static void after(Duration duration, Runnable runnable) {
            Timer timer = new Timer((int)duration.toMillis(), new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    runnable.run();
                }
            });
            timer.setRepeats(false);
            timer.start();
        }
    }
}