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

Java Applet 動畫程式設計完全指南:雙緩衝、Update 重寫與執行緒模型

     編輯:Java 相關知識

動畫核心原理

Java 動畫透過 Graphics.drawImage() 繪製幀畫面,配合執行緒週期性切換圖像產生動態效果。關鍵要素:

  • 繪圖邏輯在 paint(Graphics g) 或 update(Graphics g)
  • 專用動畫執行緒控制幀率 (Thread.sleep())
  • 雙緩衝技術消除閃爍

閃爍成因與兩大解決方案

閃爍兩大原因

  1. 幀繪製計算耗時過長,畫面更新不連貫
  2. update() 預設行為:先以背景色清空整個區域 → 呼叫 paint(),造成背景與前景交替顯示

方案一:重寫 update() 避免全域清除

// 預設 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() 內部處理局部清除
}

方案二:雙緩衝 (Off-screen Buffering)

  1. 建立離屏 Image ( createImage(width, height) )
  2. 取得其 Graphics 繪製完整幀
  3. 一次性 drawImage() 到螢幕
Image offscreen = createImage(width, height);
Graphics offg = offscreen.getGraphics();
// 在 offg 上繪製完整幀...
g.drawImage(offscreen, 0, 0, this); // 一次性上螢幕

優點:畫面平滑、無閃爍;缺點:佔用額外記憶體 (大圖像明顯)。使用雙緩衝時仍建議重寫 update()。

完整範例:類比時鐘動畫

AnimatorDemo.java

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 啟動頁面

<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 動畫請使用:

  • Canvas API + requestAnimationFrame (原生 JS,效能最佳)
  • CSS Animation/Transition (簡單動畫、GPU 加速)
  • WebGL / Three.js (3D 複雜場景)
  • JavaFX (桌面應用保留 Java 技術棧)
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved