使用 Swing Timer 每秒觸發重繪,透過三角函數計算時、分、秒三支指針角度,繪製圓形表盤、刻度數字與指針。展示 Graphics.fillPolygon 繪製楔形指針與雙重繪製消除閃爍技巧。
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
public class Clock extends JFrame implements ActionListener {
Timer timer;
int x, y, oldX, oldY, r, x0, y0, w, h, ang;
int sDeg, mDeg, hDeg, oldM, oldH;
final double RAD = Math.PI / 180.0;
public static void main(String[] args) { new Clock(); }
public Clock() {
super("Clock");
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
setBackground(new Color(0, 0, 192));
setSize(300, 300);
setVisible(true);
// 每秒觸發 repaint
new Timer(1000, e -> repaint()).start();
}
public void paint(Graphics g) {
Insets in = getInsets();
int L0 = in.left/2, T0 = in.top/2;
h = getSize().height;
// 表盤圓圈
g.setColor(Color.WHITE);
g.drawOval(L0+30, T0+30, h-60, h-60);
g.drawOval(L0+32, T0+32, h-64, h-64);
r = h/2 - 30;
x0 = 30 + r - 5 + L0;
y0 = 30 + r - 5 - T0;
// 刻度數字 1~12
for (int i = 1, ang = 60; i <= 12; i++, ang -= 30) {
int xx = (int)((r+10)*Math.cos(RAD*ang)) + x0;
int yy = (int)((r+10)*Math.sin(RAD*ang)) + y0;
g.drawString(String.valueOf(i), xx, h - yy);
}
x0 = 30 + r + L0; y0 = 30 + r + T0;
// 取得時間
Calendar now = Calendar.getInstance();
int hh = now.get(Calendar.HOUR_OF_DAY);
int mm = now.get(Calendar.MINUTE);
int ss = now.get(Calendar.SECOND);
// 顯示數字時間
g.setColor(Color.PINK); g.fillRect(L0, T0, 60, 28);
g.setColor(Color.BLUE);
g.drawString(String.format("%02d:%02d:%02d", hh, mm, ss), L0, T0+25);
// 計算指針角度 (12點鐘方向為 90 度)
sDeg = 90 - ss * 6;
mDeg = 90 - mm * 6;
hDeg = 90 - hh * 30 - mm / 2;
// 擦除舊秒針
if (oldX > 0) {
g.setColor(getBackground());
g.drawLine(x0, y0, oldX, h - oldY);
} else { oldM = mDeg; oldH = hDeg; }
// 繪製新秒針
g.setColor(Color.YELLOW);
x = (int)((r-8)*Math.cos(RAD*sDeg)) + x0;
y = (int)((r-8)*Math.sin(RAD*sDeg)) + y0 - 2*T0;
g.drawLine(x0, y0, x, h - y);
oldX = x; oldY = y;
// 擦除並重繪分針、時針 (楔形)
if (mDeg != oldM) { drawHand(g, oldM, (int)(r*0.7), getBackground()); oldM = mDeg; }
if (hDeg != oldH) { drawHand(g, oldH, (int)(r*0.5), getBackground()); oldH = hDeg; }
drawHand(g, mDeg, (int)(r*0.7), Color.GREEN);
drawHand(g, hDeg, (int)(r*0.5), Color.RED);
}
void drawHand(Graphics g, int deg, int len, Color c) {
int[] xp = new int[4], yp = new int[4];
xp[0] = x0; yp[0] = y0;
xp[1] = (int)((len-10)*Math.cos(RAD*(deg-4))) + x0;
yp[1] = h - ((int)((len-10)*Math.sin(RAD*(deg-4))) + y0);
xp[2] = (int)(len*Math.cos(RAD*deg)) + x0;
yp[2] = h - ((int)(len*Math.sin(RAD*deg)) + y0);
xp[3] = (int)((len-10)*Math.cos(RAD*(deg+4))) + x0;
yp[3] = h - ((int)((len-10)*Math.sin(RAD*(deg+4))) + y0);
g.setColor(c);
g.fillPolygon(xp, yp, 4);
}
public void actionPerformed(ActionEvent e) { timer.restart(); }
}
優化重點:
Timer 取代 Thread.sleep 避免阻塞 EDT。