使用 Swing JFrame 繪製一個會走動的類比時鐘,是學習 Java 2D 繪圖、執行緒動畫迴圈與三角函數應用的經典練習。本文提供完整可執行程式碼,涵蓋 **自訂 JPanel 繪製表盤/指針、Calendar 取得系統時間、Thread.sleep 動畫迴圈、雙緩衝防閃爍** 等核心技術。
import javax.swing.*;
import java.awt.*;
import java.util.Calendar;
import java.util.GregorianCalendar;
public class AnalogClock extends JFrame {
public AnalogClock() {
setTitle("類比時鐘");
setDefaultCloseOperation(EXIT_ON_CLOSE);
setSize(320, 360);
setLocationRelativeTo(null);
setResizable(false);
add(new ClockPanel());
setVisible(true);
}
public static void main(String[] args) {
// 確保在 EDT 建立 UI
SwingUtilities.invokeLater(AnalogClock::new);
}
// 內部類別:時鐘面板
static class ClockPanel extends JPanel {
private int hour, minute, second;
public ClockPanel() {
// 雙緩衝預設開啟,減少閃爍
new Thread(this::animationLoop, "Clock-Animation").start();
}
private void animationLoop() {
while (true) {
updateTime();
repaint(); // 觸發 paintComponent
try { Thread.sleep(1000); } catch (InterruptedException ignored) {}
}
}
private void updateTime() {
Calendar cal = new GregorianCalendar();
hour = cal.get(Calendar.HOUR_OF_DAY); // 24 小時制
minute = cal.get(Calendar.MINUTE);
second = cal.get(Calendar.SECOND);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
int w = getWidth(), h = getHeight();
int cx = w / 2, cy = h / 2;
int radius = Math.min(w, h) / 2 - 10;
// 1. 表盤外圈
g2.setColor(Color.BLACK);
g2.drawOval(cx - radius, cy - radius, 2 * radius, 2 * radius);
// 2. 刻度數字 12/3/6/9
g2.setFont(new Font("SansSerif", Font.BOLD, 14));
g2.drawString("12", cx - 6, cy - radius + 18);
g2.drawString("3", cx + radius - 14, cy + 5);
g2.drawString("6", cx - 4, cy + radius - 8);
g2.drawString("9", cx - radius + 6, cy + 5);
// 3. 秒針(紅、最長)
drawHand(g2, Color.RED, radius * 0.85f, second, 60);
// 4. 分針(藍、中等)
drawHand(g2, Color.BLUE, radius * 0.65f, minute, 60);
// 5. 時針(綠、最短,需疊加分鐘偏移)
float hourAngle = (hour % 12 + minute / 60f) * 30f; // 30 度/時
drawHand(g2, Color.GREEN, radius * 0.5f, hourAngle, 360);
// 6. 數位時間顯示
g2.setColor(Color.BLACK);
g2.setFont(new Font("Monospaced", Font.BOLD, 16));
String digital = String.format("%02d:%02d:%02d", hour % 12 == 0 ? 12 : hour % 12, minute, second);
g2.drawString(digital, cx - 35, cy - radius - 10);
}
/** 繪製單根指針
* @param g2 繪圖上下文
* @param color 顏色
* @param length 長度
* @param value 當前值(秒/分/時角度)
* @param maxValue 最大值(60 或 360) */
private void drawHand(Graphics2D g2, Color color, float length, float value, float maxValue) {
double rad = Math.toRadians(90 - (value * 360 / maxValue)); // 12 點鐘方向為 0 度,順時針
int x = cx + (int) (length * Math.cos(rad));
int y = cy - (int) (length * Math.sin(rad));
g2.setColor(color);
g2.setStroke(new BasicStroke(2f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
g2.drawLine(cx, cy, x, y);
}
}
}
| 技術 | 說明 |
|---|---|
| 自訂繪圖 | 繼承 JPanel 覆寫 paintComponent(Graphics),所有繪圖邏輯集中於此 |
| 雙緩衝 | Swing 預設開啟雙緩衝,無需手動實作,有效消除閃爍 |
| 抗鋸齒 | g2.setRenderingHint(KEY_ANTIALIASING, VALUE_ANTIALIAS_ON) 讓指針圓滑 |
| 三角函數換算 | 角度 → 弧度 → 向量:x = cx + r*cos(θ), y = cy - r*sin(θ)(Y 軸向下) |
| 時針平滑移動 | hour % 12 + minute/60.0 讓時針隨分鐘連續轉動,而非跳躍 |
| 動畫迴圈 | 獨立執行緒 Thread.sleep(1000) + repaint(),非阻塞 EDT |
| EDT 安全 | 主程式用 SwingUtilities.invokeLater 啟動 UI |
setUndecorated(true))。Timer 替代 Thread.sleep,簡化執行緒管理。此類比時鐘練習完整串聯了 Swing 繪圖、幾何數學與多執行緒動畫,是 GUI 程式設計的優質入門專案。