Virtualization is an optimization for layout and rendering that reduces the footprint and startup time for containers with large numbers of items. This feature adds virtualization support to DataGroup, VerticalLayout, HorizontalLayout, and TileLayout. It's intended to make the cost of creating and rendering a DataGroup proportional to the number of visible items.
In most cases a virtualized DataGroup should behave exactly like one where ItemRenderers are created eagerly. There are a few exceptions:
A virtualized DataGroup will "recycle" item renderers when that's possible (more below).
Virtualized layouts do not respect layout elements' major axis percent size property. That's percentHeight for VerticalLayout and percentWidth for HorizontalLayout.
In the current implementation, virtualized layouts do not respect layout element's includeInLayout property and do not support null ILayoutElements.
Virtual layouts with small numbers of items whose sizes vary dramatically will respond poorly to interactive thumb scrolling. Responsiveness will improve as the variation in size decreases and/or the length of the list increases.
Developers will choose virtualized layout when the cost of creating or measuring a DataGroup is prohibitive because of the number of data items or the complexity of the data items' ItemRenderers.
Developers will request virtualized layout by setting the "useVirtualLayout" LayoutBase property to true. DataGroup measurement will be approximate, it will be based on the layout dimensions of the first data element's ItemRenderer or on the dimensions of a typical data element's ItemRenderer. The latter is specified with a new DataGroup "typicalItem" property.
The layout target property is a GroupBase and so the new virtualization methods and properties that layouts depend on are defined in GroupBase. Any GroupBase subclass could support virtualization, however presently only DataGroup does.
public class GroupBase extends UIComponent implements IViewport
{
/**
* Returns the element at the specified index.
*
* GroupBase subclasses that support virtualization will override this
* method to lazily create/recycle and validate the specified element.
*
* By default this method is the same as getElementAt().
*
* This method is intended to be called by layout classes that specify
* useVirtualLayout=true.
*
* @param
*/
public function getVirtualElementAt(index:int):void
}
Virtualization adds methods elementAdded() and elementRemoved(), and properties useVirtualLayout and typicalLayoutElement, to LayoutBase.
The new useVirtualLayout boolean property indicates that virtualization is desirable. Presently VerticalLayout and HorizontalLayout honor it. Support for TileLayout is planned.
The elementAdded() and elementRemoved() methods are called by the layout's GroupBase target when changes occur. Layouts that support virtualization override these methods to keep cached state in sync.
The following typicalLayoutElement discussion is written in terms of VerticalLayout to make the details concrete. It applies equally well to HorizontalLayout.
Specifying variableRowHeight="false" trivializes some of the VerticalLayout computations because it means that mapping between a Y value in the viewport's coordinate system and the index of the layout element at that location is just simple arithmetic based on the layout's rowHeight.
If variableRowHeight="false" and you don't specify a rowHeight, then the preferred height of the first layout element is used. Although simple, specifying an absolute rowHeight value at compile time is perilous because the visual properties that layout elements can depend on are legion, and they're unlikely to all be fixed at compile time. Using the height of the first layout element is safer, but it imparts a significance to the first element that may not be warranted.
When the layout is virtual, only the items "in view" are laid out and only the widths/heights of elements that have been in view are cached. We can do a reasonably good job of virtual vertical layout, even when variableRowHeight="true", if the variation in row heights isn't too dramatic and if we have a reasonable estimate of the typical element height and the maximum element width. Although we could ask developers to specify values for these "typical" element dimensions, doing so is perilous for the reasons outlined above.
The LayoutBase typicalLayoutElement property avoids the peril by making it possible to implicitly define the "typical" geometry of a layout element without requiring the typical element to be part of the actual layout. VerticalLayout uses the typicalLayoutElement's preferredHeight for rowHeight when variableRowHeight="false" and rowHeight isn't specified. When virtualization is called for, the typicalLayoutElement defines our estimate of the geometry of layoutElements that haven't been scrolled into view yet.
Setting DataGroup's typicalItem property sets the layout's typicalLayoutElement property. See the DataGroup section below.
public class LayoutBase extends OnDemandEventDispatcher
{
/**
* If true, the layout class will call the target's getVirtualElementAt()
* method to retrieve the layout elements that fall within the bounds
* of the target's scrollRect.
*
* @default false
*/
public function get useVirtualLayout():Boolean
public function set useVirtualLayout(value:Boolean):void
/**
* Used by layouts when fixed row/column sizes are requested but
* a specific size isn't specified.
*
* Used by useVirtualLayouts to estimate the size of layout elements
* that have not been scrolled into view.
*
* @default null
*/
public function get typicalLayoutElement():ILayoutElement
public function set typicalLayoutElement(value:ILayoutElement):void
/**
* This method must be called by the target after a layout element
* has been added and before the target's size and display list are
* validated.
*
* Layouts that cache per element state, like virtual layouts, can
* override this method to update their cache.
*
* If the target calls this method, it's only guaranteeing that a
* a layout element will exist at the specified index at
* <code>updateDisplayList()</code> time, for example a DataGroup
* with a virtual layout will call this method when a dataProvider
* item is added.
*
* By default, this method does nothing.
*
* @param index The index of the element that was added.
*/
public function elementAdded(index:int):void
/**
* This method must be called by the target after a layout element
* has been removed and before the target's size and display list are
* validated.
*
* Layouts that cache per element state, like virtual layouts, can
* override this method to update their cache.
*
* If the target calls this method, it's only guaranteeing that a
* a layout element will no longer exist at the specified index at
* <code>updateDisplayList()</code> time, for example a DataGroup
* with a virtual layout will call this method when a dataProvider
* item is added.
*
* By default, this method does nothing.
*
* @param index The index of the element that was added.
*/
public function elementRemoved(index:int):void
}
The DataGroup typicalItem property is used to create a typicalLayoutElement for the DataGroup's layout.
public class DataGroup extends GroupBase
{
/**
* Layouts use the preferred size of the corresponding ILayoutElement
* when fixed row/column sizes are requested but a specific
* rowHeight or columnWidth isn't provided.
*
* Similarly virtual layouts use this item to define the size
* of layout elements that have not been scrolled into view.
*
* Setting this property sets the typicalLayoutElement property
* of the layout.
*
* @default null
*/
public function get typicalItem():Object
public function set typicalItem(value:Object):void
}
DataGroup recycles item renderers (IRs) when its layout specifies useVirtualLayout="true", an itemRenderer is specified, and an itemRendererFunction is not specified. These restrictions are intended to guarantee that the IR factory always produces IRs that implement IDataRenderer and are of the same type.
When an IR is scrolled out of view its data property set set to null, and the IR is set aside on an internal free list. Later on the DataGroup may use that same IR again - "recycled" - by simply setting its data property before its laid out. Recycling improves scrolling performance by avoiding the cost of creating and installing new IRs.
Recycling imposes some additional constraints on IRs which can be satisfied by using a 'set data' override to configure the IR, rather than binding. Here's an example of a DataGroup who's very simple IR is defined this way:
<Scroller>
<DataGroup typicalItem="Item 9">
<layout>
<HorizontalLayout id="vl" requestedColumnCount="2" useVirtualLayout="true"/>
</layout>
<dataProvider>
<ArrayCollection>
<String>Item 0</String>
<String>Item 1</String>
<String>Item 2</String>
<String>Item 3</String>
</ArrayCollection>
</dataProvider>
<itemRenderer>
<Component>
<ItemRenderer>
<Script>
<![CDATA[
override public function set data(value:Object):void
{
super.data = value;
if (!value)
return;
label.text = value.toString();
}
]]>
</Script>
<states>
<State name="normal"/>
<State name="hovered"/>
<State name="selected"/>
</states>
<Rect left="0" right="0" top="0" bottom="0">
<fill>
<SolidColor color="0xCEDBEF" color.hovered="0xA8C6EE" color.selected="0xCCCCCC" />
</fill>
</Rect>
<Label id="label" />
</ItemRenderer>
</Component>
</itemRenderer>
</DataGroup>
</Scroller>
In this example we've opted not to clear the IR label's text when the the data property is set to null, to avoid the cost of the machinations triggered by setting the Label text property. Note that the IR does not bind to its data property. Configuring the IR by binding is relatively expensive and if the source data properties aren't Bindable, the bindings will not be updated when the IR is recycled.
Virtualization support for the (Planned) StackLayout class.
Include examples of how this feature would be used in practice, from MXML and ActionScript code, or from the command line, or from other mechanisms as appropriate
This feature has been implemented.
Virutalization support depends heavily on the group, viewport, and layout infrastructure.
It's likely that Data Effects will have a big impact on this feature.
Virtualization should decrease the startup time and footprint cost of DataGroups with large numbers of items and/or complex ItemRenderers. Thumb scrolling performance will be diminished.
Enumerate open issues needing resolution, along with recommended solutions, if any.
None at this time.
Yes
Wiki: Flex 4
Wiki: Spark DataGroup
Wiki: Spark Horizontal and Vertical Layout
Wiki: Spark SkinnableDataContainer