Recommended: Sing it, brah! 5 fabulous songs for developers
JW's Top 5
Java Tutor is my platform for teaching about Java 7+ and JavaFX 2.0+, mainly via programming projects.
Figure 1: Visit http://tutortutor.ca/cgi-bin/makepage.cgi?/books/bj7 to learn about Beginning Java 7.
Beginning Java 7 consists of twelve chapters and four appendixes (the appendixes are not included in the print version). Now that I’ve finished the print version, I’m resuming this blog by excerpting the Clock application from this book’s Appendix D (Applications Gallery).
| Note: I recently refactored Java Tutor to focus only on Java 7 and successor versions of the Java standard edition, as well as on JavaFX 2.0 and successor versions. I eliminated my four-part series on Android Gingerbread because Android isn’t relevant to my new focus. I also revisited each post to ensure that code and content are compliant with JDK 7u2. Regarding my two-part Rebooting JavaFX series, I made sure that code and content are compliant with JDK 7u2 and JavaFX 2.0.2. |
Clock application that reveals one way to simulate an analog clock.
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Stroke;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Calendar;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.Timer;
class Clock extends JFrame
{
Clock()
{
super("Clock");
setDefaultCloseOperation(EXIT_ON_CLOSE);
setContentPane(new SwingCanvas());
pack();
setVisible(true);
}
public static void main(String[] args)
{
Runnable r = new Runnable()
{
@Override
public void run()
{
new Clock();
}
};
EventQueue.invokeLater(r);
}
}
class SwingCanvas extends JComponent
{
private final static int BORDER_WIDTH = 10;
private BasicStroke bs;
private Calendar cal;
private Dimension d;
private Font font;
private int width;
SwingCanvas()
{
bs = new BasicStroke(2.5f);
cal = Calendar.getInstance();
d = new Dimension(300, 300);
font = new Font("Arial", Font.BOLD, 14);
width = d.width-2*BORDER_WIDTH;
ActionListener al;
al = new ActionListener()
{
@Override
public void actionPerformed(ActionEvent ae)
{
cal.setTimeInMillis(System.currentTimeMillis());
repaint();
}
};
new Timer(50, al).start();
}
@Override
public Dimension getPreferredSize()
{
return d;
}
@Override
public void paint(Graphics g)
{
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.translate(BORDER_WIDTH, BORDER_WIDTH);
Stroke stroke = g2d.getStroke();
// Paint oval.
g2d.setStroke(bs);
g2d.drawOval(0, 0, width, width);
g2d.setStroke(stroke);
// Paint tick marks.
int tickEnd = width/2;
for (int second = 0; second < 60; second++)
{
int tickStart;
if (second%5 == 0)
tickStart = tickEnd-26; // long tick
else
tickStart = tickEnd-13; // short tick
drawSegment(g2d, second/60.0, tickStart, tickEnd);
}
// Paint hour labels.
g2d.setFont(font);
for (int hour = 1; hour <= 12; hour++)
{
double angle = (hour-3)*2*Math.PI/12;
int x = (int) (Math.cos(angle)*(width/2-35))+width/2-5;
int y = (int) (Math.sin(angle)*(width/2-35))+width/2+5;
g2d.drawString(""+hour, x, y);
}
// Paint hands.
int hour = cal.get(Calendar.HOUR);
int min = cal.get(Calendar.MINUTE);
int sec = cal.get(Calendar.SECOND);
int ms = cal.get(Calendar.MILLISECOND);
int secHandMaxRad = width/2-5;
double fracSec = (sec+ms/1000.0)/60.0;
g2d.setColor(Color.RED);
drawSegment(g2d, fracSec, 0, secHandMaxRad);
g2d.setColor(Color.BLACK);
int minHandMaxRad = width/3-10;
double fracMin = (min+fracSec)/60.0;
drawSegment(g2d, fracMin, 0, minHandMaxRad);
int hrHandMaxRad = width/4;
drawSegment(g2d, (hour+fracMin)/12.0, 0, hrHandMaxRad);
}
private void drawSegment(Graphics2D g2d, double fraction, int start, int end)
{
double angle = fraction*Math.PI*2-Math.PI/2.0;
double _cos = Math.cos(angle);
double _sin = Math.sin(angle);
double minx = width/2+_cos*start;
double miny = width/2+_sin*start;
double maxx = width/2+_cos*end;
double maxy = width/2+_sin*end;
g2d.drawLine((int) minx, (int) miny, (int) maxx, (int) maxy);
}
}
Listing 1: Simulating an analog clock
Listing 1 consists of Clock and SwingCanvas classes. Clock serves as this application’s main class and trivially constructs a GUI consisting of a single SwingCanvas instance. SwingCanvas describes a specialized Swing component for displaying a clock face with moving hands, and also contains animation logic for animating this clock to constantly display the current time.
SwingCanvas first declares the following fields:
BORDER_WIDTH: an integer constant that describes the number of pixels to reserve as a border around the clock face. The border gives the clock a nicer appearance.
bs: A java.awt.BasicStroke instance that determines the thickness of the clock face’s round outline. (Chapter 7 introduces BasicStroke.)
cal: A java.util.Calendar instance that’s used to extract the current time in terms of hour, minute, second, and millisecond. (Appendix C introduces Calendar.)
d: A java.awt.Dimension instance that stores the SwingCanvas component’s preferred size. (Chapter 7 introduces Dimension.)
font: A java.awt.Font instance that identifies the font used to display the clock face’s text. (Chapter 7 introduces Font.)
width: An integer that specifies the width of the SwingCanvas component’s drawing area less its border.
These fields are followed by a SwingCanvas() constructor that initializes them to appropriate values. This constructor then creates and starts a Swing timer that fires action events after an initial 50-millisecond delay and at 50-millisecond intervals. Each time an event is fired, the registered action listener initializes the Calendar instance to the current time via cal.setTimeInMillis(System.currentTimeMillis());, and then invokes repaint() to redraw the clock face to reflect this value.
The overriding getPreferredSize() method returns the component’s preferred size and the overriding paint() method redraws the clock face.
paint() first performs initial graphics setup by activating antialiasing (see Chapter 7), which ensures that the clock face looks smooth (no jagged edges), and by translating the drawing area’s (0, 0) origin to (BORDER_WIDTH, BORDER_WIDTH), to facilitate drawing.
Next, paint() saves the current stroke attribute value, installs the custom stroke that was created in the constructor, draws the clock face’s circular outline using the custom stroke, and resets the stroke attribute to its previously saved value.
At this point, paint() draws sixty tick marks (line segments) around the clock face. Each tick mark is a drawn segment of the invisible radius line (at a particular angle) that extends from the circle’s center to its circumference. Its start and end limits are stored in local variables tickStart and tickEnd.
paint() continues by installing the previously created font, and then drawing the hour labels around the clock face and closer to the circle’s center than the tick marks. For each hour from 1 to 12, it first calculates an angle (in radians) for where the hour’s textual label should appear. Because angles are relative to the 3 o’clock position, hour label 0 would appear at this position if it existed, hour label 1 would appear at the 4 o’clock position, and so on unless 3 was subtracted from the hour value before calculating the angle.
After the angle has been calculated, the java.lang.Math class’s double cos(double angle) and double sin(double angle) class methods are invoked to calculate the (x, y) location on the circumference of a unit circle that corresponds to the angle. These values are multiplied by the circle’s radius less a value so that they appear inside the tick marks perimeter; an offset is added to the result so that the location is relative to the center of the component drawing area and not the translated origin. A small offset is subtracted from the x location and added to the y location so that the label is roughly centered below its long tick mark.
paint()’s final task is to paint the second, minute, and hour hands. It first invokes Calendar methods to obtain the current hour, minute, second, and millisecond values – the millisecond value enables the hands to move smoothly in an analog fashion rather than jump forward in a digital fashion.
Each hand is drawn starting from the circle’s center, but not all the way to the circumference. For example, the second hand is drawn to 5 pixels short of the circumference (width/2-5). To distinguish the second hand from the minute and hour hands, the second hand is drawn in red whereas the other hands are drawn in black (the hour hand is shorter to distinguish it from the minute hand).
The paint() method relies on the drawSegment() method to draw tick marks and clock hands. The first argument passed to this method is the graphics context, the second argument is a fraction between 0.0 and 1.0 that represents the amount of clockwise angular movement from the 3 o’clock position, and the third and fourth arguments define the start and end of the visible portion of the radius to display.
This method first calculates the angle via expression fraction*Math.PI*2-Math.PI/2.0. It subtracts Math.PI/2 from fraction*Math.PI*2 to make the angle relative to the 12 o’clock position.
It next calculates the angle’s cosine and sine, and uses these values with the start and end arguments and the width field variable’s value to calculate the starting and ending positions of the line segment to draw.
Compile Listing 1 (javac Clock.java) and run this application (java Clock). You should see output similar to that shown in Figure 2.
Figure 2: Clock presents the current time in analog format.
I won’t be posting regularly, but will post at least once or twice a month.
Clock does not have this capability. Modify Clock to indicate whether the specified time reflects AM or PM (e.g., 3AM or 5PM).
You can download this post's code and answers here. Code was developed and tested with JDK 7u2 on a Windows XP SP3 platform.
* * *
I welcome your input to this blog, and will write about relevant topics that you suggest. While waiting for the next blog post, check out my TutorTutor website to learn more about Java and other computer technologies (and that's just the beginning).