A major headache in Flex 3 was to slightly change the layout of components. Say you would want to change a List display from vertical to horizontal. No chance. You would need to build up another component, HorizontalList, and have all the logic in there. With the Flex 4 approach to layouts, things are much more simple. Having a layout class that takes care of a component’s children offers support for extensibility. This way there is no difference between a normal vertical list and a horizontal list for example, in terms of data processing/children creations. The only difference is that the components have a different layout attached. This is achieved by specifying the layout property on the component.
The two base components that support layout inside the Flex 4 SDK are:
1. GroupBase, a class for displaying visual elements. Visual elements here means both UIComponent and GraphicElement, elements that can be laid out on the stage.
2. SkinnableDataContainer, a class for displaying visual elements based on data content. This is the base class for all list-based classes on Spark. This is where the journey begins for allowing a List component to display it’s children in different ways.
As the first component is plain simple, we’ll do a demo of customizing a DataContainer’s layout. Let’s suppose we need a menu in a circle fashion like in the image below.

It looks heavy stuff at a first sight, but all we need is a spark List and a CircleLayout class that computes the positions for each element on the list. The layout class takes in functionalities for measurement/sizing/placing elements on the component.
For positioning and sizing the elements on the stage, setLayoutBoundsPosition and setLayoutBoundsSize would be used. In this process, helper methods are called on the elements when needed to get the preferred size/position such as getPreferredBoundsWidth, getPreferredBoundsHeight. All methods are listed in mx.core.ILayoutElement interface.
For the CircleLayout that we want to build, we only need a radius specified that will help to determine the locations of the elements on a circle.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | /** * @private */ private var _radius:Number; /** * Circle radius at which the elements will be laid out. * @return */ public function get radius():Number { return _radius; } /** * @private * @param value */ public function set radius( value:Number ):void { _radius = value; target.invalidateSize(); target.invalidateDisplayList(); } |
The target is the actual component to which the layout is attached. Each time a property is updated that affects the size or display of the component after layout processing, invalidateSize and invalidateDisplayList methods should be called. These are familiar from the Flex 3 invalidation model.
These will translate on calling measure and updateDisplayList on the component which for a ILayoutElement will forward the chain to the layout’s measure and updateDisplayList.
For our example, measure will set the measured width and height on the component based on the radius and sizes for each children elements. As we are not changing anything on a children’s size, we let the components size themselves by using getPreferredBoundsWidth and getPreferredBoundsHeight. Here is the measure method in it’s complete form:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | /** * @private */ override public function measure():void { super.measure(); target.measuredWidth = radius * 2; target.measuredHeight = radius * 2; var numElements:int = target.numElements; var element:ILayoutElement; for( var i:int = 0; i < numElements; i++ ) { element = target.getElementAt( i ); element.setLayoutBoundsSize( element.getPreferredBoundsWidth(), element.getPreferredBoundsHeight() ); } } |
Now we actually need to place the elements at their specific positions inside updateDisplayList method.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | /** * @private */ override public function updateDisplayList( width:Number, height:Number ):void { super.updateDisplayList( width, height ); var stepAngle:Number = 360 / getNumLayoutElements(); var angle:Number; var numElements:uint = target.numElements; var element:ILayoutElement; var x:Number; var y:Number; var boundsWidth:Number; var boundsHeight:Number; for( var i:int = 0; i < numElements; i++ ) { element = target.getElementAt( i ); if( !element.includeInLayout ) continue; DisplayObject( element ).rotation = stepAngle * i; boundsWidth = element.getPreferredBoundsWidth( true ); boundsHeight = element.getPreferredBoundsHeight( true ); angle = Math.PI - stepAngle * i * Math.PI / 180; x = radius + Math.sin( Math.PI - angle ) * radius - boundsWidth / 2; y = radius + Math.sin( Math.PI / 2 - angle ) * radius - boundsHeight / 2; element.setLayoutBoundsPosition( x, y ); } } |
Basically what it does it calculates the angles, rotations and positions for each element (if included in layout). One goodie to mention here is the getPreferredBoundsWidth and getPreferredBoundsHeight parameter. This being true, the method will return the value post layout transform. In our case, after the element has been rotated. Other than that it’s pure math.
In a previous post i detailed how the states work in a spark component. Using that knowledge we will have two states for our demo application, one normal state and one selected state. For the selected state, the circle radius will be a little bigger and we’ll display something in the middle.
The list component looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | <s:List id="list" horizontalCenter="0" verticalCenter="0" selectionChanging="listSelectionChangingHandler()" skinClass="com.ayone.examples.circleMenu.ui.skins.ListSkin" clipAndEnableScrolling="false" labelField="name"> <s:layout> <layouts:CircleLayout id="circleLayout" radius.normal="57" radius.selected="200"/> </s:layout> <s:ArrayCollection> <fx:Object name="Menu1"/> <fx:Object name="Menu2"/> <fx:Object name="Menu3"/> <fx:Object name="Menu4"/> <fx:Object name="Menu5"/> </s:ArrayCollection> </s:List> |
Note the radius property on the layout is set based on the current state, normal or selected. This along with the invalidation mechanism will assure that the component will have its elements updated each time the state is changed. Using a custom skin to display the ellipse on the list and all is ready:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | <s:SparkSkin xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" minWidth="112" minHeight="112" alpha.disabled="0.5"> <s:states> <s:State name="normal" /> <s:State name="disabled" /> </s:states> <s:Ellipse top="1" bottom="1" left="1" right="1"> <s:stroke> <s:SolidColorStroke color="0x000000"/> </s:stroke> </s:Ellipse> <s:Scroller left="1" top="1" right="1" bottom="1" id="scroller" focusEnabled="false"> <s:DataGroup id="dataGroup" itemRenderer="spark.skins.default.DefaultItemRenderer"> <s:layout> <s:VerticalLayout gap="0" horizontalAlign="contentJustify" /> </s:layout> </s:DataGroup> </s:Scroller> </s:SparkSkin> |
And to add a little flavor to it, we’ll animate the transition between states with a MotionPath that basically updates the target property over a period of time.
1 2 3 4 5 6 7 | <s:transitions> <s:Transition> <s:Animate target="{ circleLayout }" duration="200" effectEnd="effectEndHandler(event)"> <s:SimpleMotionPath property="radius"/> </s:Animate> </s:Transition> </s:transitions> |
You can view the running application here and view the source here


1 Comment to 'Flex 4 Layout Capabilities'
August 25, 2009
“The two base components that support layout inside the Flex 4 SDK are:
1. GroupBase, …
2. SkinnableDataContainer…
”
I think SkinnableContainer also supports layout. As an example , you can specify a layout for the Application or Panel component which both inherit from SkinnableContainer
Leave a comment