java - 带有 JComponents 的滑动效果菜单
问题描述
我正在尝试使用 JLabels 和计时器制作一些滑动效果。我只想使用两个计时器(IN 和 OUT)来管理多个组件的效果。问题是,只有当我不快速从一个 JLabel 移动到另一个 JLabel 并且我不知道如何管理这件事时,一切才能正常工作。
这是我的代码:
public class Sliders extends JFrame {
private JPanel contentPane;
JLabel label,label_1;
static RainDrop frame;
javax.swing.Timer in,out;
/**
* Launch the application.
*/
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
frame = new RainDrop();
frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* Create the frame.
*/
public Sliders() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(100, 100, 450, 300);
contentPane = new JPanel();
contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
setContentPane(contentPane);
contentPane.setLayout(null);
label = new JLabel("");
label.addMouseListener(new MouseAdapter() {
@Override
public void mouseEntered(MouseEvent a1) {
setIN(2,0,label);
System.out.println("ENTRATO");
checkloop_out_mag(-270,label);
}
@Override
public void mouseExited(MouseEvent a2) {
in.stop();
setOUT(-2,0,label);
System.out.println("USCITO");
}
});
label.setBackground(Color.ORANGE);
label.setOpaque(true);
label.setBounds(-270, 0, 337, 44);
contentPane.add(label);
label_1 = new JLabel("");
label_1.addMouseListener(new MouseAdapter() {
@Override
public void mouseEntered(MouseEvent b1) {
setIN(2,44,label_1);
System.out.println("ENTRATO");
checkloop_out_mag(-270,label_1);
}
@Override
public void mouseExited(MouseEvent b2) {
in.stop();
setOUT(-2,44,label_1);
System.out.println("USCITO");
}
});
label_1.setOpaque(true);
label_1.setBackground(Color.GREEN);
label_1.setBounds(-270, 44, 337, 44);
contentPane.add(label_1);
}
public void setIN(int x,int y,JLabel label) {
in = new javax.swing.Timer(2, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
label.setLocation(label.getBounds().x+x,y);
System.out.println("SPOSTO");
System.out.println("CONTROLLO");
checkloop_in_magequals(0,label);
}
});
in.setRepeats(true);
in.start();
}
public void setOUT(int x,int y,JLabel label) {
out = new javax.swing.Timer(2, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
label.setLocation(label.getBounds().x+x,y);
System.out.println("SPOSTO");
System.out.println("CONTROLLO");
checkloop_out_equals(-270,label);
}
});
out.setRepeats(true);
out.start();
}
public void checkloop_out_equals(int z,JLabel label) {
if (label.getBounds().x==z){
out.stop();
System.out.println("STOP");
}
}
public void checkloop_out_mag(int z,JLabel label) {
if (label.getBounds().x>z){
out.stop();
System.out.println("STOP");
}
}
public void checkloop_in_magequals(int z,JLabel label) {
if (label.getBounds().x>=z){
in.stop();
System.out.println("STOP");
}
}
}
有没有办法只使用两个计时器来修复代码?还是每个 JComponent 都需要两个计时器?
解决方案
首先,使用动画框架,比如Trident或The Timing Framework或The Universal Tween Engine,它们提供了大量的功能,否则需要大量的编码才能完成。
动画是一个复杂的主题,所以我只会介绍一些基本的基础知识。动画是随时间变化的幻觉(告诉你这将是基本的)。
你的问题的本质归结为两个基本的事情:
- 您需要一个可以提供“滴答声”和(相对)定期间隔的中央“时钟”
- 一个足够不可知论、足够解耦的 API,它不关心动画的“什么”,只是在给定持续时间和范围的情况下,它可以计算每个“滴答”所需的属性
一个重要的概念是,动画应该在一段时间内播放,而不是在值之间。这样做的主要原因是灵活性。更改动画的持续时间以更改速度要容易得多,并且它允许系统相对简单地“丢弃”帧。记得我说过“动画是随时间变化的幻觉” ——这就是我所说的。
简单的 ...
让我们从一些基础知识开始......
public 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();
}
}
Range
from
描述可以用“开始”( )值和“目标”( )值来衡量的事物to
。这允许使用提供许多重要的值,但最值得注意的是,我们要执行的动画的“范围”。
在过去,我将这样的概念用于各种值,包括Point
,Rectangle
甚至Color
public interface AnimationPropertiesListener<T> {
public void stateChanged(AnimationProperties<T> animator);
}
public interface AnimationProperties<T> {
public Range<T> getRange();
public T getValue();
public boolean tick();
public void setDuration(Duration duration);
public Duration getDuration();
}
public abstract class AbstractAnimationProperties<T> implements AnimationProperties<T> {
private Range<T> range;
private LocalDateTime startTime;
private Duration duration = Duration.ofSeconds(5);
private T value;
private AnimationPropertiesListener<T> listener;
public AbstractAnimationProperties(Range<T> range, AnimationPropertiesListener<T> listener) {
this.range = range;
this.value = range.getFrom();
this.listener = listener;
}
public void setDuration(Duration duration) {
this.duration = duration;
}
public Duration getDuration() {
return duration;
}
public Range<T> getRange() {
return range;
}
@Override
public T getValue() {
return value;
}
@Override
public boolean tick() {
if (startTime == null) {
startTime = LocalDateTime.now();
}
Duration duration = getDuration();
Duration runningTime = Duration.between(startTime, LocalDateTime.now());
Duration timeRemaining = duration.minus(runningTime);
if (timeRemaining.isNegative()) {
runningTime = duration;
}
double progress = (runningTime.toMillis() / (double) duration.toMillis());
value = calculateValue(progress);
listener.stateChanged(this);
return progress >= 1.0;
}
public abstract T calculateValue(double progress);
}
“动画属性”是我们试图实现的潜在力量。它负责计算动画想要运行的时间量,动画已经运行的时间量,并提供一种计算指定动画Range
值的结果值的方法。(注意calculateValue
可能是 的属性,Range
但我只是把它留在原处)
好的,这一切都很好,但我们实际上需要一个中央时钟来提供所有的刻度并通知属性
public enum Animator {
INSTANCE;
private Timer timer;
private List<AnimationProperties> properies;
private Animator() {
properies = new ArrayList<>(5);
timer = new Timer(5, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
Iterator<AnimationProperties> it = properies.iterator();
while (it.hasNext()) {
AnimationProperties ap = it.next();
if (ap.tick()) {
it.remove();
}
}
if (properies.isEmpty()) {
timer.stop();
}
}
});
}
public void add(AnimationProperties ap) {
properies.add(ap);
timer.start();
}
public void remove(AnimationProperties ap) {
properies.remove(ap);
if (properies.isEmpty()) {
timer.stop();
}
}
}
好的,这是一个非常简单的实现。它只是一个Timer
运行速度非常快的 Swing。当有AnimationProperties
可用时,它会继续循环,直到所有AnimationProperties
tick
方法返回true
(完成),然后它停止(所以除非在后台发生事情,否则它不会做)
好的,但这一切对我们有什么帮助?
好吧,基本上,我们要做的就是计算width
一段时间内从给定值到给定值的组件的新值。既然width
被定义为 a int
,我们可以基于上面的抽象/泛型类创建一系列具体的类,例如...
public class IntRange extends Range<Integer> {
public IntRange(Integer from, Integer to) {
super(from, to);
}
public Integer getDistance() {
return getTo() - getFrom();
}
}
public class IntAnimationProperties extends AbstractAnimationProperties<Integer> {
public IntAnimationProperties(IntRange animationRange, IntRange maxRange, Duration duration, AnimationPropertiesListener<Integer> listener) {
super(animationRange, listener);
int maxDistance = maxRange.getDistance();
int aniDistance = animationRange.getDistance();
double progress = Math.min(100, Math.max(0, Math.abs(aniDistance/ (double)maxDistance)));
Duration remainingDuration = Duration.ofMillis((long)(duration.toMillis() * progress));
setDuration(remainingDuration);
}
@Override
public Integer calculateValue(double progress) {
IntRange range = (IntRange)getRange();
int distance = range.getDistance();
int value = (int) Math.round((double) distance * progress);
value += range.getFrom();
return value;
}
}
这里真正需要注意的是,IntAnimationProperties
还需要一个“最大范围”值。这是动画预期运行的总可用范围。这用于计算“动画范围”所在的当前“进度”值。
考虑一下当用户退出时,如果一个面板被展开一半会发生什么?通常它必须使用整个持续时间来动画回那个半范围。
相反,此实现计算移动到所需点所需的“剩余”持续时间,在上面的示例中,这是正常持续时间的一半。
例子...
因此,基于上述内容,我们最终可能会得到类似...
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
setLayout(null);
Slider slider1 = new Slider();
slider1.setBackground(Color.BLUE);
slider1.setLocation(0, 44);
add(slider1);
Slider slider2 = new Slider();
slider2.setBackground(Color.MAGENTA);
slider2.setLocation(0, 88);
add(slider2);
}
@Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
}
public class Slider extends JPanel {
private AnimationProperties<Integer> ap;
private IntRange maxRange = new IntRange(44, 150);
private Duration duration = Duration.ofSeconds(5);
public Slider() {
setSize(44, 44);
addMouseListener(new MouseAdapter() {
@Override
public void mouseEntered(MouseEvent e) {
animateTo(150);
}
@Override
public void mouseExited(MouseEvent e) {
animateTo(44);
}
public void animateTo(int to) {
if (ap != null) {
Animator.INSTANCE.remove(ap);
}
IntRange animationRange = new IntRange(getWidth(), to);
ap = new IntAnimationProperties(animationRange, maxRange, duration, new AnimationPropertiesListener<Integer>() {
@Override
public void stateChanged(AnimationProperties<Integer> animator) {
setSize(animator.getValue(), 44);
repaint();
}
});
Animator.INSTANCE.add(ap);
}
});
}
}
public enum Animator {
INSTANCE;
private Timer timer;
private List<AnimationProperties> properies;
private Animator() {
properies = new ArrayList<>(5);
timer = new Timer(5, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
Iterator<AnimationProperties> it = properies.iterator();
while (it.hasNext()) {
AnimationProperties ap = it.next();
if (ap.tick()) {
it.remove();
}
}
if (properies.isEmpty()) {
timer.stop();
}
}
});
}
public void add(AnimationProperties ap) {
properies.add(ap);
timer.start();
}
public void remove(AnimationProperties ap) {
properies.remove(ap);
if (properies.isEmpty()) {
timer.stop();
}
}
}
public interface AnimationProperties<T> {
public Range<T> getRange();
public T getValue();
public boolean tick();
public void setDuration(Duration duration);
public Duration getDuration();
}
public interface AnimationPropertiesListener<T> {
public void stateChanged(AnimationProperties<T> animator);
}
public 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 class AbstractAnimationProperties<T> implements AnimationProperties<T> {
private Range<T> range;
private LocalDateTime startTime;
private Duration duration = Duration.ofSeconds(5);
private T value;
private AnimationPropertiesListener<T> listener;
public AbstractAnimationProperties(Range<T> range, AnimationPropertiesListener<T> listener) {
this.range = range;
this.value = range.getFrom();
this.listener = listener;
}
public void setDuration(Duration duration) {
this.duration = duration;
}
public Duration getDuration() {
return duration;
}
public Range<T> getRange() {
return range;
}
@Override
public T getValue() {
return value;
}
@Override
public boolean tick() {
if (startTime == null) {
startTime = LocalDateTime.now();
}
Duration duration = getDuration();
Duration runningTime = Duration.between(startTime, LocalDateTime.now());
Duration timeRemaining = duration.minus(runningTime);
if (timeRemaining.isNegative()) {
runningTime = duration;
}
double progress = (runningTime.toMillis() / (double) duration.toMillis());
value = calculateValue(progress);
listener.stateChanged(this);
return progress >= 1.0;
}
public abstract T calculateValue(double progress);
}
public class IntRange extends Range<Integer> {
public IntRange(Integer from, Integer to) {
super(from, to);
}
public Integer getDistance() {
return getTo() - getFrom();
}
}
public class IntAnimationProperties extends AbstractAnimationProperties<Integer> {
public IntAnimationProperties(IntRange animationRange, IntRange maxRange, Duration duration, AnimationPropertiesListener<Integer> listener) {
super(animationRange, listener);
int maxDistance = maxRange.getDistance();
int aniDistance = animationRange.getDistance();
double progress = Math.min(100, Math.max(0, Math.abs(aniDistance/ (double)maxDistance)));
Duration remainingDuration = Duration.ofMillis((long)(duration.toMillis() * progress));
setDuration(remainingDuration);
}
@Override
public Integer calculateValue(double progress) {
IntRange range = (IntRange)getRange();
int distance = range.getDistance();
int value = (int) Math.round((double) distance * progress);
value += range.getFrom();
return value;
}
}
}
但这真的很慢☹️
好的,有两个地方可以更改持续时间。 AbstractAnimationProperties
默认Duration
为5
秒,Slider
默认Duration
为5
秒。在您的情况下,更改Slider
sDuration
可能是您想要开始的地方
哦,等等,你想要“地役权”(慢进/慢出)吗?好吧,去看看How can I implement easing functions with a thread然后去看看我之前链接的动画框架,因为他们已经这样做了
现在,如果您真的打算手动执行此操作 - 您可以查看这个 gist,它是基于“时间线”的实现,支持地役权
推荐阅读
- c++ - 在 C++ 中使用 WASAPI 录制时如何访问音频波形缓冲区?
- jquery - 如何使用 if else 语句 ajax
- ruby-on-rails - Gitlab smtp 测试失败
- elasticsearch - 插件 [ingest-geoip] 是为 Elasticsearch 6.2.4 版构建的,但 6.5.0 版正在运行
- html - 带图像的引导输入组
- javascript - 计算两个选项上的活动课程
- python - 累积梯度
- android - RecyclerView如何平滑到偏移高度的指定位置?
- hadoop - 如何在 Hadoop 中解压缩拆分的 zip 文件
- django - 访问 django celery 导致视图