動畫本質是快速連續繪製一系列靜態圖像(幀)。前文已構建動畫循環線程,此處聚焦 paint(Graphics g) 方法中的繪圖邏輯。
public void paint(Graphics g) {
g.setColor(Color.black);
g.drawString("Frame " + frame, 0, 30);
}
每幀調用 repaint() 觸發 AWT 回調 paint(),在屏幕指定位置繪製幀號。
對每個 x 座標畫一條垂直短線,隨幀號 frame 變化產生波動效果:
public void paint(Graphics g) {
Dimension d = size();
int h = d.height / 2;
for (int x = 0; x < d.width; x++) {
int y1 = (int)((1.0 + Math.sin((x - frame) * 0.05)) * h);
int y2 = (int)((1.0 + Math.sin((x + frame) * 0.05)) * h);
g.drawLine(x, y1, x, y2); // 注意:原文 g.DrawLine 大寫錯誤,Java 區分大小寫
}
}
問題: 直接在 paint() 中大量計算並繪圖,會導致明顯閃爍。
paint() 前自動清除背景(調用 update()),清除到繪製完成的間隙,用戶看到空白背景 → 閃爍。PC 平台閃爍明顯,X Window 因圖形緩存較輕微。
AWT 重繪流程:repaint() → update() → paint()。默認 update() 清屏再調 paint()。
重寫 update(),將繪圖邏輯移入,並僅擦除上一幀痕跡,不再全屏清屏:
public void paint(Graphics g) {
update(g); // paint 直接委託 update
}
public void update(Graphics g) {
Color bg = getBackground();
Dimension d = size();
int h = d.height / 2;
for (int x = 0; x < d.width; x++) {
int y1 = (int)((1.0 + Math.sin((x - frame) * 0.05)) * h);
int y2 = (int)((1.0 + Math.sin((x + frame) * 0.05)) * h);
if (y1 > y2) { int t = y1; y1 = y2; y2 = t; } // 確保 y1 <= y2
// 1. 擦除上一幀該列像素(畫背景色線)
g.setColor(bg);
g.drawLine(x, 0, x, y1);
g.drawLine(x, y2, x, d.height);
// 2. 繪製當前幀該列像素
g.setColor(Color.black);
g.drawLine(x, y1, x, y2);
}
}
原理: 背景不再自動清除,手動僅擦除會變化的垂直線區域,實現「增量更新」,徹底消除閃爍。
雖本文未展開,但業界標準解法是:
Image(或 BufferedImage)作為後台緩衝區。drawImage() 到屏幕 Graphics。Java Swing 組件默認開啟雙緩衝(JComponent.setDoubleBuffered(true)),AWT 需手動實現。
repaint() 觸發 paint()。update() 實現增量繪圖是 AWT 時代經典技巧;現代開發請直接使用 Swing 雙緩衝機制。Math.sin 非 math.sin,g.drawLine 非 g.DrawLine,Java 嚴格區分大小寫。