Некоторые основные понятия ...
- Пульсирующий эффект должен двигаться в двух направлениях, он должен постепенно исчезать и исчезать
- Для того, чтобы узнать, сколько "эффекта"следует применять, две вещи должны быть известны.Во-первых, сколько времени занимает полный эффект для цикла (от полностью непрозрачного до полностью прозрачного и обратно) и как далеко проходит цикл анимации.
Это не простая вещь для управления, естьмного «информации о состоянии», которая должна управляться и поддерживаться, и обычно это делается отдельно от других эффектов или сущностей.
На мой взгляд, самое простое решение состоит в том, чтобы разработать какую-то «временную линию»который управляет ключевыми точками (ключевыми кадрами) вдоль временной шкалы, вычисляет расстояние между каждой точкой и значением, которое она представляет.
Сделайте шаг назад на секунду.Мы знаем, что при:
- 0% мы хотим быть полностью непрозрачными
- 50% мы хотим быть полностью прозрачными
- 100% мы хотим быть полностью непрозрачными
Вышесказанное учитывает, что мы хотим «автоматически повернуть» анимацию.
Причина работы с процентами состоит в том, что она позволяет нам определять временную шкалу любого заданногопродолжительность и сроки позаботятся об остальном.Там, где это возможно, всегда работайте с такими нормированными значениями, как это, это все упрощает.
TimeLine
Ниже приводится довольно простая концепция «временной шкалы».Он имеет Duration
, время, в течение которого воспроизводится временная шкала, ключевые кадры, которые предоставляют ключевые значения по продолжительности временной шкалы и средства для вычисления определенного значения в определенной точке в течение срока службы временной шкалы.
Эта реализация также предоставляет возможность автоматического воспроизведения.То есть, если временная шкала воспроизводится «более», она указана Duration
, а не останавливается, она автоматически сбрасывается и учитывает количество «более», как часть следующего цикла (аккуратно)
public class TimeLine {
private Map<Float, KeyFrame> mapEvents;
private Duration duration;
private LocalDateTime startedAt;
public TimeLine(Duration duration) {
mapEvents = new TreeMap<>();
this.duration = duration;
}
public void start() {
startedAt = LocalDateTime.now();
}
public boolean isRunning() {
return startedAt != null;
}
public float getValue() {
if (startedAt == null) {
return getValueAt(0.0f);
}
Duration runningTime = Duration.between(startedAt, LocalDateTime.now());
if (runningTime.compareTo(duration) > 0) {
runningTime = runningTime.minus(duration);
startedAt = LocalDateTime.now().minus(runningTime);
}
long total = duration.toMillis();
long remaining = duration.minus(runningTime).toMillis();
float progress = remaining / (float) total;
return getValueAt(progress);
}
public void add(float progress, float value) {
mapEvents.put(progress, new KeyFrame(progress, value));
}
public float getValueAt(float progress) {
if (progress < 0) {
progress = 0;
} else if (progress > 1) {
progress = 1;
}
KeyFrame[] keyFrames = getKeyFramesBetween(progress);
float max = keyFrames[1].progress - keyFrames[0].progress;
float value = progress - keyFrames[0].progress;
float weight = value / max;
float blend = blend(keyFrames[0].getValue(), keyFrames[1].getValue(), 1f - weight);
return blend;
}
public KeyFrame[] getKeyFramesBetween(float progress) {
KeyFrame[] frames = new KeyFrame[2];
int startAt = 0;
Float[] keyFrames = mapEvents.keySet().toArray(new Float[mapEvents.size()]);
while (startAt < keyFrames.length && keyFrames[startAt] <= progress) {
startAt++;
}
if (startAt >= keyFrames.length) {
startAt = keyFrames.length - 1;
}
frames[0] = mapEvents.get(keyFrames[startAt - 1]);
frames[1] = mapEvents.get(keyFrames[startAt]);
return frames;
}
protected float blend(float start, float end, float ratio) {
float ir = (float) 1.0 - ratio;
return (float) (start * ratio + end * ir);
}
public class KeyFrame {
private float progress;
private float value;
public KeyFrame(float progress, float value) {
this.progress = progress;
this.value = value;
}
public float getProgress() {
return progress;
}
public float getValue() {
return value;
}
@Override
public String toString() {
return "KeyFrame progress = " + getProgress() + "; value = " + getValue();
}
}
}
Настройка временной шкалы довольно проста ...
timeLine = new TimeLine(Duration.ofSeconds(5));
timeLine.add(0.0f, 1.0f);
timeLine.add(0.5f, 0.0f);
timeLine.add(1.0f, 1.0f);
Мы даем указанное Duration
и настраиваем значения ключевых кадров.После этого нам просто нужно «запустить» его и получить текущий value
из TimeLine
в зависимости от того, как долго он играет.
Это может показаться большой работой для того, что кажется простымпроблема, но помните, что это и динамично, и многократно используется.
Это динамично, так как вы можете предоставить любой Duration
, который вы хотите, таким образом, изменяя скорость, он будет «просто работать», и повторноего можно использовать, так как вы можете создать несколько экземпляров для нескольких объектов, и он будет управляться независимо.
Пример ...
В следующем примере просто используется Swing Timer
в качестве "основной цикл »для анимации.В каждом цикле он запрашивает TimeLine
значение «current», которое просто действует как значение alpha
для «пульсирующего» эффекта.
Сам класс TimeLine
развязан настолько, что онне имеет значения, «как» вы устанавливаете свой «основной цикл», вы просто запускаете его и извлекаете из него «текущее» значение, когда это возможно ...
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.TreeMap;
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 {
private TimeLine timeLine;
private float alpha = 0;
public TestPane() {
timeLine = new TimeLine(Duration.ofSeconds(5));
timeLine.add(0.0f, 1.0f);
timeLine.add(0.5f, 0.0f);
timeLine.add(1.0f, 1.0f);
Timer timer = new Timer(5, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (!timeLine.isRunning()) {
timeLine.start();
}
alpha = timeLine.getValue();
repaint();
}
});
timer.start();
}
@Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setComposite(AlphaComposite.SrcOver.derive(alpha));
g2d.setColor(Color.RED);
g2d.fill(new Rectangle(45, 45, 110, 110));
g2d.dispose();
g2d = (Graphics2D) g.create();
g2d.setColor(getBackground());
g2d.fill(new Rectangle(50, 50, 100, 100));
g2d.setColor(Color.BLACK);
g2d.draw(new Rectangle(50, 50, 100, 100));
g2d.dispose();
}
}
}
Я бы связал TimeLine
как часть эффекта для указанной сущности.Это связало бы TimeLine
с конкретной сущностью, а это означает, что многие сущности могут иметь свои собственные TimeLine
s, вычисляющие проверку различных значений и эффектов
«Есть ли более простое решение?»
Это субъективный вопрос.«Может быть» может быть «более простой» подход, который будет выполнять ту же работу, но который не будет столь же масштабируемым или повторно используемым, как этот вид подхода.
Анимация - сложный предмет, пытающийся сделатьон работает в комплексном решении, выполняя несколько различных эффектов, и сущности только усугубляют проблему
Я играл с идеей сделать универсальный TimeLine
, чтобы его можно было использовать для создания истинности различных значений на основена желаемый результат, что делает его гораздо более гибким и многократно используемым решением.
Смешивание цветов ....
Я не знаю, является ли это требованием, но если у вас есть ряд цветов, которые вы хотите смешать, TimeLine
также поможет вам здесь (вам не нужна продолжительность так много).Вы можете настроить ряд цветов (выступая в качестве ключевых кадров) и рассчитать, какой цвет использовать, основываясь на прогрессе анимации.
Смешивание цветов несколько проблематично, я потратил много времени, пытаясьнайти достойный алгоритм, который работает для меня, который демонстрируется в алгоритм затухания цвета? и Java: плавный переход цвета