早期 Java Applet 通過繼承 java.applet.Applet 並實現 Runnable 介面,在獨立線程中驅動動畫幀更新,是許多開發者接觸多線程圖形編程的啟蒙。雖然 Applet 技術已被淘汰(JDK 9 標記棄用,JDK 11 移除),但其雙緩衝繪圖、幀率控制、線程與 UI 線程分離的核心思想在 Swing、JavaFX、Android Canvas、Web Canvas 等現代圖形框架中延續。本文解析經典代碼並對比現代實現。
import java.awt.*;
import java.applet.*;
public class CartoonApplet extends Applet implements Runnable {
Image[] frames = new Image[4]; // 幀圖片數組
Thread animator = null; // 動畫線程
int frameIndex = 0; // 當前幀
int frameHeight = 150; // 圖片高度
int yPos = 0; // 繪圖 Y 軸位置 (滾動效果)
@Override public void init() {
// 預加載圖片 (實際應用中應在後台線程加載避免阻塞 UI)
for (int i = 0; i < frames.length; i++) {
frames[i] = getImage(getCodeBase(), i + ".jpg");
}
}
@Override public void start() {
if (animator == null) {
animator = new Thread(this);
animator.start(); // 啟動動畫線程 → 調用 run()
}
}
@Override public void stop() {
animator = null; // 通知動畫線程退出 (需配合 run() 中判斷)
}
@Override public void run() {
while (Thread.currentThread() == animator) { // 安全退出檢查
// 1. 切換幀
frameIndex = (frameIndex + 1) % frames.length;
Image img = frames[frameIndex];
// 2. 滾動動畫邏輯:從底部滾入
yPos = frameHeight;
repaint(); // 請求重繪 → 觸發 update() → paint()
sleepQuietly(1000); // 幀停留時間
// 3. 滾動過渡動畫
while (yPos > 0) {
yPos -= 4; // 每幀移動 4 像素
repaint();
sleepQuietly(50); // 約 20 FPS
}
}
}
// 關鍵:override update() 避免默認 clearRect 導致閃爍
@Override public void update(Graphics g) {
// 不調用 super.update(g) (默認清屏)
// 直接繪製當前幀在當前 yPos 位置
if (frames[frameIndex] != null) {
g.drawImage(frames[frameIndex], 0, yPos - frameHeight, this);
}
}
// paint() 仍需實現,供系統首次繪製/暴露調用
@Override public void paint(Graphics g) {
update(g); // 統一繪圖邏輯
}
static void sleepQuietly(long ms) { try { Thread.sleep(ms); } catch (InterruptedException ignored) {} }
}
| 技術點 | 經典做法 | 原理 | 現代替代 |
|---|---|---|---|
| 動畫線程 | 實現 Runnable,new Thread(this).start() | 將耗時計算/邏輯移出事件分發線程 (EDT) | JavaFX AnimationTimer / Timeline / Swing Timer / Android Handler |
| 雙緩衝/避免閃爍 | override update() 不清屏,直接 drawImage | 默認 update() 調用 clearRect → paint(),導致閃爍 | Swing 默認雙緩衝 (RepaintManager) / JavaFX 場景圖天然無閃爍 |
| 幀率控制 | Thread.sleep(固定毫秒) | 簡單但不精確,受 GC/調度影響 | JavaFX AnimationTimer (納秒級回調) / Swing Timer (事件隊列) / requestAnimationFrame (Web) |
| 圖片加載 | getImage() 異步加載,MediaTracker 同步等待 | Applet 專用 API | ImageIO.read() / Toolkit.getImage() / JavaFX Image / Glide/Picasso (Android) |
| 生命周期 | init/start/stop/destroy | Applet 特有 | Servlet/Spring Bean 生命周期 / Activity/Fragment 生命周期 / JavaFX Application |
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.image.Image;
import javafx.stage.Stage;
public class ModernAnimation extends Application {
private final Image[] frames = new Image[4];
private int frameIndex = 0;
private double yPos = 0;
private long lastFrameTime = 0;
private static final double FRAME_HEIGHT = 150;
private static final double SCROLL_SPEED = 400; // 像素/秒
@Override public void init() {
for (int i = 0; i < frames.length; i++) {
frames[i] = new Image(getClass().getResource((i+1)+".jpg").toExternalForm());
}
}
@Override public void start(Stage stage) {
Canvas canvas = new Canvas(300, 300);
GraphicsContext gc = canvas.getGraphicsContext2D();
new AnimationTimer() {
@Override public void handle(long now) {
if (lastFrameTime == 0) { lastFrameTime = now; return; }
double elapsedSec = (now - lastFrameTime) / 1_000_000_000.0;
lastFrameTime = now;
// 邏輯更新 (與渲染分離)
yPos -= SCROLL_SPEED * elapsedSec;
if (yPos <= -FRAME_HEIGHT) {
yPos += FRAME_HEIGHT;
frameIndex = (frameIndex + 1) % frames.length;
}
// 渲染 (JavaFX 內部雙緩衝,無閃爍)
gc.clearRect(0, 0, canvas.getWidth(), canvas.getHeight());
if (frames[frameIndex] != null) {
gc.drawImage(frames[frameIndex], 0, yPos);
// 繪製下一幀實現無縫滾動
gc.drawImage(frames[(frameIndex+1)%frames.length], 0, yPos + FRAME_HEIGHT);
}
}
}.start();
stage.setScene(new Scene(canvas));
stage.show();
}
public static void main(String[] args) { launch(args); }
}
repaint() 推送重繪請求;JavaFX AnimationTimer.handle() 由系統按螢幕刷新率拉取渲染sleep 近似幀率;JavaFX 提供納秒級時間戳,實現幀率無關的物理運動Applet 多線程動畫雖成歷史,但其「邏輯線程分離 + 雙緩衝繪圖 + 幀循環」三大核心模式構成了圖形動畫編程的通用范式。現代開發者應直接採用 JavaFX (桌面)、Canvas API (Web)、SurfaceView/Compose (Android),享受硬件加速、聲明式動畫、響應式編程等現代紅利。理解經典原理,是為了更好地駕馭現代工具,而非重蹈覆轍。