利用心臟線參數方程 x = 16 sin³θ, y = 13 cosθ - 5 cos2θ - 2 cos3θ - cos4θ,配合 Swing 定時重繪、雙緩衝、類路徑資源加載,實現一朵花瓣飛舞組成愛心的動畫效果。
public class DrawLove extends JPanel {
private Image petalImage;
private double theta = 0;
private final double scale = 15; // 縮放係數
private final int centerX = 580, centerY = 240; // 畫布中心
public DrawLove(String imagePath) {
try {
// 類路徑加載圖片(打包 JAR 後依然有效)
petalImage = ImageIO.read(getClass().getClassLoader().getResourceAsStream(imagePath));
} catch (IOException e) { e.printStackTrace(); }
setLayout(null);
}
public void addTheta(double delta) { this.theta += delta; }
public double getTheta() { return theta; }
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (petalImage == null) return;
Graphics2D g2 = (Graphics2D) g;
// 參數方程計算座標
double x = centerX - scale * (16 * Math.pow(Math.sin(theta), 3));
double y = centerY - scale * (13 * Math.cos(theta) - 5 * Math.cos(2*theta) - 2 * Math.cos(3*theta) - Math.cos(4*theta));
g2.drawImage(petalImage, (int)x, (int)y, null);
}
}
public class LoveAnimation implements Runnable {
private final DrawLove love;
private final JFrame frame;
private final int frameDelay = 30; // ms
private final double thetaStep = 0.03; // 每幀角度增量
public LoveAnimation(DrawLove love, JFrame frame) {
this.love = love; this.frame = frame;
}
@Override
public void run() {
while (love.getTheta() < 2 * Math.PI) {
love.addTheta(thetaStep);
love.repaint(); // 觸發 paintComponent
love.revalidate();
try { Thread.sleep(frameDelay); } catch (InterruptedException ignored) {}
}
// 動畫結束清理
SwingUtilities.invokeLater(() -> frame.getContentPane().remove(love));
}
}
public class Main {
public static void main(String[] args) {
JFrame frame = new JFrame("Love Animation");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(800, 600);
frame.setLocationRelativeTo(null);
DrawLove love = new DrawLove("petal.png"); // src/resources/petal.png
frame.getContentPane().add(love);
frame.setVisible(true);
new Thread(new LoveAnimation(love, frame)).start();
}
}
theta 從 0 到 2π 掃描一周。JPanel 默認開啟雙緩衝,repaint() 異步合併繪圖請求,無閃爍。getClass().getClassLoader().getResourceAsStream() 支持 IDE 運行、JAR 打包、WebStart 等多種部署方式,避免絕對路徑失效。theta、調用 repaint(),實際繪圖在 EDT (Event Dispatch Thread) 執行,符合 Swing 單線程規則。