A simple Clock component

« previous entry | next entry »
Nov. 29th, 2005 | 02:22 am

A simple clock component

A lot of people have asked for documentation on writing components in Flex 2. I thought it might be nice if I wrote a simple component and walked through the bare minimum steps required to get one working.

Here's the source for my simple Clock component:

// Clock.as

package
{

import flash.display.*;
import flash.util.*;

import mx.core.*;

/**
 *  A simple clock component.
 */
public class Clock extends UIComponent
{
  // The dial.
  private var dial:Shape;

  // The hours, minutes and seconds hands.
  private var hoursHand:Shape;
  private var minutesHand:Shape;
  private var secondsHand:Shape;

  // Timer for updating the current time.
  private var timer:Timer;

  // Override "createChildren" to create the dial and hands.
  override public function createChildren():Void
  {
    if (!dial) {
      dial = new Shape();
      addChild(dial);
    }

    if (!hoursHand) {
      hoursHand = new Shape();
      addChild(hoursHand);
    }

    if (!minutesHand) {
      minutesHand = new Shape();
      addChild(minutesHand);
    }

    if (!secondsHand) {
      secondsHand = new Shape();
      addChild(secondsHand);
    }

    super.createChildren();
  }

  // Override "measure" to measure the clock.  There's nothing to measure in
  // this simple clock, so we assume a preferred size of 100x100.
  override public function measure():Void
  {
    measuredWidth = 100;
    measuredHeight = 100;
  }

  // Override "updateDisplayList" to draw the dial and hands and position the
  // hands according to the current time.
  override public function updateDisplayList(width:Number,
      height:Number):Void
  {
    super.updateDisplayList(width, height);

    drawDial(width, height);
    drawHands(width, height);

    positionHands(new Date());

    if (!timer)
      createTimer();
  }

  private function drawDial(width:Number, height:Number):Void
  {
    var g:Graphics = dial.graphics;
    g.clear();
    g.beginFill(0);
    g.drawRect((width - 2) / 2, 0, 2, 4);
    g.drawRect(width - 4, (height - 2) / 2, 4, 2);
    g.drawRect((width - 2) / 2, height - 4, 2, 4);
    g.drawRect(0, (height - 2) / 2, 4, 2);
    g.drawCircle(width / 2, height / 2, 1);
    g.endFill();
  }

  private function drawHands(width:Number, height:Number):Void
  {
    hoursHand.x = minutesHand.x = secondsHand.x = width / 2;
    hoursHand.y = minutesHand.y = secondsHand.y = height / 2;

    var g:Graphics;
    
    g = hoursHand.graphics;
    g.clear();
    g.beginFill(0);
    g.drawRect(-1, 2, 2, (height / 2) - 30);
    g.endFill();

    g = minutesHand.graphics;
    g.clear();
    g.beginFill(0);
    g.drawRect(-0.5, 2, 1, (height / 2) - 10);
    g.endFill();

    g = secondsHand.graphics;
    g.clear();
    g.beginFill(0xFF0000);
    g.drawRect(-0.5, 2, 1, (height / 2) - 10);
    g.endFill();
  }

  private function positionHands(time:Date):Void
  {
    hoursHand.rotation = (time.getHours() % 12) / 12 * 360 + 180;
    minutesHand.rotation = time.getMinutes() / 60 * 360 + 180;
    secondsHand.rotation = time.getSeconds() / 60 * 360 + 180;
  }

  private function createTimer():Void
  {
    // Update current time every 1 second.
    timer = new Timer(1000);
    timer.addEventListener("timer", function(event:Event):Void {
      positionHands(new Date());
    });
    timer.start();
  }
}

}

/* vim: set et ts=2 sw=2 : */

(Sorry about my poor graphics skillz :) It's like every time I get some drawing-related work in my project, I try to fake a broken wrist or something until it's assigned to somebody else (apparently there are people who love it). Nevertheless, my objective here is to show you how to write a component from an API standpoint.)

Let's go through the steps:

  1. Extend UIComponent: Rule #1: a component must extend UIComponent or one of its subclasses.
  2. Override "createChildren": Child objects must be created in the createChildren method. In this example, we're creating the clock's dial and hands in createChildren.
  3. Override "measure": In the measure method, a component is supposed to compute the values of its measuredWidth and measuredHeight properties based on its contents. For example, a Button measures its preferred width and height based on its label and current font size. In the Clock component, we really have no basis for measuring the size; instead, we set the values to an arbitrary 100. If no width or height is specified for a Clock instance, it'll default to 100.
  4. Override "updateDisplayList": In updateDisplayList, a component must size and position child objects created in the createChildren method. The two arguments to this method specify the width and height available for drawing. In the Clock example, we first draw the dial and hands and then position the hands according to the current time.

It's really that simple!

<mx:Application xmlns:mx="http://www.macromedia.com/2005/mxml"
    xmlns="*" backgroundColor="white">
  <Clock />
</mx:Application>

This component is full of limitations. In fact, it's not even a component (a component should be customisable, programmable, etc.). Over the next few posts, I'm going to "evolve" this component to support new properties, events, styles and skins, to make it like a real world Flex 2 component. Stay tuned.

Link