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

Java Applet 動畫程式設計全攻略:從基本幀動畫到雙緩衝時鐘實作

     編輯:Java 程式設計入門

Java Applet 動畫的核心在於「幀與幀之間的快速切換」。本文系統講解動畫實現原理、閃爍成因與兩大解決方案 (覆寫 update()、雙緩衝),並提供一個完整的類比時鐘動畫範例。

動畫基礎架構

  • 繪圖執行緒: 實作 Runnable,在 run() 中循環:修改狀態 → repaint() → Thread.sleep(間隔)。
  • start() 啟動執行緒,stop() 設為 null 結束。
  • paint(Graphics g) 繪製當前幀畫面。

閃爍根源與兩大對策

閃爍原因解決方案核心做法
AWT 預設 update() 先清背景再呼叫 paint()覆寫 update()不清背景,僅繪製必要區域,或直接在 update() 完成所有繪圖
直接在螢幕繪圖過程可見雙緩衝先繪至離屏 Image,再一次 drawImage 到螢幕

方案一:覆寫 update()

public void update(Graphics g) {
    // 不呼叫 g.clearRect() 或 fillRect(背景)
    // 直接繪製增量或完整畫面
    paint(g); // 或將繪圖邏輯直接寫在這裡
}

方案二:雙緩衝標準模式

Image offScreenImage;
Graphics offScreenGC;
Dimension offScreenSize;

public void update(Graphics g) {
    Dimension d = getSize();
    if (offScreenImage == null || !d.equals(offScreenSize)) {
        offScreenImage = createImage(d.width, d.height);
        offScreenGC = offScreenImage.getGraphics();
        offScreenSize = d;
    }
    // 離屏繪圖
    offScreenGC.setColor(getBackground());
    offScreenGC.fillRect(0, 0, d.width, d.height);
    offScreenGC.setColor(getForeground());
    paint(offScreenGC); // 實際繪圖邏輯在 paint()

    // 一次性繞到螢幕
    g.drawImage(offScreenImage, 0, 0, this);
}

完整實戰:類比時鐘動畫 (AnimatorDemo.java)

展示雙緩衝、三角函數計算指針角度、update() 整合、執行緒控制的綜合範例。

import java.awt.*;
import java.applet.*;
import java.util.Date;
import java.text.SimpleDateFormat;

public class AnimatorDemo extends Applet implements Runnable {
    Thread timer;
    int lastXs, lastYs, lastXm, lastYm, lastXh, lastYh;
    SimpleDateFormat formatter;
    String lastDate;
    Font clockFont;
    Color handColor, numberColor;

    public void init() {
        lastXs = lastYs = lastXm = lastYm = lastXh = lastYh = 0;
        formatter = new SimpleDateFormat("yyyy EEE MMM dd HH:mm:ss");
        lastDate = formatter.format(new Date());
        clockFont = new Font("Serif", Font.PLAIN, 14);
        handColor = Color.BLUE;
        numberColor = Color.DARK_GRAY;
        setBackground(Color.WHITE);
        resize(300, 300);
    }

    // 畫圓 (Bresenham 演算法)
    void plotPoints(int x0, int y0, int x, int y, Graphics g) { ... }
    void circle(int x0, int y0, int r, Graphics g) { ... }

    public void paint(Graphics g) {
        Date now = new Date();
        int s = Integer.parseInt(new SimpleDateFormat("s").format(now));
        int m = Integer.parseInt(new SimpleDateFormat("m").format(now));
        int h = Integer.parseInt(new SimpleDateFormat("h").format(now));
        String today = formatter.format(now);

        int xc = 80, yc = 55; // 表盤中心
        // 計算指針端點 (角度轉弧度: 度 * PI/180, 12點鐘方向為 -90度)
        int xs = (int)(Math.cos(s * Math.PI/30 - Math.PI/2) * 45 + xc);
        int ys = (int)(Math.sin(s * Math.PI/30 - Math.PI/2) * 45 + yc);
        int xm = (int)(Math.cos(m * Math.PI/30 - Math.PI/2) * 40 + xc);
        int ym = (int)(Math.sin(m * Math.PI/30 - Math.PI/2) * 40 + yc);
        int xh = (int)(Math.cos((h*30 + m/2) * Math.PI/180 - Math.PI/2) * 30 + xc);
        int yh = (int)(Math.sin((h*30 + m/2) * Math.PI/180 - Math.PI/2) * 30 + yc);

        // 表盤
        g.setFont(clockFont);
        g.setColor(handColor);
        circle(xc, yc, 50, g);
        g.setColor(numberColor);
        g.drawString("9", xc-45, yc+3);
        g.drawString("3", xc+40, yc+3);
        g.drawString("12", xc-5, yc-37);
        g.drawString("6", xc-3, yc+45);

        // 擦除舊指針 (畫背景色)
        g.setColor(getBackground());
        if (xs != lastXs || ys != lastYs) g.drawLine(xc, yc, lastXs, lastYs);
        if (xm != lastXm || ym != lastYm) {
            g.drawLine(xc, yc-1, lastXm, lastYm);
            g.drawLine(xc-1, yc, lastXm, lastYm);
        }
        if (xh != lastXh || yh != lastYh) {
            g.drawLine(xc, yc-1, lastXh, lastYh);
            g.drawLine(xc-1, yc, lastXh, lastYh);
        }

        // 畫新指針
        g.setColor(numberColor);
        g.drawLine(xc, yc, xs, ys); // 秒針
        g.setColor(handColor);
        g.drawLine(xc, yc-1, xm, ym);
        g.drawLine(xc-1, yc, xm, ym); // 分針 (加粗)
        g.drawLine(xc, yc-1, xh, yh);
        g.drawLine(xc-1, yc, xh, yh); // 時針 (加粗)

        g.drawString(today, 5, 125); // 顯示完整時間

        // 記錄本幀座標供下幀擦除
        lastXs = xs; lastYs = ys;
        lastXm = xm; lastYm = ym;
        lastXh = xh; lastYh = yh;
        lastDate = today;
    }

    public void start() { if (timer == null) { timer = new Thread(this); timer.start(); } }
    public void stop() { timer = null; }
    public void run() {
        while (timer != null) {
            repaint();
            try { Thread.sleep(1000); } catch (InterruptedException e) {}
        }
    }
}

動畫開發黃金法則

  1. 最小化 paint() 工作量: 計算、邏輯放在 run(),paint() 只負責繪圖。
  2. 善用剪輯區域: g.getClipBounds() 只重繪必要區塊。
  3. 物件複用: 避免在 paint() 內 `new` 物件 (Font、Color、StringBuilder 等),造成 GC 壓力。
  4. 雙緩衝為標配: 任何非極簡單動畫皆應啟用。

雖然 Applet 已退出歷史舞台,但其動畫程式設計思想 (遊戲迴路、雙緩衝、插值補幀) 完整傳承至 JavaFX、LibGDX、Android Canvas 等現代圖形框架中。

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