This is a very basic example of time period based animation
It will animate a Animatable
object over a 5 second period. You could simply use a List
and update/paint multiple objects simultaneously if you wanted to get fancy.
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.AffineTransform;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class AnimationTest {
public static void main(String[] args) {
new AnimationTest();
}
public AnimationTest() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public interface Animatable {
public void update(double progress);
public void draw(Graphics2D g2d);
}
public static class FlyingSquiral implements Animatable {
private final Point startPoint;
private final Point targetPoint;
private final double startAngel;
private final double targetAngel;
private Point location;
private double angle;
public FlyingSquiral(Point startPoint, Point targetPoint, double startAngel, double targetAngel) {
this.startPoint = startPoint;
this.targetPoint = targetPoint;
this.startAngel = startAngel;
this.targetAngel = targetAngel;
location = new Point(startPoint);
angle = startAngel;
}
@Override
public void update(double progress) {
location.x = (int)Math.round(startPoint.x + ((targetPoint.x - startPoint.x) * progress));
location.y = (int)Math.round(startPoint.y + ((targetPoint.y - startPoint.y) * progress));
angle = startAngel + ((targetAngel - startAngel) * progress);
}
@Override
public void draw(Graphics2D g2d) {
Graphics2D clone = (Graphics2D) g2d.create();
clone.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
clone.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
clone.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
clone.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
clone.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
clone.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
clone.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
clone.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
AffineTransform at = new AffineTransform();
at.translate(location.x, location.y);
at.rotate(Math.toRadians(angle), 25, 25);
clone.setTransform(at);
clone.draw(new Rectangle(0, 0, 50, 50));
clone.dispose();
}
}
public static class TestPane extends JPanel {
public static final long DURATION = 5000;
private long startTime;
private boolean started = false;
private FlyingSquiral squiral;
public TestPane() {
squiral = new FlyingSquiral(
new Point(0, 0),
new Point(150, 150),
0d, 360d);
Timer timer = new Timer(40, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (!started) {
startTime = System.currentTimeMillis();
started = true;
}
long time = System.currentTimeMillis();
long duration = time - startTime;
if (duration > DURATION) {
duration = DURATION;
((Timer)e.getSource()).stop();
}
double progress = (double)duration / (double)DURATION;
squiral.update(progress);
repaint();
}
});
timer.start();
}
@Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
squiral.draw(g2d);
g2d.dispose();
}
}
}
Equally, you could use a constraint based animation, where by the object keeps moving until it meets it's required constraints (angel/position). Each has pros and cons.
I prefer a time period based approach as it allows me to apply different transformations without needing to care about pre-calculating the delta. Try this, change the target angel from 360
to 720
and run it again.
I also prefer to use an animation library, as they add additional features, like interpolation, allowing to change the speed of the animation at certain points in time without changing the duration, this would allow you to do things like slow in, slow out (ramp up/out) effects, making the animation more appealing.
Take a look at...
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…