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
- Clock frame — the main application window (JFrame).
- Clock panel — a custom JPanel that paints the clock face and hands.
- Timer — a javax.swing.Timer that triggers repaints at regular intervals.
- 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.
Leave a Reply