Java 動畫透過 Graphics.drawImage() 繪製幀畫面,配合執行緒週期性切換圖像產生動態效果。關鍵要素:
paint(Graphics g) 或 update(Graphics g)Thread.sleep())update() 預設行為:先以背景色清空整個區域 → 呼叫 paint(),造成背景與前景交替顯示// 預設 update() 實作
public void update(Graphics g) {
g.setColor(getBackground());
g.fillRect(0, 0, width, height); // 清空整個畫面
g.setColor(getForeground());
paint(g);
}
// 重寫:僅繪製必要區域,不清空背景
public void update(Graphics g) {
paint(g); // 直接繪製,由 paint() 內部處理局部清除
}
Image ( createImage(width, height) )Graphics 繪製完整幀drawImage() 到螢幕Image offscreen = createImage(width, height);
Graphics offg = offscreen.getGraphics();
// 在 offg 上繪製完整幀...
g.drawImage(offscreen, 0, 0, this); // 一次性上螢幕
優點:畫面平滑、無閃爍;缺點:佔用額外記憶體 (大圖像明顯)。使用雙緩衝時仍建議重寫 update()。
import java.util.*;
import java.awt.*;
import java.applet.*;
import java.text.*;
public class AnimatorDemo extends Applet implements Runnable {
Thread timer;
int lastxs, lastys, lastxm, lastym, lastxh, lastyh;
SimpleDateFormat formatter;
String lastdate;
Font clockFaceFont;
Date currentDate;
Color handColor, numberColor;
public void init() {
lastxs = lastys = lastxm = lastym = lastxh = lastyh = 0;
formatter = new SimpleDateFormat("yyyy EEE MMM dd hh:mm:ss");
currentDate = new Date();
lastdate = formatter.format(currentDate);
clockFaceFont = new Font("Serif", Font.PLAIN, 14);
handColor = Color.blue;
numberColor = Color.darkGray;
// 參數化顏色設定
try { setBackground(new Color(Integer.parseInt(getParameter("bgcolor"), 16))); } catch (Exception e) {}
try { handColor = new Color(Integer.parseInt(getParameter("fgcolor1"), 16)); } catch (Exception e) {}
try { numberColor = new Color(Integer.parseInt(getParameter("fgcolor2"), 16)); } catch (Exception e) {}
resize(300, 300);
}
// Bresenham 畫圓演算法
public void plotpoints(int x0, int y0, int x, int y, Graphics g) {
g.drawLine(x0+x, y0+y, x0+x, y0+y);
g.drawLine(x0+y, y0+x, x0+y, y0+x);
g.drawLine(x0+y, y0-x, x0+y, y0-x);
g.drawLine(x0+x, y0-y, x0+x, y0-y);
g.drawLine(x0-x, y0-y, x0-x, y0-y);
g.drawLine(x0-y, y0-x, x0-y, y0-x);
g.drawLine(x0-y, y0+x, x0-y, y0+x);
g.drawLine(x0-x, y0+y, x0-x, y0+y);
}
public void circle(int x0, int y0, int r, Graphics g) {
int x=0, y=r; float d=5/4-r;
plotpoints(x0,y0,x,y,g);
while (y > x) {
if (d < 0) { d += 2*x+3; x++; }
else { d += 2*(x-y)+5; x++; y--; }
plotpoints(x0,y0,x,y,g);
}
}
public void paint(Graphics g) {
int xh, yh, xm, ym, xs, ys, s=0, m=10, h=10;
int xcenter=80, ycenter=55;
String today;
currentDate = new Date();
formatter = new SimpleDateFormat("s", Locale.getDefault());
try { s = Integer.parseInt(formatter.format(currentDate)); } catch (NumberFormatException n) { s=0; }
formatter.applyPattern("m");
try { m = Integer.parseInt(formatter.format(currentDate)); } catch (NumberFormatException n) { m=10; }
formatter.applyPattern("h");
try { h = Integer.parseInt(formatter.format(currentDate)); } catch (NumberFormatException n) { h=10; }
formatter.applyPattern("EEE MMM dd HH:mm:ss yyyy");
today = formatter.format(currentDate);
// 計算指針座標 (極座標轉直角座標)
xs = (int)(Math.cos(s * 3.14f/30 - 3.14f/2) * 45 + xcenter);
ys = (int)(Math.sin(s * 3.14f/30 - 3.14f/2) * 45 + ycenter);
xm = (int)(Math.cos(m * 3.14f/30 - 3.14f/2) * 40 + xcenter);
ym = (int)(Math.sin(m * 3.14f/30 - 3.14f/2) * 40 + ycenter);
xh = (int)(Math.cos((h*30 + m/2) * 3.14f/180 - 3.14f/2) * 30 + xcenter);
yh = (int)(Math.sin((h*30 + m/2) * 3.14f/180 - 3.14f/2) * 30 + ycenter);
// 繪製表盤
g.setFont(clockFaceFont);
g.setColor(handColor);
circle(xcenter, ycenter, 50, g);
g.setColor(numberColor);
g.drawString("9", xcenter-45, ycenter+3);
g.drawString("3", xcenter+40, ycenter+3);
g.drawString("12", xcenter-5, ycenter-37);
g.drawString("6", xcenter-3, ycenter+45);
// 增量更新:以背景色抹除舊指針,再繪製新指針
g.setColor(getBackground());
if (xs != lastxs || ys != lastys) {
g.drawLine(xcenter, ycenter, lastxs, lastys);
g.drawString(lastdate, 5, 125);
}
if (xm != lastxm || ym != lastym) {
g.drawLine(xcenter, ycenter-1, lastxm, lastym);
g.drawLine(xcenter-1, ycenter, lastxm, lastym);
}
if (xh != lastxh || yh != lastyh) {
g.drawLine(xcenter, ycenter-1, lastxh, lastyh);
g.drawLine(xcenter-1, ycenter, lastxh, lastyh);
}
// 繪製新指針與時間字串
g.setColor(numberColor);
g.drawString("", 5, 125); // 清除舊時間位置
g.drawString(today, 5, 125);
g.drawLine(xcenter, ycenter, xs, ys); // 秒針
g.setColor(handColor);
g.drawLine(xcenter, ycenter-1, xm, ym); // 分針
g.drawLine(xcenter-1, ycenter, xm, ym);
g.drawLine(xcenter, ycenter-1, xh, yh); // 時針
g.drawLine(xcenter-1, ycenter, xh, yh);
// 更新上一幀座標
lastxs=xs; lastys=ys;
lastxm=xm; lastym=ym;
lastxh=xh; lastyh=yh;
lastdate = today;
currentDate = null;
}
// 執行緒生命週期管理
public void start() { timer = new Thread(this); timer.start(); }
public void stop() { timer = null; }
public void run() {
Thread me = Thread.currentThread();
while (timer == me) {
try { Thread.sleep(1000); } catch (InterruptedException e) {}
repaint(); // 觸發 paint()
}
}
// 關鍵:重寫 update 消除閃爍
public void update(Graphics g) { paint(g); }
}
<HTML>
<HEAD><TITLE>類比時鐘 Applet</TITLE></HEAD>
<BODY>
<applet codebase="." code="AnimatorDemo.class" width=200 height=150></applet>
</BODY></HTML>
| 技術點 | 實作要點 |
|---|---|
| 動畫執行緒 | 實作 Runnable,start() 啟動,stop() 清理,run() 循環 sleep() + repaint() |
| 幀率控制 | Thread.sleep(毫秒),範例為 1000ms (1 FPS),遊戲通常 16ms (60 FPS) |
| 增量繪圖 | 記錄上一幀座標,僅重繪變化區域 (背景色抹除舊、前景色繪新) |
| 雙緩衝替代 | 若畫面複雜,改用 createImage() 離屏繪圖再 drawImage() |
| 參數化配置 | getParameter() 從 HTML 讀取顏色、尺寸等設定 |
| 資源釋放 | stop() 中清理執行緒,destroy() 釋放非記憶體資源 |
Applet 已被瀏覽器淘汰 (Java 9 標記棄用,Java 11 移除)。現代 Web 動畫請使用: