自學 Java Swing 時,繼承 `JFrame` 重寫 `paint(Graphics g)` 方法繪製背景圖片。運行發現:
`paint()` 不是開發者主動調用,而是由 **Swing 繫統在以下情況自動觸發**:
代碼關鍵順序:
public myFrame() {
// ... 初始化代碼 ...
this.setVisible(true); // 第1次觸發 paint() → 此時 nowBG 還未初始化!
StaticValue.init(); // 初始化圖片資源
for(...) allBG.add(...); // 創建場景
this.nowBG = allBG.get(0); // nowBG 賦值
this.repaint(); // 第2次觸發 paint() → 此時 nowBG 已就緒
}
關鍵錯誤:`setVisible(true)` **必須放在所有組件初始化完成之後**!
public myFrame() {
// 1. 基礎屬性設置
this.setTitle("馬里奧遊戲程序");
this.setSize(900, 600);
this.setLocationRelativeTo(null);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setResizable(false);
// 2. 資源初始化(在顯示之前!)
StaticValue.init(); // 載入圖片等靜態資源
for (int i = 1; i <= 3; i++) {
this.allBG.add(new Background(i, i == 3));
}
this.nowBG = this.allBG.get(0);
// 3. 事件監聽器註冊
this.addKeyListener(this);
// 4. 最後顯示窗口(此時 paint() 會被調用,nowBG 已就緒)
this.setVisible(true);
}
原代碼在 `paint()` 中創建 `BufferedImage` 屬於「主動繪圖」模式,但 Swing 推薦「被動繪圖」+ 雙緩衝:
public class GamePanel extends JPanel {
private Background currentBG;
public GamePanel() {
setDoubleBuffered(true); // 啟用雙緩衝(默認開啟)
setPreferredSize(new Dimension(900, 600));
}
public void setBackground(Background bg) {
this.currentBG = bg;
repaint(); // 請求重繪,而非直接調用 paint
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g); // 關鍵:清除背景、繼承雙緩衝邏輯
if (currentBG != null) {
// 直接繪製到組件的 Graphics 上(Swing 已處理雙緩衝)
g.drawImage(currentBG.getBgImage(), 0, 0, this);
}
}
}
// 主窗口使用
public class MyFrame extends JFrame {
private GamePanel gamePanel;
public MyFrame() {
gamePanel = new GamePanel();
add(gamePanel);
// 初始化資源...
gamePanel.setBackground(firstBG);
pack(); // 自適應大小
setVisible(true);
}
}
| 錯誤做法 | 正確做法 | 原因 |
|---|---|---|
| 繼承 JFrame 重寫 paint() | 繼承 JPanel 重寫 paintComponent() | JPanel 專為繪圖設計,支持雙緩衝 |
| 在 paint() 中創建 BufferedImage | 直接用傳入的 Graphics 繪製 | Swing 已在底層實現雙緩衝,手動創建多餘且易出錯 |
| 構造器中先 setVisible(true) 再初始化 | 先初始化所有資源,最後 setVisible(true) | 避免 paint() 在數據未就緒時被調用 |
| 去掉 null 判斷直接繪圖 | 保留防禦性判斷 if (bg != null) | 應對異步初始化、資源加載失敗等異常情況 |
| 調用 paint() 手動觸發繪圖 | 調用 repaint() 請求重繪 | repaint() 會合併請求、在 EDT 執行,性能更好 |
// 原代碼異常點
g2.drawImage(this.nowBG.getBgImage(), 0, 0, this); // nowBG 為 null
// 修復:防禦性編程
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (currentBG != null && currentBG.getBgImage() != null) {
g.drawImage(currentBG.getBgImage(), 0, 0, this);
}
}
Java Swing 繪圖核心原則:
掌握這些原理,能避開 90% 的 Swing 繪圖坑,實現流暢無閃爍的遊戲/動畫界面。