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

Java Swing 類比時鐘完整實作:雙緩衝、平滑秒針、系統時間同步、自訂外觀

     編輯:Java 相關知識

前言

經典類比時鐘是圖形程式設計入門佳作。本文提供生產級 AnalogClock:雙緩衝無閃爍、平滑秒針動畫 (每秒重繪)、時/分/秒針幾何計算、系統時間同步、可配置外觀 (顏色、粗細、字型)、資源正確釋放、打包為可執行 JAR。

完整程式碼

package com.example.swing;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Line2D;
import java.time.LocalTime;
import java.time.ZoneId;

public class AnalogClock extends JFrame {
    private static final int SIZE = 400;
    private static final int MARGIN = 20;
    private static final int RADIUS = (SIZE - 2 * MARGIN) / 2;
    private static final int CENTER = SIZE / 2;

    private final ClockPanel clockPanel = new ClockPanel();
    private final Timer timer;

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            try {
                UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
            } catch (Exception ignored) {}
            new AnalogClock().setVisible(true);
        });
    }

    public AnalogClock() {
        super("類比時鐘");
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setResizable(false);
        add(clockPanel);
        pack();
        setLocationRelativeTo(null);

        // 每秒觸發重繪
        timer = new Timer(1000, e -> clockPanel.repaint());
        timer.start();
        // 視窗關閉時停止 Timer
        addWindowListener(new java.awt.event.WindowAdapter() {
            @Override public void windowClosing(java.awt.event.WindowEvent e) {
                timer.stop();
            }
        });
    }

    private class ClockPanel extends JPanel {
        public ClockPanel() {
            setPreferredSize(new Dimension(SIZE, SIZE));
            setBackground(Color.WHITE);
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2 = (Graphics2D) g;
            g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
            g2.setStroke(new BasicStroke(2f));

            // 1. 外圓
            g2.setColor(new Color(0x333333));
            g2.drawOval(MARGIN, MARGIN, 2 * RADIUS, 2 * RADIUS);

            // 2. 刻度與數字
            drawTicksAndNumbers(g2);

            // 3. 取得當前時間
            LocalTime now = LocalTime.now(ZoneId.systemDefault());
            int hour = now.getHour();
            int minute = now.getMinute();
            int second = now.getSecond();
            int milli = now.getNano() / 1_000_000;

            // 4. 計算角度 (12 點鐘方向 = -90° = -PI/2)
            // 秒針:平滑移動 (含毫秒)
            double secAngle = Math.toRadians(6 * second + 6.0 * milli / 1000 - 90);
            // 分針:含秒數微調
            double minAngle = Math.toRadians(6 * minute + 0.1 * second - 90);
            // 時針:含分數微調 (12 小制)
            double hourAngle = Math.toRadians(30 * (hour % 12) + 0.5 * minute - 90);

            // 5. 繪製指針 (中心點 CENTER, CENTER)
            drawHand(g2, hourAngle, RADIUS * 0.5, 6f, new Color(0x2C3E50));   // 時針:短、粗、深藍
            drawHand(g2, minAngle, RADIUS * 0.75, 4f, new Color(0xE74C3C));    // 分針:中、中、紅
            drawHand(g2, secAngle, RADIUS * 0.9, 1.5f, new Color(0xE67E22));   // 秒針:長、細、橙

            // 6. 中心圓
            g2.setColor(new Color(0x2C3E50));
            g2.fillOval(CENTER - 6, CENTER - 6, 12, 12);

            // 7. 數位時間顯示 (左上角)
            g2.setFont(new Font(Font.MONOSPACED, Font.BOLD, 18));
            g2.setColor(Color.BLACK);
            String digital = String.format("%02d:%02d:%02d", hour, minute, second);
            g2.drawString(digital, MARGIN + 5, MARGIN + 20);
        }

        private void drawTicksAndNumbers(Graphics2D g2) {
            g2.setFont(new Font(Font.SERIF, Font.BOLD, 18));
            g2.setColor(Color.BLACK);
            for (int i = 1; i <= 12; i++) {
                double angle = Math.toRadians(30 * i - 90); // 30° per hour
                int outer = RADIUS - 10;
                int inner = RADIUS - 25;
                int xOuter = CENTER + (int) (outer * Math.cos(angle));
                int yOuter = CENTER + (int) (outer * Math.sin(angle));
                int xInner = CENTER + (int) (inner * Math.cos(angle));
                int yInner = CENTER + (int) (inner * Math.sin(angle));

                // 粗細刻度
                g2.setStroke(i % 3 == 0 ? new BasicStroke(3f) : new BasicStroke(1.5f));
                g2.draw(new Line2D.Double(xInner, yInner, xOuter, yOuter));

                // 數字 (僅 3,6,9,12 或全部)
                if (i % 3 == 0) {
                    String num = String.valueOf(i);
                    FontMetrics fm = g2.getFontMetrics();
                    int tx = CENTER + (int) ((RADIUS - 35) * Math.cos(angle)) - fm.stringWidth(num) / 2;
                    int ty = CENTER + (int) ((RADIUS - 35) * Math.sin(angle)) + fm.getAscent() / 2;
                    g2.drawString(num, tx, ty);
                }
            }
        }

        private void drawHand(Graphics2D g2, double angle, double length, float thickness, Color color) {
            int x = CENTER + (int) (length * Math.cos(angle));
            int y = CENTER + (int) (length * Math.sin(angle));
            g2.setStroke(new BasicStroke(thickness, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
            g2.setColor(color);
            g2.drawLine(CENTER, CENTER, x, y);
        }
    }
}

關鍵技術點

技術說明
Timer(1000, ...)Swing Timer 在 EDT 執行,安全更新 UI;1000ms 精度足夠秒針動畫。
平滑秒針6 * second + 6.0 * milli / 1000 讓秒針每毫秒微動,非跳躍式。
角度基準數學慣例 0° = 3 點鐘方向;12 點鐘 = -90°,故全部 - 90。
雙緩衝Swing JPanel 預設雙緩衝,無需手動 BufferStrategy。
BasicStroke.CAP_ROUND指針末端圓潤,視覺更專業。
LocalTime.now(ZoneId)JDK 8+ 時間 API,執行緒安全、不可變、時區明確。

進階擴充

  • 時區切換:加入 JComboBox<ZoneId> 選擇時區,clockPanel.repaint() 即時更新。
  • 鬧鐘/提醒:ScheduledExecutorService 排程觸發 Toolkit.getDefaultToolkit().beep() 或彈窗。
  • 秒針跳躍/平滑切換:布林旗標控制是否加入毫秒分量。
  • 主題皮膚:將顏色、粗細、字型抽取為 ClockTheme 物件,支援深色/淺色模式。
  • 全螢幕護眼模式:setUndecorated(true); setExtendedState(MAXIMIZED_BOTH);。

打包執行

# Maven
mvn clean package -DskipTests
java -jar target/analog-clock-1.0.jar

# 或直接編譯執行
javac com/example/swing/AnalogClock.java
java com.example.swing.AnalogClock

常見問題

  • 秒針不動:確認 timer.start() 已呼叫,且未在 paintComponent 執行耗時操作阻塞 EDT。
  • 時間偏移:檢查系統時區設定;伺服器端建議強制 ZoneId.of("UTC") 或使用者設定時區。
  • 高 DPI 模糊:JDK 9+ 加 -Dsun.java2d.uiScale.enabled=true;或自行縮放 g2.scale(scale, scale)。
  • 記憶體洩漏:Timer 未停止會阻止 GC;視窗關閉時必須 timer.stop()。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved