Build a Real-Time Analog Clock in Java Swing

Build a Real-Time Analog Clock in Java SwingCreating a real-time analog clock in Java Swing is a great way to practice GUI programming, custom painting, animation, and time-handling. This article walks through the design, implementation, and enhancements of a fully functional analog clock built with Java Swing. It includes source code, explanations of key concepts, and ideas for extending the project.


Why build an analog clock in Java Swing?

  • Hands-on GUI practice: You’ll learn custom painting with Swing’s painting model (paintComponent), layout basics, and double buffering.
  • Animation & timing: Implement smooth real-time updates using a Swing Timer.
  • 2D graphics skills: Use Java2D for rotation, anti-aliasing, and drawing shapes and text.
  • Practical project: Results in a visually pleasing widget you can reuse in desktop apps.

Key components

  1. Clock frame — the main application window (JFrame).
  2. Clock panel — a custom JPanel that paints the clock face and hands.
  3. Timer — a javax.swing.Timer that triggers repaints at regular intervals.
  4. Time source — java.time.LocalTime (or Calendar) to obtain the current time.

Design considerations

  • Maintain separation of concerns: UI setup in the frame, painting logic in the panel.
  • Use double buffering and anti-aliasing to avoid flicker and improve visual quality.
  • Support resizing: compute lengths and positions relative to the panel’s current size.
  • Smooth second hand: update at 60 frames per second or compute fractional seconds for smooth motion.

Full source code

// File: RealTimeAnalogClock.java import javax.swing.*; import java.awt.*; import java.awt.event.*; import java.time.LocalTime; import java.time.ZoneId; import java.time.ZonedDateTime; public class RealTimeAnalogClock {     public static void main(String[] args) {         SwingUtilities.invokeLater(() -> {             JFrame frame = new JFrame("Real-Time Analog Clock");             frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);             frame.add(new ClockPanel());             frame.pack();             frame.setLocationRelativeTo(null);             frame.setVisible(true);         });     } } class ClockPanel extends JPanel {     private static final int PREFERRED_SIZE = 400;     private final Timer timer;     public ClockPanel() {         setPreferredSize(new Dimension(PREFERRED_SIZE, PREFERRED_SIZE));         setBackground(Color.white);         // 60 FPS for smooth second hand         timer = new Timer(1000 / 60, e -> repaint());         timer.start();     }     @Override     protected void paintComponent(Graphics g) {         super.paintComponent(g);         drawClock((Graphics2D) g);     }     private void drawClock(Graphics2D g2) {         int w = getWidth();         int h = getHeight();         int size = Math.min(w, h);         int cx = w / 2;         int cy = h / 2;         int radius = (int) (size * 0.45);         // Rendering hints for smoothness         g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);         // Draw the dial         g2.setColor(Color.lightGray);         g2.fillOval(cx - radius, cy - radius, radius * 2, radius * 2);         g2.setColor(Color.white);         g2.fillOval(cx - (int)(radius*0.95), cy - (int)(radius*0.95), (int)(radius*1.9), (int)(radius*1.9));         g2.setColor(Color.black);         g2.setStroke(new BasicStroke(3));         g2.drawOval(cx - radius, cy - radius, radius * 2, radius * 2);         // Draw tick marks         for (int i = 0; i < 60; i++) {             double angle = Math.toRadians(i * 6); // 360/60 = 6 deg             int inner = radius - (i % 5 == 0 ? 15 : 7);             int x1 = cx + (int) (inner * Math.sin(angle));             int y1 = cy - (int) (inner * Math.cos(angle));             int x2 = cx + (int) (radius * Math.sin(angle));             int y2 = cy - (int) (radius * Math.cos(angle));             g2.setStroke(new BasicStroke(i % 5 == 0 ? 3f : 1.5f));             g2.drawLine(x1, y1, x2, y2);         }         // Get current time with fractional seconds for smoothness         ZonedDateTime now = ZonedDateTime.now(ZoneId.systemDefault());         int hour = now.getHour() % 12;         int minute = now.getMinute();         int second = now.getSecond();         int nano = now.getNano();         double fractionalSecond = second + nano / 1_000_000_000.0;         // Calculate angles         double secondAngle = Math.toRadians(fractionalSecond * 6); // 360/60         double minuteAngle = Math.toRadians((minute + fractionalSecond / 60.0) * 6);         double hourAngle = Math.toRadians((hour + (minute + fractionalSecond / 60.0) / 60.0) * 30); // 360/12=30         // Draw hour hand         int hourLen = (int) (radius * 0.5);         drawHand(g2, cx, cy, hourAngle, hourLen, 8f, Color.BLACK);         // Draw minute hand         int minLen = (int) (radius * 0.75);         drawHand(g2, cx, cy, minuteAngle, minLen, 5f, Color.DARK_GRAY);         // Draw second hand         int secLen = (int) (radius * 0.85);         drawHand(g2, cx, cy, secondAngle, secLen, 2f, Color.RED);         // Center cap         g2.setColor(Color.BLACK);         g2.fillOval(cx - 6, cy - 6, 12, 12);     }     private void drawHand(Graphics2D g2, int cx, int cy, double angle, int length, float strokeWidth, Color color) {         int x = cx + (int) (length * Math.sin(angle));         int y = cy - (int) (length * Math.cos(angle));         g2.setColor(color);         g2.setStroke(new BasicStroke(strokeWidth, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));         g2.drawLine(cx, cy, x, y);     } } 

How it works — brief explanation

  • The ClockPanel repaints at ~60 FPS using a Swing Timer to produce smooth motion.
  • ZonedDateTime.now(…) provides current hour, minute, second, and nanoseconds for fractional-second precision.
  • Hands are drawn by converting time to angles, then computing end coordinates via sine/cosine.
  • Resizing is handled by basing sizes on the panel’s current width/height.

Possible enhancements

  • Add ticking sound on seconds using javax.sound.sampled.
  • Skins/themes: change colors, numerals, background images.
  • Add numerals (1–12) with proper positioning and scaling.
  • Support multiple time zones with a dropdown.
  • Save as an applet or export as a lightweight desktop widget.

Troubleshooting tips

  • If the UI becomes unresponsive, ensure long-running tasks aren’t on the EDT. Timer callbacks and painting run on the EDT; keep them lightweight.
  • For very smooth animations, consider using ~60 FPS; but reduce if CPU use is too high.
  • If hands appear jittery when resized, add a small buffer or throttle repaints during resize events.

This project demonstrates practical Swing painting, timing, and Java2D techniques while producing a useful, attractive UI component you can extend.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *