Our analog clock is going to have 60 tick marks arranged in a circle, with three clock hands inside. Now, the program would run that much faster if we didn't have to draw all that every second. So, we're going to have two drawing buffers. At the very beginning, we call createFace()
to draw all those tick marks in one buffer (tickImage
). Then, every second, we copy that onto our primary drawing buffer (faceBuffer
), exchanging 60 drawing operations for one, draw the clock hands, and finally copy faceBuffer
onto the screen.
Here is the promised createFace()
method, which is called once in the lifetime of the program by init()
. Add this right before the getArcPoint()
method:
// Draw the tick marks in tickImage (called only once) private void createFace() { tickImage = createImage(size, size); Graphics g = tickImage.getGraphics(); // Erase the background g.setColor(backColor); g.fillRect(0, 0, size, size); // Draw the minute tick marks (first the 5-minute ones, then // the smaller ones in between) Point center; g.setColor(tickColor); for(int tick = 0; tick < 60; tick += 5) { center = getArcPoint(tick, halfSize - tickRadius); g.fill3DRect(center.x - tickRadius, center.y - tickRadius, tickSize, tickSize, true); } if(drawSmallTicks) { g.setColor(backColor); for(int tick = 1; tick < 60; tick++) { if(tick % 5 > 0) { center = getArcPoint(tick, halfSize - tickRadius); g.fill3DRect(center.x - 1, center.y - 1, 3, 3, false); } } } }
createFace
in DetailThe rather long createFace()
method draws the clock face without the handsoncein tickImage
. You should be familiar with the first few statements. We first create the drawing buffer tickImage
with createImage()
, making it the same size as the clock applet. Then we get a Graphics
object that's linked to it. After that, we erase the background by drawing a filled rectangle using the background color.
Next, we draw the tick marks that are at five-minute intervals:
Point center; g.setColor(tickColor); for(int tick = 0; tick < 60; tick += 5) { center = getArcPoint(tick, halfSize - tickRadius); g.fill3DRect(center.x - tickRadius, center.y - tickRadius, tickSize, tickSize, true); }
We define a Point
object called center
and set the drawing color to tickColor
. Then we draw the tick marks using a for
loop. This statement executes a body repeatedly until a condition is true. The syntax is:
for(initial-statement; boolean-expression; update-statement) loop-body
First, initial-statement
is executed. Then, if the Boolean expression is true
, loop-body
is executed followed by update-statement
. The expression is evaluated again and the body executed, repeatedly, until the expression is false
. Then the program resumes after the loop body.
So, take a look at our for
statement here:
for(int tick = 0; tick < 60; tick += 5) { ... }
In the initial-statement
part, we declare an integer called tick
and set it to 0. Then the boolean-expression
is evaluated. Obviously, tick
is less than 60, so loop-body
is executed (we'll get to that in a sec). Then, in the update-statement
part, tick
is incremented by 5. boolean-expression
is evaluated again, and since tick
is still less than 60, loop-body
is executed. This continues on, giving us the sequence tick
= 0, 5, 10, ... 55. After that, tick
is incremented to 60, boolean-expression
is false
, and the loop exits.
Inside the loop, we call getArcPoint()
to get the point at "clock angle" tick
and radius halfSize - tickRadius
, which puts the point right inside the edge of the clock, but leaving enough room for the tick mark.
Then we call fill3DRect()
to draw a 3D rectangle, using the current color (tickColor
):
g.fill3DRect(center.x - tickRadius, center.y - tickRadius, tickSize, tickSize, true);
This method takes five arguments: the coordinates of the upper-left corner, the width and height, and a boolean
value. If the last parameter is true
, the rectangle is drawn raised; if false
, it's lowered. Here, the tick mark is centered on (center.x
, center.y
) and is a raised square with sides of length tickSize
. Java automatically determines the shading of the beveled edges, so the box looks something like this: .
After this we draw the tick marks that are at one-minute intervals, if drawSmallTicks
is true
:
if(drawSmallTicks) { g.setColor(backColor); for(int tick = 1; tick < 60; tick++) { if(tick % 5 > 0) { center = getArcPoint(tick, halfSize - tickRadius); g.fill3DRect(center.x - 1, center.y - 1, 3, 3, false); } } }
We first change the drawing color to backColor
. Then comes the loop: we count from 1 to 59, incrementing tick
by one each time.
Inside the loop body, we check if tick
is a multiple of 5 (so we won't overwrite the larger tick marks) using the modulo operator (%
):
if(tick % 5 > 0)
The expression (a % b)
returns the remainder when a
is divided by b
. So, if tick
isn't a multiple of 5, tick % 5
will be greater than 0, and the code inside the if
statement will then be executed:
center = getArcPoint(tick, halfSize - tickRadius); g.fill3DRect(center.x - 1, center.y - 1, 3, 3, false);
We use getArcPoint()
to get the center of the tick mark again, and call fill3DRect()
. This time, it's a 3x3 square, and it's lowered (false
).
If you want a sneak peek right now at your handiwork, make one small change to the paint()
method:
public void paint(Graphics g) { g.drawImage(tickImage, 0, 0, null); }
Compile the applet and open ClockApplet.html in your Web browser. You should now see a ring of 60 tick marks. Try changing the size of the applet to something less than 100 pixelsthe smaller tick marks disappear. Be sure you change tickImage
in the paint()
method back to faceBuffer
before continuing on to the next step:
public void paint(Graphics g) { g.drawImage(faceBuffer, 0, 0, null); }