如何通过命令 JPanel 及其组件和颜色淡出或淡入
How can I fade out or fade in by command JPanel, its components and its color
我想制作一个包含白色背景、边框和消息的 JPanel 的玻璃面板 "please wait"。
代码示例如下:
JLabel glassLabel = new JLabel("Please wait");
FadingPanel msg = new FadingPanel();
glassLabel.setFont(new Font("Dialog", Font.BOLD, 26));
msg.setLayout(new BorderLayout());
msg.add(glassLabel,BorderLayout.NORTH);
msg.setBackground(Color.white);
msg.setFont(UIManager.getFont("Table.font").deriveFont(24f));
msg.setBorder(new CompoundBorder(new TitledBorder(""),
new EmptyBorder(20,20,20,20)));
等待查询时会淡入淡出。
问题是我的结果很糟糕。
需要帮助
考虑使用 JDialog container. When it is undecorated, you can change its opacity:
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Point;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.WindowConstants;
public class FadeDialog extends JDialog {
private float alfa = 1;
private JLabel label;
private boolean isFadeIn = true;
private JButton fadeIn, fadeOut;
FadeDialog() {
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
setLocation(new Point(300, 300));
getContentPane().setLayout(new BorderLayout(5,0));
setUndecorated(true); //opacity supported for undecorated JDialogs
JButton close = new JButton("Close");
close.addActionListener(e -> dispose());
getContentPane().add(close, BorderLayout.PAGE_END);
getContentPane().add(new ContentPane(), BorderLayout.CENTER);
pack();
setVisible(true);
Timer timer = new Timer(2000, e -> fade());//endless fade-in-out loop
timer.setInitialDelay(100);
timer.start();
}
void fade() {
alfa = isFadeIn ? alfa + 0.1f : alfa -0.1f;
if(alfa <=0 ) {
alfa = 0; isFadeIn = true;
}else if(alfa >= 1) {
alfa = 1; isFadeIn = false;
}
fadeIn.setEnabled(! isFadeIn); fadeOut.setEnabled(isFadeIn);
label.setText("Alfa is " + alfa);
setOpacity(alfa); //set JDialog opacity
}
class ContentPane extends JPanel {
ContentPane() {
setPreferredSize(new Dimension(200, 100));
setLayout(new BorderLayout());
fadeIn = new JButton("Fade In");
fadeIn.addActionListener(e -> isFadeIn = true);
add(fadeIn, BorderLayout.PAGE_START);
label = new JLabel("Alfa is " + alfa);
add(label, BorderLayout.CENTER);
fadeOut = new JButton("Fade Out");
fadeOut.addActionListener(e -> isFadeIn = false);
add(fadeOut, BorderLayout.PAGE_END);
}
}
public static void main(String[] args) {
new FadeDialog();
}
}
the other is that none of them show it with glass panel
动画 glassPane
的不透明度状态与动画任何 Swing 组件的状态没有什么不同,毕竟 glassPane
只是另一个组件。
one is that the Timer system doesn't know if the start function started and it keeps the panel hanging on because it closing it before fading the panel and then before it shows it and then it dont try to close it again
这更多是关于你自己的内部状态管理。面板不应该关心,它应该只是响应更改不透明度级别的请求,向前或向后
你应该拥有的是某种“引擎”,它可以在达到某些状态时提供事件,此时你可以决定应该做什么,从“面板”本身删除功能。
理论 TL;DR
好的,首先,一些理论。
动画...
动画是随时间变化的幻觉。在您的情况下,您要在指定的时间段内从 0 移动到 1 并再次返回。这通常被称为“线性 progression/animation”。大多数天真的动画实现会简单地向一个值添加一个常量增量,并一直这样做,直到达到所需的状态。这是天真的,因为并非所有系统都是平等的。有些人能够比其他人更快地达到所需的状态,从而使动画不均匀并提供糟糕的用户体验。
相反,您应该专注于在固定时间段内执行操作,以系统允许的速度计算所需的值。这允许动画根据系统的能力根据需要“丢弃”帧。这通常被称为“基于持续时间的动画”。
这种方法更强大,因为它允许您以非常简单的方式调整动画的速度。它还允许您执行一些非常高级的操作,例如地役权,这不容易通过线性进展实现。
摆动和动画...
Swing 是单线程的。这意味着您不能在事件调度线程的上下文中执行阻塞或长 运行 操作。
Swing 也不是线程安全的。这意味着您不应该从 EDT 上下文之外更新 UI(或 UI 所依赖的任何状态)。
对于动画,您需要通过某种方式将 post 快速、重复的事件放到 EDT 上,这样您就可以安全地对 UI 进行更改。为此,最常用的工具是 Swing Timer
...
框架
所以基于此,我们需要的是某种“引擎”,给定“运行ge”和“持续时间”可以定期通知我们“滴答声”我们可以计算动画播放的进度,并根据我们的输入计算我们应该使用的值...简单...
我个人更喜欢使用动画库,但示例中提供的简单框架基本上将所有这些概念抽象到一个可重用的框架中。
让它成为...
nb: 我运行没空,所以底层框架包含在主要例子中
好的,这一切都很好,但它对我们有什么实际帮助。本质上,上面的想法是抽象出通用功能并使其可重用(是的,我确实经常使用它)
我们现在需要的是一个可以实际使用它的组件,比如...
public interface FaderListener {
public void fadeDidComplete(FadePane pane);
}
public class FadePane extends JPanel {
private double alpha = 1;
private boolean fadingIn = true;
private DoubleAnimatable animatable;
private Duration duration = Duration.ofSeconds(5);
private List<FaderListener> listeners = new ArrayList<>(5);
public FadePane() {
setOpaque(false);
}
public void addFadeListener(FaderListener listener) {
listeners.add(listener);
}
public void removeFadeListener(FaderListener listener) {
listeners.remove(listener);
}
public boolean isFadingIn() {
return fadingIn;
}
public double getAlpha() {
return alpha;
}
@Override
public void paint(Graphics g) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.setComposite(AlphaComposite.SrcOver.derive((float)getAlpha()));
g2d.setColor(getBackground());
g2d.fillRect(0, 0, getWidth(), getHeight());
super.paint(g2d);
g2d.dispose();
}
protected void fadeTo(double to) {
double currentAlpha = getAlpha();
if (animatable != null) {
animatable.stop();
animatable = null;
}
if (currentAlpha == to) {
fadeDidComplete();
return;
}
DoubleRange animationRange = new DoubleRange(currentAlpha, to);
double maxFrom = to == 1 ? 1 : 0;
double maxTo = to == 1 ? 0 : 1;
DoubleRange maxRange = new DoubleRange(maxFrom, maxTo);
animatable = new DoubleAnimatable(animationRange, maxRange, duration, new AnimatableListener<Double>() {
@Override
public void animationChanged(Animatable<Double> animatable) {
alpha = animatable.getValue();
repaint();
}
}, new AnimatableLifeCycleListenerAdapter<Double>() {
@Override
public void animationCompleted(Animatable<Double> animatable) {
fadeDidComplete();
}
});
Animator.INSTANCE.add(animatable);
}
public void fadeIn() {
fadingIn = true;
fadeTo(1);
}
public void fadeOut() {
fadingIn = false;
fadeTo(0);
}
protected void fadeDidComplete() {
for (FaderListener listener : listeners) {
listener.fadeDidComplete(this);
}
}
}
好的,这是一个非常简单的概念。它是一个 JPanel
,它有一个 alpha
属性,它改变了组件的不透明度级别——基本上,这都是伪造的,因为 Swing 只支持不透明和 t运行sparent 组件, 不是 t运行slucent 组件。所以我们设置组件为t运行sparent,自己手动绘制背景。
该组件公开了两个方法,fadeIn
和fadeOut
,并支持一个FaderListener
,可以用来通知感兴趣的人淡入淡出操作已经完成
可运行示例...
import java.awt.AlphaComposite;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
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() {
setBackground(Color.RED);
setLayout(new BorderLayout());
FadePane pane = new FadePane();
pane.setLayout(new GridBagLayout());
pane.add(new JLabel("Look ma, no hands"));
add(pane);
JButton btn = new JButton("Switch");
btn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
btn.setEnabled(false);
if (pane.isFadingIn()) {
pane.fadeOut();
} else {
pane.fadeIn();
}
}
});
add(btn, BorderLayout.SOUTH);
pane.addFadeListener(new FaderListener() {
@Override
public void fadeDidComplete(FadePane pane) {
btn.setEnabled(true);
}
});
}
@Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
}
public interface FaderListener {
public void fadeDidComplete(FadePane pane);
}
public class FadePane extends JPanel {
private double alpha = 1;
private boolean fadingIn = true;
private DoubleAnimatable animatable;
private Duration duration = Duration.ofSeconds(5);
private List<FaderListener> listeners = new ArrayList<>(5);
public FadePane() {
setOpaque(false);
}
public void addFadeListener(FaderListener listener) {
listeners.add(listener);
}
public void removeFadeListener(FaderListener listener) {
listeners.remove(listener);
}
public boolean isFadingIn() {
return fadingIn;
}
public double getAlpha() {
return alpha;
}
public void setFaddedOut() {
alpha = 0;
fadingIn = false;
}
public void setFaddedIn() {
alpha = 1;
fadingIn = true;
}
@Override
public void paint(Graphics g) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.setComposite(AlphaComposite.SrcOver.derive((float)getAlpha()));
g2d.setColor(getBackground());
g2d.fillRect(0, 0, getWidth(), getHeight());
super.paint(g2d);
g2d.dispose();
}
protected void fadeTo(double to) {
double currentAlpha = getAlpha();
if (animatable != null) {
animatable.stop();
animatable = null;
}
if (currentAlpha == to) {
fadeDidComplete();
return;
}
DoubleRange animationRange = new DoubleRange(currentAlpha, to);
double maxFrom = to == 1 ? 1 : 0;
double maxTo = to == 1 ? 0 : 1;
DoubleRange maxRange = new DoubleRange(maxFrom, maxTo);
animatable = new DoubleAnimatable(animationRange, maxRange, duration, new AnimatableListener<Double>() {
@Override
public void animationChanged(Animatable<Double> animatable) {
alpha = animatable.getValue();
repaint();
}
}, new AnimatableLifeCycleListenerAdapter<Double>() {
@Override
public void animationCompleted(Animatable<Double> animatable) {
fadeDidComplete();
}
});
Animator.INSTANCE.add(animatable);
}
public void fadeIn() {
fadingIn = true;
fadeTo(1);
}
public void fadeOut() {
fadingIn = false;
fadeTo(0);
}
protected void fadeDidComplete() {
for (FaderListener listener : listeners) {
listener.fadeDidComplete(this);
}
}
}
public class DoubleAnimatable extends AbstractAnimatable<Double> {
public DoubleAnimatable(DoubleRange animationRange, DoubleRange maxRange, Duration duration, AnimatableListener<Double> listener, AnimatableLifeCycleListener<Double> lifeCycleListener) {
super(animationRange, duration, listener, lifeCycleListener);
double maxDistance = maxRange.getDistance();
double aniDistance = animationRange.getDistance();
double progress = Math.min(100, Math.max(0, Math.abs(aniDistance / maxDistance)));
Duration remainingDuration = Duration.ofMillis((long) (duration.toMillis() * progress));
setDuration(remainingDuration);
}
}
public interface AnimatableListener<T> {
public void animationChanged(Animatable<T> animatable);
}
public interface AnimatableLifeCycleListener<T> {
public void animationStopped(Animatable<T> animatable);
public void animationCompleted(Animatable<T> animatable);
public void animationStarted(Animatable<T> animatable);
public void animationPaused(Animatable<T> animatable);
}
public class AnimatableLifeCycleListenerAdapter<T> implements AnimatableLifeCycleListener<T> {
@Override
public void animationStopped(Animatable<T> animatable) {
}
@Override
public void animationCompleted(Animatable<T> animatable) {
}
@Override
public void animationStarted(Animatable<T> animatable) {
}
@Override
public void animationPaused(Animatable<T> animatable) {
}
}
public abstract class AbstractAnimatable<T> implements Animatable<T> {
private Range<T> range;
private LocalDateTime startTime;
private Duration duration = Duration.ofSeconds(5);
private T value;
private AnimatableListener<T> animatableListener;
private AnimatableLifeCycleListener<T> lifeCycleListener;
// private Easement easement;
private double rawOffset;
public AbstractAnimatable(Range<T> range, Duration duration, AnimatableListener<T> listener) {
this.range = range;
this.value = range.getFrom();
this.animatableListener = listener;
}
public AbstractAnimatable(Range<T> range, Duration duration, AnimatableListener<T> listener, AnimatableLifeCycleListener<T> lifeCycleListener) {
this(range, duration, listener);
this.lifeCycleListener = lifeCycleListener;
}
// public AbstractAnimatable(Range<T> range, Duration duration, Easement easement, AnimatableListener<T> listener) {
// this(range, duration, listener);
// this.easement = easement;
// }
//
// public AbstractAnimatable(Range<T> range, Duration duration, Easement easement, AnimatableListener<T> listener, AnimatableLifeCycleListener<T> lifeCycleListener) {
// this(range, duration, easement, listener);
// this.lifeCycleListener = lifeCycleListener;
// }
//
// public void setEasement(Easement easement) {
// this.easement = easement;
// }
//
// @Override
// public Easement getEasement() {
// return easement;
// }
public Duration getDuration() {
return duration;
}
public Range<T> getRange() {
return range;
}
public void setRange(Range<T> range) {
this.range = range;
}
@Override
public T getValue() {
return value;
}
protected void setDuration(Duration duration) {
this.duration = duration;
}
public double getCurrentProgress(double rawProgress) {
double progress = Math.min(1.0, Math.max(0.0, getRawProgress()));
// Easement easement = getEasement();
// if (easement != null) {
// progress = easement.interpolate(progress);
// }
return Math.min(1.0, Math.max(0.0, progress));
}
public double getRawProgress() {
if (startTime == null) {
return 0.0;
}
Duration duration = getDuration();
Duration runningTime = Duration.between(startTime, LocalDateTime.now());
double progress = rawOffset + (runningTime.toMillis() / (double) duration.toMillis());
return Math.min(1.0, Math.max(0.0, progress));
}
@Override
public void tick() {
if (startTime == null) {
startTime = LocalDateTime.now();
fireAnimationStarted();
}
double rawProgress = getRawProgress();
double progress = getCurrentProgress(rawProgress);
if (rawProgress >= 1.0) {
progress = 1.0;
}
value = getRange().valueAt(progress);
fireAnimationChanged();
if (rawProgress >= 1.0) {
fireAnimationCompleted();
}
}
@Override
public void start() {
if (startTime != null) {
// Restart?
return;
}
Animator.INSTANCE.add(this);
}
@Override
public void stop() {
stopWithNotification(true);
}
@Override
public void pause() {
rawOffset += getRawProgress();
stopWithNotification(false);
double remainingProgress = 1.0 - rawOffset;
Duration remainingTime = getDuration().minusMillis((long) remainingProgress);
setDuration(remainingTime);
lifeCycleListener.animationStopped(this);
}
protected void fireAnimationChanged() {
if (animatableListener == null) {
return;
}
animatableListener.animationChanged(this);
}
protected void fireAnimationCompleted() {
stopWithNotification(false);
if (lifeCycleListener == null) {
return;
}
lifeCycleListener.animationCompleted(this);
}
protected void fireAnimationStarted() {
if (lifeCycleListener == null) {
return;
}
lifeCycleListener.animationStarted(this);
}
protected void fireAnimationPaused() {
if (lifeCycleListener == null) {
return;
}
lifeCycleListener.animationPaused(this);
}
protected void stopWithNotification(boolean notify) {
Animator.INSTANCE.remove(this);
startTime = null;
if (notify) {
if (lifeCycleListener == null) {
return;
}
lifeCycleListener.animationStopped(this);
}
}
}
public interface Animatable<T> {
public Range<T> getRange();
public T getValue();
public void tick();
public Duration getDuration();
//public Easement getEasement();
// Wondering if these should be part of a secondary interface
// Provide a "self managed" unit of work
public void start();
public void stop();
public void pause();
}
public abstract class Range<T> {
private T from;
private T to;
public Range(T from, T to) {
this.from = from;
this.to = to;
}
public T getFrom() {
return from;
}
public T getTo() {
return to;
}
@Override
public String toString() {
return "From " + getFrom() + " to " + getTo();
}
public abstract T valueAt(double progress);
}
public class DoubleRange extends Range<Double> {
public DoubleRange(Double from, Double to) {
super(from, to);
}
public Double getDistance() {
return getTo() - getFrom();
}
@Override
public Double valueAt(double progress) {
double distance = getDistance();
double value = distance * progress;
value += getFrom();
return value;
}
}
public enum Animator {
INSTANCE;
private Timer timer;
private List<Animatable> properies;
private Animator() {
properies = new ArrayList<>(5);
timer = new Timer(5, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
List<Animatable> copy = new ArrayList<>(properies);
Iterator<Animatable> it = copy.iterator();
while (it.hasNext()) {
Animatable ap = it.next();
ap.tick();
}
if (properies.isEmpty()) {
timer.stop();
}
}
});
}
public void add(Animatable ap) {
properies.add(ap);
timer.start();
}
protected void removeAll(List<Animatable> completed) {
properies.removeAll(completed);
}
public void remove(Animatable ap) {
properies.remove(ap);
if (properies.isEmpty()) {
timer.stop();
}
}
}
}
But it's not a glassPane
...好吧,正如我所说,glassPane
只是另一个组成部分
这是一个简单的示例,它利用了框架的 glassPane
,并且会在面板淡出时将 glassPane
重置为默认组件
import java.awt.AlphaComposite;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagLayout;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
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() {
setLayout(new GridBagLayout());
JButton btn = new JButton("Switch");
btn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
Window window = SwingUtilities.getWindowAncestor(TestPane.this);
if (!(window instanceof JFrame)) {
System.out.println("Not out frame");
return;
}
JFrame frame = (JFrame) window;
FadePane pane = new FadePane();
pane.setLayout(new BorderLayout());
pane.add(new JLabel("All your base are belong to us"));
pane.setFaddedOut();
pane.addFadeListener(new FaderListener() {
@Override
public void fadeDidComplete(FadePane pane) {
System.out.println("Completed");
if (pane.getAlpha() == 1) {
System.out.println("Fade out");
pane.fadeOut();
} else {
System.out.println("Remove glasspane");
frame.setGlassPane(new JPanel());
}
}
});
frame.setGlassPane(pane);
System.out.println("Fade in");
pane.setVisible(true);
pane.fadeIn();
}
});
add(btn);
}
@Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
}
}
nb: 要求的类在前面的例子中
我想制作一个包含白色背景、边框和消息的 JPanel 的玻璃面板 "please wait"。
代码示例如下:
JLabel glassLabel = new JLabel("Please wait");
FadingPanel msg = new FadingPanel();
glassLabel.setFont(new Font("Dialog", Font.BOLD, 26));
msg.setLayout(new BorderLayout());
msg.add(glassLabel,BorderLayout.NORTH);
msg.setBackground(Color.white);
msg.setFont(UIManager.getFont("Table.font").deriveFont(24f));
msg.setBorder(new CompoundBorder(new TitledBorder(""),
new EmptyBorder(20,20,20,20)));
等待查询时会淡入淡出。 问题是我的结果很糟糕。
需要帮助
考虑使用 JDialog container. When it is undecorated, you can change its opacity:
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Point;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.WindowConstants;
public class FadeDialog extends JDialog {
private float alfa = 1;
private JLabel label;
private boolean isFadeIn = true;
private JButton fadeIn, fadeOut;
FadeDialog() {
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
setLocation(new Point(300, 300));
getContentPane().setLayout(new BorderLayout(5,0));
setUndecorated(true); //opacity supported for undecorated JDialogs
JButton close = new JButton("Close");
close.addActionListener(e -> dispose());
getContentPane().add(close, BorderLayout.PAGE_END);
getContentPane().add(new ContentPane(), BorderLayout.CENTER);
pack();
setVisible(true);
Timer timer = new Timer(2000, e -> fade());//endless fade-in-out loop
timer.setInitialDelay(100);
timer.start();
}
void fade() {
alfa = isFadeIn ? alfa + 0.1f : alfa -0.1f;
if(alfa <=0 ) {
alfa = 0; isFadeIn = true;
}else if(alfa >= 1) {
alfa = 1; isFadeIn = false;
}
fadeIn.setEnabled(! isFadeIn); fadeOut.setEnabled(isFadeIn);
label.setText("Alfa is " + alfa);
setOpacity(alfa); //set JDialog opacity
}
class ContentPane extends JPanel {
ContentPane() {
setPreferredSize(new Dimension(200, 100));
setLayout(new BorderLayout());
fadeIn = new JButton("Fade In");
fadeIn.addActionListener(e -> isFadeIn = true);
add(fadeIn, BorderLayout.PAGE_START);
label = new JLabel("Alfa is " + alfa);
add(label, BorderLayout.CENTER);
fadeOut = new JButton("Fade Out");
fadeOut.addActionListener(e -> isFadeIn = false);
add(fadeOut, BorderLayout.PAGE_END);
}
}
public static void main(String[] args) {
new FadeDialog();
}
}
the other is that none of them show it with glass panel
动画 glassPane
的不透明度状态与动画任何 Swing 组件的状态没有什么不同,毕竟 glassPane
只是另一个组件。
one is that the Timer system doesn't know if the start function started and it keeps the panel hanging on because it closing it before fading the panel and then before it shows it and then it dont try to close it again
这更多是关于你自己的内部状态管理。面板不应该关心,它应该只是响应更改不透明度级别的请求,向前或向后
你应该拥有的是某种“引擎”,它可以在达到某些状态时提供事件,此时你可以决定应该做什么,从“面板”本身删除功能。
理论 TL;DR
好的,首先,一些理论。
动画...
动画是随时间变化的幻觉。在您的情况下,您要在指定的时间段内从 0 移动到 1 并再次返回。这通常被称为“线性 progression/animation”。大多数天真的动画实现会简单地向一个值添加一个常量增量,并一直这样做,直到达到所需的状态。这是天真的,因为并非所有系统都是平等的。有些人能够比其他人更快地达到所需的状态,从而使动画不均匀并提供糟糕的用户体验。
相反,您应该专注于在固定时间段内执行操作,以系统允许的速度计算所需的值。这允许动画根据系统的能力根据需要“丢弃”帧。这通常被称为“基于持续时间的动画”。
这种方法更强大,因为它允许您以非常简单的方式调整动画的速度。它还允许您执行一些非常高级的操作,例如地役权,这不容易通过线性进展实现。
摆动和动画...
Swing 是单线程的。这意味着您不能在事件调度线程的上下文中执行阻塞或长 运行 操作。
Swing 也不是线程安全的。这意味着您不应该从 EDT 上下文之外更新 UI(或 UI 所依赖的任何状态)。
对于动画,您需要通过某种方式将 post 快速、重复的事件放到 EDT 上,这样您就可以安全地对 UI 进行更改。为此,最常用的工具是 Swing Timer
...
框架
所以基于此,我们需要的是某种“引擎”,给定“运行ge”和“持续时间”可以定期通知我们“滴答声”我们可以计算动画播放的进度,并根据我们的输入计算我们应该使用的值...简单...
我个人更喜欢使用动画库,但示例中提供的简单框架基本上将所有这些概念抽象到一个可重用的框架中。
让它成为...
nb: 我运行没空,所以底层框架包含在主要例子中
好的,这一切都很好,但它对我们有什么实际帮助。本质上,上面的想法是抽象出通用功能并使其可重用(是的,我确实经常使用它)
我们现在需要的是一个可以实际使用它的组件,比如...
public interface FaderListener {
public void fadeDidComplete(FadePane pane);
}
public class FadePane extends JPanel {
private double alpha = 1;
private boolean fadingIn = true;
private DoubleAnimatable animatable;
private Duration duration = Duration.ofSeconds(5);
private List<FaderListener> listeners = new ArrayList<>(5);
public FadePane() {
setOpaque(false);
}
public void addFadeListener(FaderListener listener) {
listeners.add(listener);
}
public void removeFadeListener(FaderListener listener) {
listeners.remove(listener);
}
public boolean isFadingIn() {
return fadingIn;
}
public double getAlpha() {
return alpha;
}
@Override
public void paint(Graphics g) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.setComposite(AlphaComposite.SrcOver.derive((float)getAlpha()));
g2d.setColor(getBackground());
g2d.fillRect(0, 0, getWidth(), getHeight());
super.paint(g2d);
g2d.dispose();
}
protected void fadeTo(double to) {
double currentAlpha = getAlpha();
if (animatable != null) {
animatable.stop();
animatable = null;
}
if (currentAlpha == to) {
fadeDidComplete();
return;
}
DoubleRange animationRange = new DoubleRange(currentAlpha, to);
double maxFrom = to == 1 ? 1 : 0;
double maxTo = to == 1 ? 0 : 1;
DoubleRange maxRange = new DoubleRange(maxFrom, maxTo);
animatable = new DoubleAnimatable(animationRange, maxRange, duration, new AnimatableListener<Double>() {
@Override
public void animationChanged(Animatable<Double> animatable) {
alpha = animatable.getValue();
repaint();
}
}, new AnimatableLifeCycleListenerAdapter<Double>() {
@Override
public void animationCompleted(Animatable<Double> animatable) {
fadeDidComplete();
}
});
Animator.INSTANCE.add(animatable);
}
public void fadeIn() {
fadingIn = true;
fadeTo(1);
}
public void fadeOut() {
fadingIn = false;
fadeTo(0);
}
protected void fadeDidComplete() {
for (FaderListener listener : listeners) {
listener.fadeDidComplete(this);
}
}
}
好的,这是一个非常简单的概念。它是一个 JPanel
,它有一个 alpha
属性,它改变了组件的不透明度级别——基本上,这都是伪造的,因为 Swing 只支持不透明和 t运行sparent 组件, 不是 t运行slucent 组件。所以我们设置组件为t运行sparent,自己手动绘制背景。
该组件公开了两个方法,fadeIn
和fadeOut
,并支持一个FaderListener
,可以用来通知感兴趣的人淡入淡出操作已经完成
可运行示例...
import java.awt.AlphaComposite;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
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() {
setBackground(Color.RED);
setLayout(new BorderLayout());
FadePane pane = new FadePane();
pane.setLayout(new GridBagLayout());
pane.add(new JLabel("Look ma, no hands"));
add(pane);
JButton btn = new JButton("Switch");
btn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
btn.setEnabled(false);
if (pane.isFadingIn()) {
pane.fadeOut();
} else {
pane.fadeIn();
}
}
});
add(btn, BorderLayout.SOUTH);
pane.addFadeListener(new FaderListener() {
@Override
public void fadeDidComplete(FadePane pane) {
btn.setEnabled(true);
}
});
}
@Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
}
public interface FaderListener {
public void fadeDidComplete(FadePane pane);
}
public class FadePane extends JPanel {
private double alpha = 1;
private boolean fadingIn = true;
private DoubleAnimatable animatable;
private Duration duration = Duration.ofSeconds(5);
private List<FaderListener> listeners = new ArrayList<>(5);
public FadePane() {
setOpaque(false);
}
public void addFadeListener(FaderListener listener) {
listeners.add(listener);
}
public void removeFadeListener(FaderListener listener) {
listeners.remove(listener);
}
public boolean isFadingIn() {
return fadingIn;
}
public double getAlpha() {
return alpha;
}
public void setFaddedOut() {
alpha = 0;
fadingIn = false;
}
public void setFaddedIn() {
alpha = 1;
fadingIn = true;
}
@Override
public void paint(Graphics g) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.setComposite(AlphaComposite.SrcOver.derive((float)getAlpha()));
g2d.setColor(getBackground());
g2d.fillRect(0, 0, getWidth(), getHeight());
super.paint(g2d);
g2d.dispose();
}
protected void fadeTo(double to) {
double currentAlpha = getAlpha();
if (animatable != null) {
animatable.stop();
animatable = null;
}
if (currentAlpha == to) {
fadeDidComplete();
return;
}
DoubleRange animationRange = new DoubleRange(currentAlpha, to);
double maxFrom = to == 1 ? 1 : 0;
double maxTo = to == 1 ? 0 : 1;
DoubleRange maxRange = new DoubleRange(maxFrom, maxTo);
animatable = new DoubleAnimatable(animationRange, maxRange, duration, new AnimatableListener<Double>() {
@Override
public void animationChanged(Animatable<Double> animatable) {
alpha = animatable.getValue();
repaint();
}
}, new AnimatableLifeCycleListenerAdapter<Double>() {
@Override
public void animationCompleted(Animatable<Double> animatable) {
fadeDidComplete();
}
});
Animator.INSTANCE.add(animatable);
}
public void fadeIn() {
fadingIn = true;
fadeTo(1);
}
public void fadeOut() {
fadingIn = false;
fadeTo(0);
}
protected void fadeDidComplete() {
for (FaderListener listener : listeners) {
listener.fadeDidComplete(this);
}
}
}
public class DoubleAnimatable extends AbstractAnimatable<Double> {
public DoubleAnimatable(DoubleRange animationRange, DoubleRange maxRange, Duration duration, AnimatableListener<Double> listener, AnimatableLifeCycleListener<Double> lifeCycleListener) {
super(animationRange, duration, listener, lifeCycleListener);
double maxDistance = maxRange.getDistance();
double aniDistance = animationRange.getDistance();
double progress = Math.min(100, Math.max(0, Math.abs(aniDistance / maxDistance)));
Duration remainingDuration = Duration.ofMillis((long) (duration.toMillis() * progress));
setDuration(remainingDuration);
}
}
public interface AnimatableListener<T> {
public void animationChanged(Animatable<T> animatable);
}
public interface AnimatableLifeCycleListener<T> {
public void animationStopped(Animatable<T> animatable);
public void animationCompleted(Animatable<T> animatable);
public void animationStarted(Animatable<T> animatable);
public void animationPaused(Animatable<T> animatable);
}
public class AnimatableLifeCycleListenerAdapter<T> implements AnimatableLifeCycleListener<T> {
@Override
public void animationStopped(Animatable<T> animatable) {
}
@Override
public void animationCompleted(Animatable<T> animatable) {
}
@Override
public void animationStarted(Animatable<T> animatable) {
}
@Override
public void animationPaused(Animatable<T> animatable) {
}
}
public abstract class AbstractAnimatable<T> implements Animatable<T> {
private Range<T> range;
private LocalDateTime startTime;
private Duration duration = Duration.ofSeconds(5);
private T value;
private AnimatableListener<T> animatableListener;
private AnimatableLifeCycleListener<T> lifeCycleListener;
// private Easement easement;
private double rawOffset;
public AbstractAnimatable(Range<T> range, Duration duration, AnimatableListener<T> listener) {
this.range = range;
this.value = range.getFrom();
this.animatableListener = listener;
}
public AbstractAnimatable(Range<T> range, Duration duration, AnimatableListener<T> listener, AnimatableLifeCycleListener<T> lifeCycleListener) {
this(range, duration, listener);
this.lifeCycleListener = lifeCycleListener;
}
// public AbstractAnimatable(Range<T> range, Duration duration, Easement easement, AnimatableListener<T> listener) {
// this(range, duration, listener);
// this.easement = easement;
// }
//
// public AbstractAnimatable(Range<T> range, Duration duration, Easement easement, AnimatableListener<T> listener, AnimatableLifeCycleListener<T> lifeCycleListener) {
// this(range, duration, easement, listener);
// this.lifeCycleListener = lifeCycleListener;
// }
//
// public void setEasement(Easement easement) {
// this.easement = easement;
// }
//
// @Override
// public Easement getEasement() {
// return easement;
// }
public Duration getDuration() {
return duration;
}
public Range<T> getRange() {
return range;
}
public void setRange(Range<T> range) {
this.range = range;
}
@Override
public T getValue() {
return value;
}
protected void setDuration(Duration duration) {
this.duration = duration;
}
public double getCurrentProgress(double rawProgress) {
double progress = Math.min(1.0, Math.max(0.0, getRawProgress()));
// Easement easement = getEasement();
// if (easement != null) {
// progress = easement.interpolate(progress);
// }
return Math.min(1.0, Math.max(0.0, progress));
}
public double getRawProgress() {
if (startTime == null) {
return 0.0;
}
Duration duration = getDuration();
Duration runningTime = Duration.between(startTime, LocalDateTime.now());
double progress = rawOffset + (runningTime.toMillis() / (double) duration.toMillis());
return Math.min(1.0, Math.max(0.0, progress));
}
@Override
public void tick() {
if (startTime == null) {
startTime = LocalDateTime.now();
fireAnimationStarted();
}
double rawProgress = getRawProgress();
double progress = getCurrentProgress(rawProgress);
if (rawProgress >= 1.0) {
progress = 1.0;
}
value = getRange().valueAt(progress);
fireAnimationChanged();
if (rawProgress >= 1.0) {
fireAnimationCompleted();
}
}
@Override
public void start() {
if (startTime != null) {
// Restart?
return;
}
Animator.INSTANCE.add(this);
}
@Override
public void stop() {
stopWithNotification(true);
}
@Override
public void pause() {
rawOffset += getRawProgress();
stopWithNotification(false);
double remainingProgress = 1.0 - rawOffset;
Duration remainingTime = getDuration().minusMillis((long) remainingProgress);
setDuration(remainingTime);
lifeCycleListener.animationStopped(this);
}
protected void fireAnimationChanged() {
if (animatableListener == null) {
return;
}
animatableListener.animationChanged(this);
}
protected void fireAnimationCompleted() {
stopWithNotification(false);
if (lifeCycleListener == null) {
return;
}
lifeCycleListener.animationCompleted(this);
}
protected void fireAnimationStarted() {
if (lifeCycleListener == null) {
return;
}
lifeCycleListener.animationStarted(this);
}
protected void fireAnimationPaused() {
if (lifeCycleListener == null) {
return;
}
lifeCycleListener.animationPaused(this);
}
protected void stopWithNotification(boolean notify) {
Animator.INSTANCE.remove(this);
startTime = null;
if (notify) {
if (lifeCycleListener == null) {
return;
}
lifeCycleListener.animationStopped(this);
}
}
}
public interface Animatable<T> {
public Range<T> getRange();
public T getValue();
public void tick();
public Duration getDuration();
//public Easement getEasement();
// Wondering if these should be part of a secondary interface
// Provide a "self managed" unit of work
public void start();
public void stop();
public void pause();
}
public abstract class Range<T> {
private T from;
private T to;
public Range(T from, T to) {
this.from = from;
this.to = to;
}
public T getFrom() {
return from;
}
public T getTo() {
return to;
}
@Override
public String toString() {
return "From " + getFrom() + " to " + getTo();
}
public abstract T valueAt(double progress);
}
public class DoubleRange extends Range<Double> {
public DoubleRange(Double from, Double to) {
super(from, to);
}
public Double getDistance() {
return getTo() - getFrom();
}
@Override
public Double valueAt(double progress) {
double distance = getDistance();
double value = distance * progress;
value += getFrom();
return value;
}
}
public enum Animator {
INSTANCE;
private Timer timer;
private List<Animatable> properies;
private Animator() {
properies = new ArrayList<>(5);
timer = new Timer(5, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
List<Animatable> copy = new ArrayList<>(properies);
Iterator<Animatable> it = copy.iterator();
while (it.hasNext()) {
Animatable ap = it.next();
ap.tick();
}
if (properies.isEmpty()) {
timer.stop();
}
}
});
}
public void add(Animatable ap) {
properies.add(ap);
timer.start();
}
protected void removeAll(List<Animatable> completed) {
properies.removeAll(completed);
}
public void remove(Animatable ap) {
properies.remove(ap);
if (properies.isEmpty()) {
timer.stop();
}
}
}
}
But it's not a
glassPane
...好吧,正如我所说,glassPane
只是另一个组成部分
这是一个简单的示例,它利用了框架的 glassPane
,并且会在面板淡出时将 glassPane
重置为默认组件
import java.awt.AlphaComposite;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagLayout;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
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() {
setLayout(new GridBagLayout());
JButton btn = new JButton("Switch");
btn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
Window window = SwingUtilities.getWindowAncestor(TestPane.this);
if (!(window instanceof JFrame)) {
System.out.println("Not out frame");
return;
}
JFrame frame = (JFrame) window;
FadePane pane = new FadePane();
pane.setLayout(new BorderLayout());
pane.add(new JLabel("All your base are belong to us"));
pane.setFaddedOut();
pane.addFadeListener(new FaderListener() {
@Override
public void fadeDidComplete(FadePane pane) {
System.out.println("Completed");
if (pane.getAlpha() == 1) {
System.out.println("Fade out");
pane.fadeOut();
} else {
System.out.println("Remove glasspane");
frame.setGlassPane(new JPanel());
}
}
});
frame.setGlassPane(pane);
System.out.println("Fade in");
pane.setVisible(true);
pane.fadeIn();
}
});
add(btn);
}
@Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
}
}
nb: 要求的类在前面的例子中