6 steps in creating custom UIComponent in Flex (Halo framework)

Flex documentation.
The code here is just for demonstration purpose.

1. Initialization and configuration. You call Constructor() of the UIComponent. The constructor must have zero required parameters. This is due to the fact that there is no way to carry them into MXML tags. Inside constructor, you usually don’t do much here. A few things to do is to initialize all the states(properties) and hard code event listeners. Properties must expect that parts haven’t been created yet.

Public function UIComponent()
{
super();
focusRect = false;
tabEnabled = (this is IFocusManagerComponent);
tabChildren = false;
addEventListener(Event.ADDED, addedHandler);
addEventListener(Event.REMOVED, removedHandler);
}

2. Attachment of static parts. You can override createChildren () to attach static parts. “Static” parts are supporting visual assets such as masks and borders, which are needed for all dynamically generated parts throughout the lifetime of your component (regardless of the instances and states of your class).

Override protected function createChildren():void
{
_mask = new Shape();
_border = new Shape();
addChild(_mask);
addChild(_border);
if(_content!=null)
_content.mask = mask;
}

createChildren() is invoked by initialize(), which is the public method and is invoked when you add your component (explicitly calling addChild()/addChildAt() or adding MXML tags in your application) to the display list. Inside initialize(), 5 main lifecycle actions occur:
a.    preinitialize event is dispatched
b.    createChildren() is called
c.    initialize event is dispatched

First full invalidation/validation pass occurs (childrenCreated() is called, which then calls invalidateProperties(); invalidateSize(); invalidateDisplayList();  These will cause the validation methods to be invoked in order: commitProperties();measure();updateDisplayList())
d.    creationComplete event is dispatched
This basically completes the first full pass of invalidation/validation cycle.
AddChildren() also automatically invalidate its parent.

Hold on: From now on, since our UIComponent has entered the stage (in the display list), Flex is there to watch out any request for update from your component. LayoutManager (SystemManager?) governs any invalidation by calling the component’s callLater() method, which is listening to FlexEvent.RENDER event handler and FlexEvent.ENTER_FRAME event handler. The rule of game is if any of the following invalidation methods (shown on the left column) gets run at execution time, LayoutManager/SystemManager(?) will eventually invoke the corresponding method you defined for your component (shown on the right column).

InvalidateProperties()->commitProperties()
InvalidateSize()->measure()
invalidateDisplayList()->updateDisplayList()

3. Attachment of dynamic parts. Dynamic or data driven parts are created in commitProperties() method. Commit values typically set using a property setter. It is invoked by the framework before measurement and layout. This is the place to add/remove children not required through the life of the entire component (as opposed to createChildren()).

There is a definite pattern that should be followed in order to avoid extra work. See Dirty flags and storage variables.

A lot of times, Flex does dynamic parts by data renderers/ editors through factory pattern. Note that we don’t create parts in updateDisplayList(). This is because Adding children to a component automatically invalidates its size and display.  Since commit properties runs before measure() and updateDisplayList() runs, adding children in commitProperties() won’t accidentally trigger _another_ validation pass, which would happen if you tried to create them in updateDisplayList().

override protected function commitProperties():void
{
if(_itemsDirty)
{
_itemsDirty = false;
for(i = numChildren-1;i>=0;i–)
{
removeChildAt(numChildren-1);
}
_itemIndexMap = new Dictionary(true);
_children = [];

for(var i:int = 0;i<_dataProvider.length;i++)
{
var t:TiltingPane = new TiltingPane();
_itemIndexMap[t] = i;
t.addEventListener(MouseEvent.CLICK,itemClickHandler,false,0,true);
t.styleName = this;
_children[i] = t;

var content:UIComponent = UIComponent(_itemRenderer.newInstance());

IDataRenderer(content).data = _dataProvider.getItemAt(i);

t.content = content;
addChildAt(t,0);
}
}

4. Calculates the default (“natural” or “suggested” or “desired”) size, and optionally the default minimum size, of the component. The parent has ultimate say on sizing its child. You do this in measure(). measure() is implicitly invoked when component children change size. (Don’t call measure() on your children because the framework will do it for you). Measurement occurs from the bottom up. Framework optimizes away unnecessary calls to measure() so don’t rely on measure().(?)

override protected function measure():void
{
super.measure();
if(_currTarget!=null&&this.contains(_currTarget))
{
this.measuredMinHeight = this.measuredHeight = _currTarget.getExplicitOrMeasuredHeight();
this.measuredMinWidth = this.measuredWidth = _currTarget.getExplicitOrMeasuredWidth();
}
}

Note that you can use the convenient methods getExplicitOrMeasuredWidth()/getExplicitOrMeasuredHeight() on a UIComponent to get its explicit or measured dimensions. A tip is you can give a static number for measuredWidth/Height and then if your parts show up, then you go back and figure out the right sizes for them.

5. Draws and lays out (size and position) parts, using the allocated width and height. You do this in updateDisplayList().
You must set size and position for each child.

If the component has no children, this method is where you would do programmatic drawing using methods on the component’s Graphics object such as graphics.drawRect().

If the component has children, and if the child is not a UIComponent, set the x, y, width and height properties; if the child is a UIComponent, you would call the move() and setActualSize() methods on it since these methods aggregate invalidation events. Again, use setActualSize() and getExplicitOrMeasuredWidth(), etc. on each individual child to get their measured size or explicitly set size:

t.setActualSize(t.getExplicitOrMeasuredWidth(), t.getExplicitOrMeasuredHeight());

Components may do programmatic drawing even if they have children. In doing either, you should use the component’s unscaledWidth and unscaledHeight as its bounds.

It is important to use unscaledWidth and unscaledHeight instead of the width and height properties.

protected function get unscaledHeight():Number
{
return height / Math.abs(scaleY);
}

@param unscaledWidth Specifies the width of the component, in pixels, in the component’s coordinates, regardless of the value of the scaleX property of the component.

override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
if(_content)
{
var contentWidth:Number = _content.getExplicitOrMeasuredWidth();
var contentHeight:Number= _content.getExplicitOrMeasuredHeight();
var centerX:Number = unscaledWidth/2;
var p:Number = (Math.abs(_actualAngle)/90);
p = Math.sqrt(p);
_horizontalScale = 1 – p;
if(_actualAngle >= 0)
{
_verticalShear = p * -kPerspective;
}
else
{
_verticalShear = p * kPerspective;
}
_verticalShearEffect = contentWidth/2 * _verticalShear;
_content.setActualSize(contentWidth,contentHeight);                perspectiveDistort(centerX,0,contentWidth,contentHeight);
var borderColor:Number = getStyle(“borderColor”);
var borderThickness:Number = getStyle(“borderThickness”);
var g:Graphics = _border.graphics;
g.clear();
if(!isNaN(borderColor) && !isNaN(borderThickness))
{
g.lineStyle(borderThickness,borderColor,1,false,LineScaleMode.NORMAL,CapsStyle.NONE,JointStyle.MITER);
drawPerspectiveFrame(g,centerX,0, contentWidth, contentHeight);
}
if(_reflectionBitmap == null)
{
createReflectionBitmap(_content);
}                positionReflectionBitmap(centerX,0,contentWidth,contentHeight);
}
}

6. Detachment from display list. Todo. Remove listeners, clean up references, etc.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s


%d bloggers like this: