首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java Applet 多線程動畫實現解析:從 AWT 到現代 JavaFX 的演進

     編輯:Java 相關知識

早期 Java Applet 通過繼承 java.applet.Applet 並實現 Runnable 介面,在獨立線程中驅動動畫幀更新,是許多開發者接觸多線程圖形編程的啟蒙。雖然 Applet 技術已被淘汰(JDK 9 標記棄用,JDK 11 移除),但其雙緩衝繪圖、幀率控制、線程與 UI 線程分離的核心思想在 Swing、JavaFX、Android Canvas、Web Canvas 等現代圖形框架中延續。本文解析經典代碼並對比現代實現。

經典 Applet 動畫代碼解析


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 專用 APIImageIO.read() / Toolkit.getImage() / JavaFX Image / Glide/Picasso (Android)
生命周期init/start/stop/destroyApplet 特有Servlet/Spring Bean 生命周期 / Activity/Fragment 生命周期 / JavaFX Application

現代 JavaFX 實現對比 (推薦)


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); }
}

關鍵差異總結

  • 推拉模型反轉:Applet 主動 repaint() 推送重繪請求;JavaFX AnimationTimer.handle() 由系統按螢幕刷新率拉取渲染
  • 時間基準:Applet 用 sleep 近似幀率;JavaFX 提供納秒級時間戳,實現幀率無關的物理運動
  • 線程模型:Applet 手動管理動畫線程;JavaFX 單線程 (JavaFX Application Thread) 處理所有 UI,動畫邏輯在同一線程按幀執行,無同步問題
  • 資源管理:Applet 依賴瀏覽器/插件生命周期;JavaFX 獨立應用/模塊化部署

總結

Applet 多線程動畫雖成歷史,但其「邏輯線程分離 + 雙緩衝繪圖 + 幀循環」三大核心模式構成了圖形動畫編程的通用范式。現代開發者應直接採用 JavaFX (桌面)、Canvas API (Web)、SurfaceView/Compose (Android),享受硬件加速、聲明式動畫、響應式編程等現代紅利。理解經典原理,是為了更好地駕馭現代工具,而非重蹈覆轍。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved