CSCI 201: Intro to Programming (Java)

Lab 6: Analog clock design

Your task today is to design an analog clock application similar to the one shown below. You will learn how to get and set up current time and make the clock working.

Lab Task I: static clock design

This part of the lab is devoted to the design of a static clock display similar to the one shown below. The clock won't work yet. We will add the functionality to this application in the second part of this lab.

The entire Clock project consist of three classes borrowed from the Swing Template. Class SwingApp is used for the initialization purposes, while the class DrawPanel is used for drawing.

Class SwingApp

We use the class variable private DisplayPanel clock although it is used just in the only method of this class. This is done for a later development of this class, when another method using this class variable will be added.

The class constructor

  1. Declare a variable pane of class Container and initialize it with getContentPane().
  2. Initialize the class variable clock with new DrawPanel().
  3. Set up the desired background of the clock panel by using the setBackground() method.
  4. Add the panel clock to the container pane.

Class DisplayPanel

This class has three members hours, minutes, and seconds to set up the clock. These variables will be later initialized and updated by the methods of class Clock, so we set the (default) package access specifier for them. For now, we just initialize these variables with some (legal) values to display static time.

The paintComponent() method

  1. Declare the following variables:
    int rClock = 95;                  // clock radius  
    int rNumbers = 85;                // number radius 
    int rHourHand = 50;               // hours arm length 
    int rMinHand = 70;                // minutes arm length
    int rSecHand = 80;                // seconds arm length
    
  2. Draw the clock, assuming the app size is about 200x200 pixels, so the point (100,100) is its center:
    g.setColor(Color.black);
    g.drawOval(100 - rClock, 100 - rClock, 2*rClock, 2*rClock);
  3. Compute the widths and height of the 1- and 2-digit strings on the clock display:
    int width1 = g.getFontMetrics().stringWidth("0");
    int width2 = g.getFontMetrics().stringWidth("00");
    int height= g.getFontMetrics().getHeight();
  4. Draw the numbers 3, 6, 9, and 12 on the clock display:
    g.setColor(Color.magenta); 
    g.drawString("12", (int)(100 - width2/2.0), 
    	   (int)(100 - rNumbers + height/2.0));
    g.drawString("6",  (int)(100 - width1/2.0), 
    	   (int)(100 + rNumbers + height/2.0));
    g.drawString("9",  (int)(100 - rNumbers - width1/2.0), 
    	   (int)(100 + height/2.0));
    g.drawString("3",  (int)(100 + rNumbers - width1/2.0), 
    	   (int)(100 + height/2.0));
    
  5. Draw the seconds arm. The underlying math is simple. There are 60 seconds in one minute and they are uniformly distributed in the range of 360° of the circle. Hence, every second is worth 360° / 60 = 6° of the circle area. Therefore, for the position of the seconds arm corresponding to s seconds, the angle θ shown in the figure, can be computed as θ = s·6°, and the angle α is then α = 90° - θ = 90° - s·6°.

    Now, if r is the seconds arm radius, then the coordinates of its endpoint p are (r · cos(α), r · sin(α)) (assuming that the origin is in the center of the circle). To compute sin(α) and cos(α) one needs to convert the angle from degrees to radians by using the formula

    ωradians = ωdegrees·π / 180, where π ≈ 3.14

    Finally, moving the origin from the center of the circle to the top left corner of the app area leads to the formulas

    x = 100 + r·cos((90 - s·6)·π / 180)
    y = 100 - r·sin((90 - s·6)·π / 180)

    Here we assume that the app size is 220x250 pixels, so the constant 100 is used for translation of the origin. Hence, one gets the following code to draw the seconds arm:

    g.setColor(Color.green);
    double angle = (90 - seconds*6.0)*Math.PI/180;
    g.drawLine(100, 100, (int)(100 + rSecHand*Math.cos(angle)),
    	     (int)(100 - rSecHand*Math.sin(angle)));
    
  6. Draw the minutes arm:
    g.setColor(Color.red);
    angle = (90 - minutes*6.0)*Math.PI/180;
    g.drawLine(100, 100, (int)(100 + rMinHand*Math.cos(angle)), 
    	     (int)(100 - rMinHand*Math.sin(angle)));
    
  7. Draw the hours arm:
    g.setColor(Color.blue);
          angle = (90 - (hours + minutes/60.0)*30.0)*Math.PI/180;
          g.drawLine(100, 100, (int)(100 + rHourHand*Math.cos(angle)),    
                               (int)(100 - rHourHand*Math.sin(angle)));
    
  8. Finally, draw the center "axis nail" for the arms:
    g.setColor(Color.black);
          g.fillOval(100 - 3, 100 - 3, 6, 6);
    

Now you are ready to compile the project. Make sure that it looks "nice".


Lab Task II: adding functionality to the clock

Getting current time from the system

  1. We use the class Calendar to obtain the current time. It is a part of the package java.util, so we add the following line to import this class:
    import java.util.Calendar;
  2. The class Calendar has method get() and predefined constants HOUR, MINUTE, and SECOND corresponding to the fields of the time instance. We use these constants to retrieve the time and add the following lines to the SwingApp constructor:
    Calendar cal = Calendar.getInstance();
    clock.hours = cal.get(Calendar.HOUR);
    clock.minutes =  cal.get(Calendar.MINUTE);
    clock.seconds = cal.get(Calendar.SECOND);
    
  3. Recompile your app. Make sure that it gets correct time from the system and sets the clock arms to the appropriate positions. At this point, your app only gets the current time from the system, which is used to set up the clock arms just once when the app is loaded. The arms do not yet move automatically.

Animating the clock

We use the class Timer of package javax.swing. The constructor of this class takes two parameters: the number of milliseconds between the timer events, and the class name to respond on the timer events. We make our app be the listener for the timer events and use the keyword this for the reference to the SwingApp class.

  1. Add the following two lines at the end of the SwingApp constructor:
    Timer timer = new Timer(1000, this);
    timer.start();
    

    The method start() starts the timer. From now on, the method actionPerformed() will be called every 1000 milliseconds (=1 second), the time interval between two consecutive clock updates.

  2. As soon as we use event processing, we need to import the corresponding package:
    import java.awt.event.*;
  3. Furthermore, implementing the ActionListener interface needs to use the corresponding keywords in the class Clock declaration:
    class SwingApp extends JFrame implements ActionListener
  4. Finally, we must add the actionPerformed() method. In this method, we just increment the seconds variable and check whether it reached the value 60. If this is the case, we reset seconds to 0 and add 1 to minutes. Maintaining the minutes variable is similar, but we take care on a possible update of the hours variable. Since these variables are defined in the DisplayPanel class, we use the clock. "dot" syntax to access them. After updating these variables, we call the repaint() method of class DisplayPanel to incorporate the changes.
    public void actionPerformed(ActionEvent e)
    {
       clock.seconds++;
       if (clock.seconds == 60)
       {
          clock.seconds = 0;
          clock.minutes++;
          if (clock.minutes == 60)
          {
             clock.minutes = 0;
             clock.hours++;
             if (clock.hours == 24)
                clock.hours = 0;
          }
       }
       clock.repaint();
    }
    
  5. Recompile your app and check how it works. All three arms should move now.
    Warning: the clock app shows time set up on your computer, which might differ from the actual time.

Lab Task III: enhancement of the clock app

Try to do as much as you can in the lab.

  1. Add necessary code to show all hours (1, 2, ..., 12).
  2. Add necessary code to show all minute bars and hour bars. The hour bars should be a bit longer than the minute ones.
  3. Add digital clock to be shown below the analog one. Use BorderLayout to set up the layput.