Java: Example - Buffered Analog Clock

This analog (uses hands) clock uses buffered drawing to increase speed. In buffered drawing some or all of an image is computed once and stored so that it doesn't have to be redrawn later. In this program the clock face is drawn only once, and the hands are computed each time.

An application/applet which displays the Clock component

  1 
  2 
  3 
  4 
  5 
  6 
  7 
  8 
  9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
 28 
 29 
 30 
 31 
 32 
 33 
 34 
 35 
 36 
 37 
 38 
 39 
 40 
 41 
 42 
 43 
 44 
 45 
 46 
 47 
 48 
 49 
 50 
 51 
// File   : GUI-lowlevel/animation/analogclock/ClockAnalogBuf.java
// Purpose: An applet/application which displays our clock component.
// Tag    : <applet code="ClockAnalogBuf.class" height="300" width="300"/>
// Author : Fred Swartz, 1999-2007, Placed in public domain.

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

///////////////////////////////////////////////////////////// ClockAnalogBuf
public class ClockAnalogBuf extends JApplet {
    
    //=============================================================== fields
    private Clock _clock;                        // Our clock component.
    
    //================================================================= main
    public static void main(String[] args) {
        JFrame window = new JFrame();
        window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        window.setTitle("Analog Clock");
        window.setContentPane(new ClockAnalogBuf());
        window.pack();                          // Layout components
        window.setLocationRelativeTo(null);     // Center window.
        window.setVisible(true);
    }
    
    //========================================================== constructor
    public ClockAnalogBuf() {
        //... Create an instance of our new clock component.
        _clock = new Clock();
        
        //... Set the applet's layout and add the clock to it.
        setLayout(new BorderLayout());
        add(_clock, BorderLayout.CENTER);
        
        //... Start the clock running.
        start(); 
    }
    
    //=============================================================== start
    @Override public void start() {
        _clock.start();
    }
    
    //================================================================ stop
    @Override public void stop() {
        _clock.stop();
    }
}

A Clock component

  1 
  2 
  3 
  4 
  5 
  6 
  7 
  8 
  9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
 28 
 29 
 30 
 31 
 32 
 33 
 34 
 35 
 36 
 37 
 38 
 39 
 40 
 41 
 42 
 43 
 44 
 45 
 46 
 47 
 48 
 49 
 50 
 51 
 52 
 53 
 54 
 55 
 56 
 57 
 58 
 59 
 60 
 61 
 62 
 63 
 64 
 65 
 66 
 67 
 68 
 69 
 70 
 71 
 72 
 73 
 74 
 75 
 76 
 77 
 78 
 79 
 80 
 81 
 82 
 83 
 84 
 85 
 86 
 87 
 88 
 89 
 90 
 91 
 92 
 93 
 94 
 95 
 96 
 97 
 98 
 99 
100 
101 
102 
103 
104 
105 
106 
107 
108 
109 
110 
111 
112 
113 
114 
115 
116 
117 
118 
119 
120 
121 
122 
123 
124 
125 
126 
127 
128 
129 
130 
131 
132 
133 
134 
135 
136 
137 
138 
139 
140 
141 
142 
143 
144 
145 
146 
147 
148 
149 
150 
151 
152 
153 
154 
155 
156 
157 
158 
159 
160 
161 
162 
163 
// File   : GUI-lowlevel/animation/analogclock/Clock.java
// Purpose: An analog clock component -- Uses Timer and Calendar.
// Note   : Uses a BufferedImage for clock face so isn't drawn each time.
// Author : Fred Swartz, 1999-2007, Placed in public domain.

import java.util.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.awt.image.*;

//////////////////////////////////////////////////////////////// Clock class
class Clock extends JComponent {
    
    //============================================================ constants
    private static final double TWO_PI   = 2.0 * Math.PI;
    
    private static final int    UPDATE_INTERVAL = 100;  // Millisecs
    
    //=============================================================== fields
    private Calendar _now = Calendar.getInstance();  // Current time.
    
    private int _diameter;                 // Height and width of clock face
    private int _centerX;                  // x coord of middle of clock
    private int _centerY;                  // y coord of middle of clock
    private BufferedImage _clockImage;     // Saved image of the clock face.
    
    private javax.swing.Timer _timer;      // Fires to update clock.
    
    //==================================================== Clock constructor
    public Clock() {
        setPreferredSize(new Dimension(300,300));
        
        _timer = new javax.swing.Timer(UPDATE_INTERVAL, new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                updateTime();
                repaint();
            }
        });
    }
    
    //================================================================ start
    /** Start the timer. */
    public void start() {
        _timer.start(); 
    }
    
    //================================================================= stop
    /** Stop the timer. */
    public void stop() {
        _timer.stop(); 
    }
    
    //=========================================================== updateTime
    private void updateTime() {
        //... Avoid creating new objects.
        _now.setTimeInMillis(System.currentTimeMillis());
    }
    
    //======================================================= paintComponent
    @Override public void paintComponent(Graphics g) {
        Graphics2D g2 = (Graphics2D)g;
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                            RenderingHints.VALUE_ANTIALIAS_ON);
        
        //... The panel may have been resized, get current dimensions
        int w = getWidth();
        int h = getHeight();
        _diameter = ((w < h) ? w : h);
        _centerX = _diameter / 2;
        _centerY = _diameter / 2;
        
        //... Create the clock face background image if this is the first time,
        //    or if the size of the panel has changed
        if (_clockImage == null
                || _clockImage.getWidth() != w
                || _clockImage.getHeight() != h) {
            _clockImage = (BufferedImage)(this.createImage(w, h));
            
            //... Get a graphics context from this image
            Graphics2D g2a = _clockImage.createGraphics();
            g2a.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                                 RenderingHints.VALUE_ANTIALIAS_ON);
            drawClockFace(g2a);
        }
        
        //... Draw the clock face from the precomputed image
        g2.drawImage(_clockImage, null, 0, 0);
        
        //... Draw the clock hands dynamically each time.
        drawClockHands(g2);
    }
    
    //====================================== convenience method drawClockHands
    private void drawClockHands(Graphics2D g2) {
        //... Get the various time elements from the Calendar object.
        int hours   = _now.get(Calendar.HOUR);
        int minutes = _now.get(Calendar.MINUTE);
        int seconds = _now.get(Calendar.SECOND);
        int millis  = _now.get(Calendar.MILLISECOND);
        
        //... second hand
        int handMin = _diameter / 8;    // Second hand doesn't start in middle.
        int handMax = _diameter / 2;    // Second hand extends to outer rim.
        double fseconds = (seconds + (double)millis/1000) / 60.0;
        drawRadius(g2, fseconds, 0, handMax);
        
        //... minute hand
        handMin = 0;                    // Minute hand starts in middle.
        handMax = _diameter / 3; 
        double fminutes = (minutes + fseconds) / 60.0;
        drawRadius(g2, fminutes, 0, handMax);
        
        //... hour hand
        handMin = 0;
        handMax = _diameter / 4;
        drawRadius(g2, (hours + fminutes) / 12.0, 0, handMax);
    }
    
    //======================================= convenience method drawClockFace
    private void drawClockFace(Graphics2D g2) {
        // ... Draw the clock face.  Probably into a buffer.        
        g2.setColor(Color.PINK);
        g2.fillOval(0, 0, _diameter, _diameter);
        g2.setColor(Color.BLACK);
        g2.drawOval(0, 0, _diameter, _diameter);
        
        int radius = _diameter / 2;
        
        //... Draw the tick marks around the circumference.
        for (int sec = 0; sec < 60; sec++) {
            int tickStart;
            if (sec%5 == 0) {
                tickStart = radius - 10;  // Draw long tick mark every 5.
            } else {
                tickStart = radius - 5;   // Short tick mark.
            }
            drawRadius(g2, sec / 60.0, tickStart , radius);
        }
    }
    
    //==================================== convenience method drawRadius
    // This draw lines along a radius from the clock face center.
    // By changing the parameters, it can be used to draw tick marks,
    // as well as the hands.
    private void drawRadius(Graphics2D g2, double percent,
                            int minRadius, int maxRadius) {
        //... percent parameter is the fraction (0.0 - 1.0) of the way
        //    clockwise from 12.   Because the Graphics2D methods use radians
        //    counterclockwise from 3, a little conversion is necessary.
        //    It took a little experimentation to get this right.
        double radians = (0.5 - percent) * TWO_PI;
        double sine   = Math.sin(radians);
        double cosine = Math.cos(radians);
        
        int dxmin = _centerX + (int)(minRadius * sine);
        int dymin = _centerY + (int)(minRadius * cosine);
        
        int dxmax = _centerX + (int)(maxRadius * sine);
        int dymax = _centerY + (int)(maxRadius * cosine);
        g2.drawLine(dxmin, dymin, dxmax, dymax);
    }
}