Home  >  

Flex 4 & Custom Layouts

Author photo
AddThis Social Bookmark Button
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:

<s:layout>
	<s:VerticalLayout/>
</s:layout>
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.

<?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>
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.

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 );
            } 
       }
    }
}
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.

<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>
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

Read more from Andrew Trice. Andrew Trice's Atom feed

Comments

17 Comments

Bromley John said:

Nice sample. Very clean and easy. Thanks.

Raul Riera said:

I have been looking at these examples for a while, but where do I get those libraries? They dont seem to come with Gumbo

Erik said:

If you add 300 buttons you get a really nice shape :)

glenn said:

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.

Chucky said:

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

Andrew Trice said:

I think you can either use setChildIndex or swapChildren on the GroupBase to swap depths, however I haven't tested this out myself.

Chucky said:

@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!

Andrew Trice said:

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.

Chucky said:

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?

Andrew Trice said:

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.

Chucky said:

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.

Andrew Trice said:

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.

Anonymous said:

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?

Dilip said:

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.

Leave a comment


Tag Cloud

Question of the Week: Dream App

If you had an unlimited budget and unlimited resources what application would you build and why would you build it?

Answer

Latest Features

Recommended for You

@InsideRIA on Twitter

Archives

  • Or, visit our complete archive.  

About This Site

Welcome to the premiere community site for all things RIA sponsored by O'Reilly Media and Adobe Systems Incorporated.