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

Java Swing 繪製類比時鐘:自訂繪圖與 Timer 驅動完整範例

     編輯:Java 程式設計入門

程式功能

繼承 JFrame,覆寫 paint(Graphics) 繪製類比時鐘面板:外圓、刻度數字(1~12)、時/分/秒三支指針,並以 javax.swing.Timer 每秒觸發 repaint() 更新指針角度,右上角顯示數位時間(HH:mm:ss)。

核心繪圖邏輯

  • 中心點 (200,200),半徑 200px。
  • 指針角度計算(以 12 點鐘方向為 -90°,順時針增加):
    • 秒針:90 - second * 6
    • 分針:90 - minute * 6
    • 時針:90 - hour * 30 - minute / 2
  • 使用 Graphics2D 設定 BasicStroke 調整指針粗細(秒針 1.0、分針 2.2、時針 4.3)。
  • 雙緩衝技巧:先以背景色繪製前一幀指針抹除,再繪製新指針,減少閃爍。

完整原始碼

import java.awt.*;
import java.awt.event.*;
import java.util.Calendar;
import java.util.GregorianCalendar;
import javax.swing.*;

public class AnalogClock extends JFrame implements ActionListener {
    private static final double RAD = Math.PI / 180;

    public AnalogClock() {
        super("Analog Clock");
        setSize(400, 400);
        setLocation(400, 200);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setBackground(Color.WHITE);
        setResizable(true);
        setVisible(true);

        // 每秒重繪
        new Timer(1000, e -> repaint()).start();

        addWindowListener(new WindowAdapter() {
            @Override public void windowClosing(WindowEvent e) { System.exit(0); }
        });
    }

    @Override public void paint(Graphics g) {
        super.paint(g); // 清背景
        Graphics2D g2 = (Graphics2D) g;
        Insets ins = getInsets();
        int h = getSize().height;
        int cx = 200, cy = 200; // 固定中心(簡化版)

        // 表盤外圓
        g2.setStroke(new BasicStroke(3f));
        g.drawOval(40, 40, h - 80, h - 80);

        // 刻度數字 1~5 (右上區)
        int R1 = 140, angle = 60;
        for (int i = 1; i <= 5; i++, angle -= 30) {
            int x = (int) (cx + R1 * Math.cos(RAD * angle));
            int y = (int) (cy - R1 * Math.sin(RAD * angle));
            g.setFont(new Font("標楷體", Font.BOLD, 20));
            g.drawString(String.valueOf(i), x, y);
        }
        // 刻度數字 7~11 (左下區)
        int R2 = 150; angle = -120;
        for (int i = 7; i <= 11; i++, angle -= 30) {
            int x = (int) (cx + R2 * Math.cos(RAD * angle));
            int y = (int) (cy - R2 * Math.sin(RAD * angle));
            g.drawString(String.valueOf(i), x, y);
        }
        g.drawString("6", 190, 350);
        g.drawString("12", 190, 60);

        // 目前時間
        Calendar now = new GregorianCalendar();
        int hour = now.get(Calendar.HOUR_OF_DAY);
        int min = now.get(Calendar.MINUTE);
        int sec = now.get(Calendar.SECOND);
        String time = String.format("%02d:%02d:%02d", hour, min, sec);
        g.setColor(Color.WHITE);
        g.fillRect(10, 30, 90, 28);
        g.setColor(Color.RED);
        g.drawString(time, 10, 50);

        // 指針角度
        int S = 90 - sec * 6;
        int M = 90 - min * 6;
        int H = 90 - hour * 30 - min / 2;

        // 秒針(黑,細)
        drawHand(g, cx, cy, 130, S, Color.BLACK, 1.0f, true);
        // 分針(藍,中)
        drawHand(g, cx, cy, 110, M, Color.BLUE, 2.2f, true);
        // 時針(綠,粗)
        drawHand(g, cx, cy, 90, H, Color.GREEN, 4.3f, false);
    }

    /** 繪製單支指針,erase=true 先以背景色抹除上一幀 */
    private void drawHand(Graphics g, int cx, int cy, int len, int angleDeg,
                          Color color, float thickness, boolean erase) {
        Graphics2D g2 = (Graphics2D) g;
        int x = (int) (cx + len * Math.cos(RAD * angleDeg));
        int y = (int) (cy - len * Math.sin(RAD * angleDeg));
        if (erase) {
            int prev = 90 - (angleDeg == 90 - (int)(len==130?0:0) ? 0 : 0); // 簡化:實際需記錄上一角度
            // 此處為示意,完整版建議保存上一角度至欄位
        }
        g2.setStroke(new BasicStroke(thickness));
        g.setColor(color);
        g.drawLine(cx, cy, x, y);
    }

    @Override public void actionPerformed(ActionEvent e) {} // Timer 使用 Lambda

    public static void main(String[] args) {
        SwingUtilities.invokeLater(AnalogClock::new);
    }
}

改進建議

  1. 將指針上一幀角度存為實例變數,drawHand 才能正確抹除,避免殘影。
  2. 使用 AffineTransform 旋繪製指針圖形(如三角形箭頭),比單線條更美觀。
  3. 加入抗鋸齒:g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
  4. 支援視窗縮放:依 getWidth()/getHeight() 動態計算中心與半徑。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved