Начните с идеи, что анимация - это иллюзия изменений во времени. Таким образом, вам нужно иметь возможность переходить из одного состояния в другое в зависимости от количества доступного времени и текущего используемого времени.
Итак, нам действительно нужен какой-то способ динамического изменения размера фигуры. на количество времени, которое было сыграно.
Итак, первое, что нужно сделать, это взять код рисования и создать из него Shape
. Это обеспечивает, как с базовой линией.
public class HeartShape extends Path2D.Double {
public HeartShape() {
moveTo(0.0, -150.0);
curveTo(-200.0, -25.0, -200.0, 225.0, 0, 100.0);
moveTo(0.0, -150.0);
curveTo(200.0, -25.0, 205.0, 235.0, 0, 100.0);
}
}
Это станет нашим "источником", это будет иметь смысл позже ?
Далее нам нужно настроить состояние воспроизведения, это включает «время начала» или «время привязки» и желаемая продолжительность воспроизведения.
Как только мы это получим, мы можем использовать Timer
, чтобы вычислить количество сыгранного времени и сгенерировать из него значение «прогресса».
В качестве примечания, подобные анимации на основе времени более гибкие и обычно выглядят лучше, чем линейные прогрессии (IMHO)
Затем мы можем использовать значение прогрессии в качестве основы для изменения размера форма.
Для этого мы используем Shape
API ...
Shape shape = heartShape.createTransformedShape(AffineTransform.getScaleInstance(scale, scale));
Все это может выглядеть примерно так ...
class MyJPanel extends JPanel {
private static final long serialVersionUID = 1L;
private HeartShape heartShape = new HeartShape();
private Instant anchorPoint;
private Duration playDuration = Duration.ofSeconds(5);
private double scale = 1;
public MyJPanel() {
super();
setPreferredSize(new Dimension(800, 600));
setBackground(new Color(200, 200, 255));
Timer timer = new Timer(5, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (anchorPoint == null) {
anchorPoint = Instant.now();
}
Duration playTime = Duration.between(anchorPoint, Instant.now());
double progress = (double)playTime.toMillis() / playDuration.toMillis();
if (progress >= 1) {
anchorPoint = null;
progress = 1;
}
scale = progress;
repaint();
}
});
timer.start();
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
AffineTransform gat = new AffineTransform();
gat.scale(1.0, -1.0);
gat.translate(getWidth() / 2.0, -getHeight() / 2.0);
g2d.transform(gat);
Shape shape = heartShape.createTransformedShape(AffineTransform.getScaleInstance(scale, scale));
g2d.setPaint(Color.PINK);
g2d.fill(shape);
g2d.setStroke(new BasicStroke(5.0f));
g2d.setPaint(Color.BLACK);
g2d.draw(shape);
g2d.dispose();
}
}
Хорошо, но это простое движение от 0-1, нам нужно, чтобы оно «отскочило» (от начальной точки к конечной точке и обратно).
В этот момент мы вошли на территорию «ключевые кадры», но я постараюсь сделать это проще, но если вам интересно, вы можете посмотреть:
Но мы предупреждаем ed, это будет как твоя голова ? - это мое ?
Во-первых, нам нужно определить «диапазон» действительных значений. Это будет представлять диапазон, в котором будет масштабироваться фигура ...
private double lowerRange = 0.75;
private double upperRange = 1.25;
Здесь я только что выбрал несколько произвольных значений около 1.0
, вы можете поиграть с ними и посмотреть, что вам нравится.
Далее, в Timer
нам нужно изменить способ вычисления scale
на что-то более похожее ...
scale = ((upperRange - lowerRange) * progress) + lowerRange;
repaint();
Хорошо, круто, теперь у нас есть растущая фигура от 0.75
до 1.25
, но это все еще только в линейном направлении.
Хорошо, здесь ключевые кадры и временная шкала были бы действительно полезны, так как все, что мы действительно хотим, это что-то вроде «автореверс» (за исключением примененного времени), мы можем сделать что-то более похожее на ...
if (progress > 0.5) {
progress = 1.0 - progress;
}
scale = ((upperRange - lowerRange) * progress) + lowerRange;
repaint();
И, до того, у нас пульсирующее сердце ?, сладкое
Хорошо, но подождите, это немного медленно. Что ж, тогда просто измените playDuration
, возможно, вместо этого попробуйте private Duration playDuration = Duration.ofSeconds(1);
!
Посмотрите, как это просто, вам нужно всего лишь изменить одну переменную, и вы полностью изменили способ воспроизведения анимации !! Сказал, что анимация, основанная на времени, потрясла ?
Последний пример запуска ...
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Path2D;
import java.time.Duration;
import java.time.Instant;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class HelloCurves {
public HelloCurves() {
JFrame jf = new JFrame("HelloCurves");
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel jp = new MyJPanel();
jf.add(jp);
jf.pack();
jf.setResizable(false);
jf.setLocationRelativeTo(null);
jf.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(HelloCurves::new);
}
class MyJPanel extends JPanel {
private static final long serialVersionUID = 1L;
private HeartShape heartShape = new HeartShape();
private Instant anchorPoint;
private Duration playDuration = Duration.ofSeconds(1);
private double scale = 1;
private double lowerRange = 0.75;
private double upperRange = 1.25;
public MyJPanel() {
super();
setPreferredSize(new Dimension(800, 600));
setBackground(new Color(200, 200, 255));
Timer timer = new Timer(5, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (anchorPoint == null) {
anchorPoint = Instant.now();
}
Duration playTime = Duration.between(anchorPoint, Instant.now());
double progress = (double) playTime.toMillis() / playDuration.toMillis();
if (progress >= 1) {
anchorPoint = null;
progress = 1;
}
if (progress > 0.5) {
progress = 1.0 - progress;
}
scale = ((upperRange - lowerRange) * progress) + lowerRange;
repaint();
}
});
timer.start();
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
AffineTransform gat = new AffineTransform();
gat.scale(1.0, -1.0);
gat.translate(getWidth() / 2.0, -getHeight() / 2.0);
g2d.transform(gat);
Shape shape = heartShape.createTransformedShape(AffineTransform.getScaleInstance(scale, scale));
g2d.setPaint(Color.PINK);
g2d.fill(shape);
g2d.setStroke(new BasicStroke(5.0f));
g2d.setPaint(Color.BLACK);
g2d.draw(shape);
g2d.dispose();
}
}
public class HeartShape extends Path2D.Double {
public HeartShape() {
moveTo(0.0, -150.0);
curveTo(-200.0, -25.0, -200.0, 225.0, 0, 100.0);
moveTo(0.0, -150.0);
curveTo(200.0, -25.0, 205.0, 235.0, 0, 100.0);
}
}
}
Убедитесь, что вы можете объяснить это в своей лекции, потому что, если вы представили мне этот код, я я буду задавать много вопросов о том, как это работает и почему вы выбираете этот путь 1080