Home >
If you haven't been keeping up with the latest on Flex 4, this is something that you will certainly want to look into. One of the new features of the Flex 4/Spark component architecture is that you can customize the layout of a container without altering the container itself. All you need to do is define a custom layout.
Containers in the Flex 4/Spark architecture do not control their own layout. Instead, each has a layout property that determines how the children are laid out onscreen. You can have a single Group container, and give it a vertical layout, horizontal layout, or a tile layout, depending how you've built it. It is as simple as:
But what is really cool is that you are not just limited to the default layouts that are defined in the framework. You can easily customize the BaseLayout class to implement your own custom layout logic. Below is a simple example showing how to do a layout of clockwise placement of components around the origin. Just click on the button in the lower left to add more buttons to the layout.
Below is the code for the main application file. As you can see, it's pretty simple. This is just a DataGroup, which is something like a repeater, that contains a series of buttons. The layout of the container is based on a custom layout implementation. On creationComplete, the data provider for the DataGroup is populated, which creates button instances in the layout.
You can see that the layout for the DataGroup instance is controlled by the CircularLayout class, which is shown below. In this class, it just loops over the children of the datagroup object and places them clockwise in a circle. I looked at the source of the VerticalLayout class to figure out how it worked, and from there I was able to start building my own layout implementation.
And the item renderer used in this example is really basic. It's just an ItemRenderer instance that contains a Button... plain, simple, and easy to see what's going on.
This example was created using the 4.0.0.7052 nighly build available from
http://opensource.adobe.com/wiki/display/flexsdk/Download+Flex+4
___________________________________
Andrew Trice
Principal Architect
Cynergy Systems
http://www.cynergysystems.com
Containers in the Flex 4/Spark architecture do not control their own layout. Instead, each has a layout property that determines how the children are laid out onscreen. You can have a single Group container, and give it a vertical layout, horizontal layout, or a tile layout, depending how you've built it. It is as simple as:
<s:layout>
<s:VerticalLayout/>
</s:layout>
Below is the code for the main application file. As you can see, it's pretty simple. This is just a DataGroup, which is something like a repeater, that contains a series of buttons. The layout of the container is based on a custom layout implementation. On creationComplete, the data provider for the DataGroup is populated, which creates button instances in the layout.
<?xml version="1.0" encoding="utf-8"?>
<s:Application
xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/halo"
xmlns:local="*">
<s:creationComplete>
<![CDATA[
for ( var x:int = 0; x < 10; x++ ){
dataSource.addItem( dataSource.length );
}
]]>
</s:creationComplete>
<fx:Declarations>
<mx:ArrayCollection id="dataSource" />
</fx:Declarations>
<s:DataGroup
width="100%" height="100%"
dataProvider="{ dataSource }"
itemRenderer="SimpleItemRenderer">
<s:layout>
<local:CircularLayout />
</s:layout>
</s:DataGroup>
<mx:Button
left="5" bottom="5"
label="Click to Add a Button"
click="dataSource.addItem( dataSource.length )" />
</s:Application>
package
{
import mx.core.ILayoutElement;
import spark.layouts.supportClasses.LayoutBase;
public class CircularLayout extends LayoutBase
{
override public function updateDisplayList(w:Number, h:Number):void
{
super.updateDisplayList(w, h);
if (!target)
return;
var layoutElement:ILayoutElement;
var count:uint = target.numElements;
var angle : Number = 360/count;
var radius : Number = Math.min( target.width/2, target.height/2 ) - 25;
var w2 : Number = target.width/2;
var h2 : Number = target.height/2;
for (var i:int = 0; i < count; i++)
{
layoutElement = target.getElementAt(i);
if (!layoutElement || !layoutElement.includeInLayout)
continue;
var radAngle : Number = (angle * i) * (Math.PI / 180) ;
var _x : Number = Math.sin( radAngle );
var _y : Number = - Math.cos( radAngle );
layoutElement.setLayoutBoundsPosition( w2 + (_x * radius) - 25, h2 + (_y * radius) - 10 );
}
}
}
}
<s:ItemRenderer
xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/halo">
<s:states>
<s:State name="normal"/>
<s:State name="hovered"/>
</s:states>
<s:layout>
<s:BasicLayout/>
</s:layout>
<s:Button label="{ data }" baseColor.hovered="#FF0000" />
</s:ItemRenderer>
___________________________________
Andrew Trice
Principal Architect
Cynergy Systems
http://www.cynergysystems.com




Facebook Application Development
So sexy!!
Nice sample. Very clean and easy. Thanks.
I have been looking at these examples for a while, but where do I get those libraries? They dont seem to come with Gumbo
If you add 300 buttons you get a really nice shape :)
ERIC - thats so funny, I did just that before scrolling down to see your comment. nice to know i'm not the only saddo out there ;-)
glenn
tinylion
Neat example, thanks. Can't wait to start playing with 4.
Thanks for this demo, but the real problem comes when you want to change the Z value of the element, as you better than me know, GroupBase doesn't have x, y, z, properties, you change x and y properties trough the setLayoutBoundsPosition method that only accepts the x and y property.. Could you help me out with this?
Thanks for the blog!:D
I think you can either use setChildIndex or swapChildren on the GroupBase to swap depths, however I haven't tested this out myself.
@Andrew Trice
but how can i change the child index if it's not a Child but instead it is and Element? If I try setChildIndex I get an error and this would only work fine if i just want to change the zindex of the child, but the true is that i want to create one 3D layout!
You can't set the depth of objects that are not on the display list, nor would you want to. You only should manage depths on objects that are children of the GroupBase, not elements of the GroupBase.
Elements that are accessed by getElementAt are not necessarily children on the display list, they are just elements that can be laid out within the GroupBase. Children are accessed by getChildAt, and are actually on the display list.
This way, you can build layouts that support data virtualization, and scale to support very large data providers, without decreasing application performance.
If you are building a 3d layout, i would loop over the elements to determine what should be placed on screen, then loop over the children and set depths appropriately. It's hard to give any more detail without knowing what you are doing.
I see!! I really have to learn better the framework! So let's see, with this code:
override public function updateDisplayList(w:Number, h:Number):void
{
var child:DisplayObject;
var count:uint = target.numElements;
for (var i:int = 0; i {
if(target.getChildAt(i) is DisplayObject)
{
child = target.getChildAt(i);
trace(child);
}
}
I have access to the display objects that are children of the group? is it correct?
Be careful, there is target.numElements, which is the number of elements that could be renderered (same as the # of items in your data provider), and there is target.numChildren, which is all children that are on the display list. To loop over all children, I think you want var count:uint = target.numChildren;
Take a look at the source of the HorizontalLayout class inside of the Flex framework to get an idea how Adobe is doing it internally.
ups, you're right -.-' i was copying / pasting the code from flash builder and didn't changed that! I'm getting something now and this is what I've being looking for! Thanks a lot for the help!!Seriously, thanks!!
@Chucky, @Andrew -- Andrew is right that in flex 4, you use the xxxElement APIs instead of xxxChild APIs. However, layouts shouldn't mess with the element order of the layout elements...that's under the control of the client code, and shouldn't be modified by the layout.
However, to do advanced layout, there a few additional features you might want to take advantage of:
1) every element has a depth property, which controls the layering of the elements on screen, independent of their order within their parent. Some layouts will modify this property to get proper layering.
2) while most layouts simply use the new layout functions to set the bounds and size of the element post transform, more advanced layouts will want complete control over the transformation of the elements. In this case, you can call setLayoutBounds, passing false for the postTransformation flag, and then call setLayoutMatrix to have finegrained control over 2D and 3D rotation, scale, and position.
Go to town...great to see everyone reallying digging in to this stuff.
Ely Greenfield.
Adobe, Flex SDK.
Thanks for the clarification Ely. I thought I remember seeing a "setDepth" function in earlier nightly builds. Since I didn't see it in the current codebase, I missed the "depth" property and incorrectly assumed it was back to setChildIndex. I'll have to dig into these some more myself.
Ely's comments suggests there should be a "depth" property on ILayoutElements ?. I cant find that anywhere.
Is there some other way to change their z-order?
Great example!
I have one question though... how to invoke updateDisplayList method in the custom layout?
I've a group of buttons that get placed in a SkinnableDataContainer with the help of a custom layout class as follows:
dataProvider="{appModel.myButtons}"
width="{DIAPLAY_WIDTH}" height="{BUTTON_HEIGHT+4}"
skinClass="view.skins.ContainerSkin"
itemRenderer="view.renderer.MyButtonRenderer"
click="startPointAreaClickHandler(event)"
mouseMove="areaMouseMoveHandler(event)"
mouseUp="areaMouseUpHandler(event)"
rollOut="areaMouseOutHandler(event)"
>
myButtons is an ArrayCollection of VOs and VO has x and y coordinates as fields. Values of x and y determine the location of the button. When I add an object to myButtons, the application behaves as expected (adds a button in buttonsContainer at specified x and y coordinate). But when I just change values of x and y of an existing button, the buttons don't get repositioned. I have tried calling invalidateDisplayList on buttonsContainer but that doesn't trigger updateDisplayList method in the custom layout ButtonsLayout.
How do I trigger updateDisplayList method in the custom layout so that the button positions get updated whenever an event changes values of x and y of a button?
Any help is very much appreciated.