Component Class – Part Three

In the previous article you saw how to create a component in ActionScript  and how that mimics a component written in MXML:

MXML ActionScript
Root tag class extends
Metadata tag [Metadata above class definition]
Child component tags override createChildren(), using new operator and addChild() function.
Properties Set and Get functions; override commitProperties()
Events Use addEventHandler and specify an event argument to the handler function.

In this article we’ll look at how to make that circle of arrows rotate.

Download Files

This is a link to the same file download in the previous article; nothing has changed.

Arrow Class

In the first article the cycle arrows is a GIF. Easy to place using the Image component (either in MXML or in ActionScript). That would be enough, except we want to rotate the arrows as the user clicks on the link in the component.

So what’s wrong with that? Rotation in Flash is pretty easy, you just set the rotation property to an angle and the object rotates – about its (0,0) point. That’s the catch – in Flex a component has (0,0) as its upper-left corner. When you rotate a Flex component by changing its rotation property, it pivots on this corner – it does not rotate about its center.

              

There are ways around this using a translation matrix, but I think this alternative will prove educational and help you out when you have some awkward things to do in Flex.

Principle

Keep in mind that (0,0) is the upper-left corner of a Flex component and to make life very easy and simple in the Flex framework, the Arrow component is going to keep it this way. The difference is that inside of the Arrow component, the circle of arrows will appear and it will rotate and not the Arrow component itself.

Here’s the Arrow component in its entirety, but broken into sections. I think it will be easier to explain this way.

package com.adobe.examples.skins
{
import mx.core.UIComponent;
import flash.display.Graphics;
import flash.display.Shape;

public class Arrows extends UIComponent {

There really isn’t any existing Flex component to extend so the Arrow class extends UIComponent – the base class for every Flex component. UIComponent is what every Flex component inherits from.

createChildren

private var canvas:Shape;

/**
* createChildren (override)
*
* Creates the shape in which the arrows appear. This shape can then
* be rotated.
*/
override protected function createChildren():void
{
canvas = new Shape();

// after drawing the arrows below, I realized they were too big, but my
// calculations for the lines and curves were already figured out. So I
// just scaled the graphic a bit to make it look better.
canvas.scaleX = 0.6;
canvas.scaleY = 0.6;
addChild(canvas);
}

This component has a single child, a
flash.display.Shape , where the arrows will appear. This is the part that actually rotates. The Shape class is a very basic, lightweight Flash class for drawing. It has very little overhead and is ideal for this purpose.

measure


/**
* measure (override)
*
* Return the default width and height
*/
override protected function measure():void
{
measuredWidth = measuredMinWidth = 20;
measuredHeight = measuredMinHeight = 20;
}

So far you’ve seen createChildren() and commitProperties() – functions which you override to make your component. Here is another –
measure() . This function is critical to making components behave properly with the Flex framework’s layout manager. You don’t need to have measure() in the CycleSelectButton components because the HBox does this for you – a benefit of using a Container as a basis for your own components.

The measure function is called only when the Flex framework does not know how large the component should be. If you supply an explicit width and height to a component, measure() is never called because the layout manager knows how big it is. Keep this in mind and do not write anything in this function that is critical since it is not always called.

The measure() function’s job is to set the
measuredWidth and measuredHeight properties (and optionally, like here, the measuredMinWidth and measuredMinHeight properties). Sometimes measure() can be complex if you have lots of children to the component – you have to measure all of them and then figure out how large the overall component is.

In this case, I create the circle of arrows to occupy a 20×20 area. So that’s what I set measuredWidth and measuredHeight to.

updateDisplayList

      /**
* updateDisplayList (override)
*
* Position the canvas containing the arrows in the middle of the component. Then
* draw the arrows.
*/
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
super.updateDisplayList( unscaledWidth, unscaledHeight );

canvas.x = unscaledWidth/2;
canvas.y = unscaledHeight/2;

drawArrows( canvas.graphics );
}

The
updateDisplayList() is another function in the Flex framework called in the life cycle of a component. The updateDisplayList() function is perhaps the most fun – it is were you actually make things happens. In this case two things are done: the canvas Shape with the arrows is positioned and the arrows are drawn.

Remember that the arrows are being drawn within the canvas at (0,0) – so we need to place the canvas Shape where we want its (0,0) to be – and that’s in the middle of the component which is (unscaledWidth/2, unscaledHeight/2). If this is confusing, change it to (0,0) and see what happens.

I moved the arrow-drawing into a separate function to make it clearer (this function appears at the end of the class):

		/**
* drawArrows
*
* Draws the circle of arrows in the given graphic. The circle is centered
* at (0,0) to make it easy to rotate
*/
private function drawArrows( g:Graphics ) : void
{

g.clear();
g.lineStyle(0, 0x0000CC, 1);

g.moveTo(-10,0);
g.curveTo(-10,-10,0,-10);
g.curveTo(2.5,-11,8,-8);
g.moveTo(8,-8);
g.lineTo(6.5,-13);
g.moveTo(8,-8);
g.lineTo(2.5,-6);

g.moveTo(10,0);
g.curveTo(10,10,0,10);
g.curveTo(-2.5,11,-8,8);
g.moveTo(-8,8);
g.lineTo(-6.5,13);
g.moveTo(-8,8);
g.lineTo(-2.5,6);
}

The
flash.display.Graphics (g) is the Flash entity which places instructions into the Flash Player’s display list. Ultimately every component does this. The first thing is almost always clear() so you don’t add more graphics – you usually want to replace them. You can read more about the Drawing API in the Flex documentation. You can see that the arrows are a series of
lineTo and curveTo functions about (0,0).

The rotation Property

		/**
* rotation - apply rotation to the canvas shape, not to this component
*/
override public function set rotation(value:Number):void
{
canvas.rotation = value;
}
override public function get rotation():Number
{
return canvas.rotation;
}

 

Here’s a trick you may not expect: overriding a very basic property like rotation. If you were to do something like
arrows.rotation = 45 you would and should expect the component to rotate to 45 degrees. But if that happens, the component rotates about its upper-left corner. What we want to do is rotate the Shape instead. By overriding the component’s rotate property, we re-direct the value down to the Shape and prevent the component itself from being rotated.

Another benefit of overriding the property is that it will make sense to the developer who uses the component. Telling them that they can rotate the arrows by changing the rotation property is what they expect. Trying to explain they should not use rotation but instead use another set of functions is awkward.

Use in CycleSelectButtonV2

Now open the CycleSelectButtonV2 code and replace the Image component with an instance of the new Arrows component. The code provided in the download already does this; you use the Arrows component like anything else:

  • You have to create an instance of it using new Arrows().
  • You can add it to the CycleSelectButtonV2 component with addChild().
    		override protected function createChildren() : void
    {
    	arrows = new Arrows();
    arrows.width = 20;
    arrows.height= 20;
    addChild(arrows);
    
    linkButton = new LinkButton();
    addChild(linkButton);
    
    // add a listener for the click on the LinkButton.
    linkButton.addEventListener(MouseEvent.CLICK, handleClick);
    
    super.createChildren();
    }
  • You make it spin by changing its rotation property in the LinkButton click event handler:
    arrows.rotation = arrows.rotation + 45;

What’s Next

In the next article in this series we’ll write this component one more time, but completely from scratch by extending UIComponent.