Learn how to create a simple virtual layout in Flex 4

This post goes over the basics of writing a very simple custom layout that supports virtualization in spark. It assumes that you have already built a custom layout that handles the real (non-virtual) case and are comfortable doing so.

The main difference between a virtual and non-virtual layout is how many renderers are created. A non-virtual layout creates a renderer for every item at startup, where virtual layouts only create renderers for items that are currently in view. This is a huge performance gain when you have dataProviders with thousands of items, but only a handful are shown at any given time. The spark List turns on virtual layout by default.

The layout we build here is called SimpleHorizontalLayout. As the name suggests, it is a very simplistic approach to something like the spark HorizontalLayout. It extends LayoutBase and lays out elements next to each other from left to right. It assumes that every element is the same size as the typicalLayoutElement (which is the renderer created for the typicalItem). If the typicalItem is not specified by the user then the first item in the dataProvider will be used by default.

Here is a rough outline of what to do when writing a virtual layout:

  • start with a layout that works in the real (non-virtual) case
  • override measure()
  • override updateDisplayList()
  • override scrollPositionChanged()

If you want your layout to support keyboard navigation in a spark List then you will also need to:

  • override getElementBoundsLeftOfScrollRect() etc.
  • override getElementBounds()
  • override getNavigationDestinationIndex()

1. Override measure()

In virtual layouts, the measure method should only rely on size data cached at updateDisplayList() time. Everything else (that’s everything the first time measure is called) has to be estimated. In this simple case we guess at the size by assuming that every renderer is the same size as the typicalLayoutElement.

2. Override updateDisplayList()

In the non-virtual case you are able to loop through every element in the target and lay them all out. This means that the target’s contentWidth/contentHeight is calculated exactly. But in the virtual case you don’t have this luxury, you know a couple of things: the scroll position, the size of the target, and the typicalLayoutElement. Given these things you will need to figure out which indices are actually in view and layout only those elements. I like to follow this procedure when tackling this step:

1. Figure out what we’re given
2. Given the scroll position figure out the first index that should be in view
3. Figure out how many indices are in view
4. Figure out the last index in view
5. Figure out what coordinates to position the first index at
6. Position the renderer of each index that is in view using getVirtualElementAt()
7. Keep track of how many pixels visible renderers are hanging off the ends of the view

The important thing about a virtual layout updateDisplayList() method is that it can only retrieve elements with target.getVirtualElementAt() and that it should only retrieve the elements it needs. Also getVirtualElementAt() should never be called outside of the updateDisplayList() in the layout. Currently only DataGroup supports virtual layouts. It does so by allocating the renderers requested by layout.updateDisplayList(), freeing the rest.

3. Override scrollPositionChanged()

When the scroll position changes it might mean that new items are coming into view so we need to invalidate the display list if that is the case. You’ll notice that this sample has an optimization that only invalidates the display list if the scroll position has changed enough to expose new renderers. This is done by taking advantage of the Flash scrollRect and knowing that the renderers that are only partially in view can be valuable in saving extra invalidations.

4. Override getElementBoundsLeftOfScrollRect() etc.

This method returns the bounds of the first layout element that either spans or is to the left of the scrollRect’s left edge. This gets called when someone clicks on the track or the arrows of the List’s scrollbar.

5. Override getElementBounds()

This method returns a Rectangle that corresponds to the layout bounds of the item at the given index. A virtual layout overrides this method to compute a potentially approximate value for elements that are not in view. This works together with getNavigationDestinationIndex() to support keyboard navigation.

6. Override getNavigationDestinationIndex()

This method returns the index that the List should scroll to given a specific keyboard navigation command like HOME, END, LEFT, RIGHT, etc.

Example

You can find the code equivalent to this procedure in the source of SimpleHorizontalLayout.as in the following example:

View Source

Note: Item indices don’t necessarily have to be contiguous for a virtual layout. See SDK-24052 for an example.

In this example the virtual layout perfectly approximates it’s non-virtual counterpart. This is because we assumed that every element was equal in size and assumed that size was equal to the typicalLayoutElement’s size. In spark the HorizontalLayout/VerticalLayout classes are more robust in being able to handle renderers of different sizes as long as a reasonable typicalLayoutElement/typicalItem is provided. This is made possible by caching renderer sizes it encounters in order to make as accurate a prediction as possible (see spark.layouts.supportClasses.LinearLayoutVector for the cache implementation). These layouts also provide useful properties like gap, horizontalAlign, verticalAlign, etc. that are beyond the scope of this post.

More Information

Here are some ways to learn more about virtual layout in Flex 4:

  • Read the Spark Virtualization spec.
  • Read the ASDoc for spark.layouts.supportClasses.LayoutBase.
  • Look at the code in HorizontalLayout/VerticalLayout.
  • Keep an eye on Hans and Evtim‘s blogs.
  •  

    Note: This sample requires Flex SDK 4.0.0.13729 or higher. You can get the latest SDK builds from opensource.adobe.com.

16 thoughts on “Learn how to create a simple virtual layout in Flex 4”

  1. This is awesome! I have been looking for an article like this, since first learning about the new Spark architecture. Thanks for putting this up.

  2. @anon – Thanks for the feedback. This post was designed to use the simplest custom layout I could think of in order to demonstrate the process of taking a non-virtual custom layout and making it virtual.

    You should be able to start with a non-virtual CoverFlow layout and after following the steps laid out above make it capable of handling the virtual case. If you find that this process doesn’t work for you then please leave a comment with what didn’t work so I can update this post. I would also look at Evtim’s blog for some 3D layout tips.

  3. @Silva Developer – I’m not sure I understand your question. If you look at the code in updateDisplayList you will see this line that is responsible for positioning the element:

    element.setLayoutBoundsPosition(x, y);

  4. Hi Steven,

    Thanks for your answer.

    I was tried set the x and y of each custom UIComponent datagroup dataprovider, but doesn’t work… Keep positioning in the same way, horizontally.

    If you’ve some tip, will welcome.

    Tranks!

  5. @Silva Developer – Setting x and y on the elements directly will only work on a layout that respects them (like BasicLayout). In this layout you need to use element.setLayoutBoundPosition(x,y) to position the element rather than setting x and y directly.

  6. Hi Steven,

    Thanks for the tutorial. What if I need to use a tile layout? I am guessing I need to alter some code, but not exactly sure where.

    Thanks for the insight.

  7. @Scott – Flex 4 does provide a TileLayout class which supports virtual layout. Take a look at that class to see if it does what you need.

  8. Hi Steven,

    This is a great tutorial. Thanks for posting.

    Is it necessary to put the SimpleHorizontalLayout inside a List in order to utilize virtual layout? In other words does the outside element have to have the useVirtualLayout property or could we just set it directly on SimpleHorizontalLayout?

    I am trying to make a gallery app to flip through a user’s photos, very similar to the iPhone Photos app (but for AIR for Android). I can’t figure out how to overrride the List’s free-form scrolling to change it to image by image. I was going to try extending ListBase as my next effort, but I thought you might have a better suggestion?

    Thanks.

  9. @Brian – The List component forces useVirtualLayout=”true” on its layout by default. The only way to turn it off is to set useVirtualLayout=”false” directly on the List. The comments in this post explain a little more.

    There are lower level spark components that support virtual layout.

    The lowest level is the spark DataGroup (which List uses in its skin). That component doesn’t expose a useVirtualLayout property and in that case you set useVirtualLayout on the layout directly and it is respected.

    DataGroup has no concept of chrome (like Group) so it can’t be skinned. If you want to also support spark skinning then look into using the SkinnableDataContainer component (which is what List extends from).

    On the mobile front it sounds like you want item snapping and page based scrolling. This is something listed as a B+ feature in the List spec.

    I believe implementing this by extending List will be tricky. What I would probably recommend is to use DataGroup and setup your own scrolling logic that then sets the verticalScrollPosition/horizontalScrollPosition directly and animates/snaps appropriately.

  10. Steven,

    Great article. Thanks.

    Quick question, I’m using virtual-layout and I’ve a “List” with custom IR with two states: normal, open. How could I avoid recycling the IRs that are in “open” state? So, basically if user open an IR then I don’t wanna recycle that IR when user scrolls down the list.

  11. @Rozen – You can’t selectively recycle item renderers, but what I would recommend doing is having the state be a field in the data object and have your ItemRenderer override public function set data() and set the renderer’s state based on the value of that field. That way whether the renderer is open or closed is determined completely from within the data item (not a specific renderer instance).

Comments are closed.