Saturnboy
 11.4

Programmatic Skinning in Flex 4 — an FXG Clock

, , ,

I tried to push my Flex 4 skinning skills to the limit, and the result is a fancy clock skin.

clock
(click to see it in action)

The fancy clock skin overlays a very simple clock component. The Clock component is quite underwhelming, but here it is in all of its abbreviated glory:

public class Clock extends SkinnableComponent {
    public var secAngle:Number = 0.0;
    public var minAngle:Number = 0.0;
    public var hourAngle:Number = 0.0;
    private var _timer:Timer;
    private var _tween:GTween;
 
    public function Clock() {
        ...
        _tween = new GTween(this, 2, ...);
        _timer = new Timer(2000,30);
        _timer.addEventListener(TimerEvent.TIMER, tickHandler);
    }
 
    private function tickHandler(e:TimerEvent):void {
        var t:Date = new Date();
 
        var h:Number = t.getHours();
        var m:Number = t.getMinutes() + h * 60;
        var s:Number = t.getSeconds() + m * 60;
        var ms:Number = t.getMilliseconds();
 
        _tween.proxy.secAngle = -90 + s * 6 + ms * 0.006;
        _tween.proxy.minAngle = -90 + s * 0.1;
        _tween.proxy.hourAngle = -90 + (s / 120);
    }
    ...
}

At the core of the Clock component is a single timer. When the timer fires, it uses the current date to compute the angle of the hour hand, minute hand, and second hand. The actual motion of the hands is managed via the awesome proxy functionality in Grant Skinner’s GTween library.

All the interesting FXG action takes place in the skin, so let’s check it out:

<?xml version="1.0" encoding="utf-8"?>
<s:Skin
        xmlns:fx="http://ns.adobe.com/mxml/2009"
        xmlns:s="library://ns.adobe.com/flex/spark"
        creationComplete="complete()">
 
    <fx:Metadata>
        [HostComponent("components.Clock")]
    </fx:Metadata>
 
    <fx:Script>
        <![CDATA[
            private function complete():void {
                ...lots of programmatic FXG here...
            }
        ]]>
    </fx:Script>
 
    ...lots of vanilla FXG here...
 
    <s:Path rotation="{hostComponent.minAngle}" ...
    <s:Path rotation="{hostComponent.hourAngle}" ...
    <s:Path rotation="{hostComponent.secAngle}" ...
</s:Skin>

Getting data to the skin is done via the standard binding to hostComponent. There is no need for the more complicated partAdded() and partRemoved() mechanism, because the Clock component is not intended for general use, unlike my previous components: Terrific TabBar, SuperTextInput, and Drawer.

The skin has a big block of programmatic FXG skinning, done in actionscript code inside the complete() handler which is fired by the creationComplete event. It also has a big block of vanilla MXML declarative FXG skinning, which, most importantly, includes the clock hands and the previously mentioned binding to hostComponent.

By its nature, a clock has a lot of identical elements that are positioned in a circle. The obvious attack is to put the repeated elements in a loop, or set of loops, and go the programmatic route to construct the skin. For example, the long thin ticks along the outer ring are constructed with a double loop. Here’s the relevant snippet of code from inside the complete() handler:

for each (var i:int in [0,30,60,90,120,150,180,210,240,270,300,330]) {
    for each (var j:int in [6,12,18,24]) {
        var p:Path = new Path();
        p.data = "M 174,-0.5 L 184,-0.5 184,0.5 174,0.5 Z";
        p.x = 250;
        p.y = 250;
        p.rotation = i + j;
        p.fill = gray;
        this.addElementAt(p, 1);
    }
}

A new Path element is created and its data property is set. Our tick mark is 10px long, but only 1px wide. With each iteration, a tick mark is created and rotated into place. There are two important tricks in this code that are worth pointing out. First, the rotation origin is at (0,0) for each element, so we must construct our path from -0.5 to 0.5 for it to rotate correctly. Second, we must use addElementAt() to place each element into the proper layer of the drawing because the programmatic FXG code runs after all the vanilla (aka non-programmatic) FXG has been drawn.

Conclusion

No one in their right mind would ever build a clock skin like this. To do this the right way, you’d definitely want to use an image or two to construct the skin. The only reason I went the programmatic FXG skinning route was to verify to myself that it could be done. So, yes it can be done, and I enjoyed the trip.

Files

Comments

11.4.2010

1

Beautiful AND clever!

Nikos

1.28.2011

2

fantastic mate, looks so awesome

Sandy

4.2.2011

3

Beautiful, elegant and inspiring.

4.11.2011

4

Hello there,
I’m currently working on a Java API for Flex and I came across this post and found your clock the best I’ve ever seen, so I decided to to write a Java API for it and add it to the collection of Components my library will support. You can find a demo here :
http://www.gwt4air.appspot.com/#mx.extended.Clock

That application is entirely written in Java using the library I’ll release soon. I hope it’s ok for you to use your component and I’m looking forward for your feedback.

Cheers,
Alain

Eric

5.27.2011

5

This was very useful to me. Great demonstration. I do have a question though. Do you know how you might handle skin state changes programmaticly? Where did you find documentation on how to do skins with actionscript?

5.27.2011

6

@Eric: If you want to write pure AS3 skins, I’d suggest looking at all the Flex mobile skinning stuff that’s coming out now with FB 4.5 (in Flex mobile MXML skins are not allowed/recommended so you need to go the pure AS3 route).

Take a look here and here for some mobile skinning info.

Sohil

8.28.2011

7

Awesomeness!!!!

Robin

8.31.2011

8

Hey Justin,

You could use SkinParts for the angles instead of binding to hostComponent. Let that compiler work for you, and keep your skins even more dumb =)

Read more about SkinPart metadata.

8.31.2011

9

@Robin: you are correct. I did a lot of work with SkinParts in some of my previous posts: here, here, and here.

11.19.2011

10

what did you use to make get your paths coords you used in the fxg code?

11.19.2011

11

@Nikos: I just wrote ‘em down and adjusted until I liked the output. Sometimes I use graph paper, but this time the only complicated part was the clock hands..and they weren’t all that hard to just draw out.

11.21.2011

12

@justin, thx, you mean you manually coded the path list?
How come you always start from p1.y = 250 and how do you get the transformation to be at the origin of the clock hands for all the lines?

11.21.2011

13

ah I think I get it, the path is relative to p1 coords

p1.data = "M 180,-3 L 184,-3 184,3 180,3 Z";

11.21.2011

14

i wonder why they reversed the x and y for path data with the usual coord system

11.21.2011

15

@justin, sorry if I’m asking too many questions, can you explain this code a bit more, I don’t get it:

//multiply by 6 to get basic sec angle, then add up to
//5.994 degrees (999 * 0.006) to account for milliseconds
_tween.proxy.secAngle = -90 + s * 6 + ms * 0.006;

11.21.2011

16

@Nikos: It’s just math, don’t be afraid.

In 60s the second hand goes around one time, which is 360 degrees. So 1s is 60/360 = 6 degs / second. Or 0.006 degs / millisecond.

The -90 makes the hand point up at 0s (because I originally drew it pointing right).

For example, what’s the angle at 10s? -90 + 6 * 10 = -30 deg. And what’s the angle at 10s and 500ms (aka 10.5s)? -90 + 6 * 10 + 0.006 * 500 = -27 deg.

11.24.2011

17

ah I understand, clever coding.

I find that if you pause the clock and restart after 1- seconds the tween goes from the laststop point to the new time, would it not be better to jump to the current time?

11.24.2011

18

@Robin,

I’m having a hard time getting the skin part to work like so

in the skin:

[Bindable]
public var minAngle:Number;

in the component:

[SkinPart]
public var minAngle:Number = 0.0;

© 2014 saturnboy.com