Creating custom item renderers with the LayoutGroup container (Starling version)
The LayoutGroup
container is a simple Feathers component that holds children and provides the ability to specify a layout. This makes an ideal base for any custom component, but it's especially useful for custom item renderers in the List
, DataGrid
, Tree
and GroupedList
components.
Feathers includes several subclasses of LayoutGroup
that are designed specifically to help you get started creating custom item renderers (or "cell" renderers in a DataGrid
).
LayoutGroupListItemRenderer
can be used as an item renderer in aList
.LayoutGroupDataGridCellRenderer
can be used as a cell renderer in aDataGrid
.LayoutGroupTreeItemRenderer
can be used as an item renderer in aTree
.LayoutGroupGroupedListItemRenderer
can be used as an item renderer in aGroupedList
.LayoutGroupGroupedListHeaderOrFooterRenderer
can be used as either a header renderer or a footer renderer in aGroupedList
.
All of these classes implement the required functions from their respective interfaces, to save you time on bootstrapping code, and they provide a couple of useful functions that you can override the update the layout and the parse the data without worrying about the lowest level parts of the Feathers component architecture.
Below, we will look at how to create a simple custom item renderer by extending one of these classes. At the very end, the complete source code for a simple custom item renderer will be provided to offer a starting point for other custom item renderers.
These base classes for item renderers based on
LayoutGroup
provide the easiest way to build custom item renderers in Feathers. However, they have the risk of slightly lower performance. For a more advanced approach at a lower level in the Feathers architecture, please see Creating custom Item Renderers withFeathersControl
andIListItemRenderer
instead.
The Simplest Item Renderer
Let's implement a very simple item renderer. It will contain a Label
component to display some text and it will be possible to customize some padding around the edges.
When it's finished, we'll want to use it like this:
var list:List = new List();
list.itemRendererFactory = function():IListItemRenderer
{
var itemRenderer:CustomLayoutGroupItemRenderer = new CustomLayoutGroupItemRenderer();
itemRenderer.padding = 10;
return itemRenderer;
};
list.dataProvider = new ArrayCollection(
[
{ label: "One" },
{ label: "Two" },
{ label: "Three" },
{ label: "Four" },
{ label: "Five" },
]);
this.addChild(list);
Notice that we set a padding
property to adjust the layout. The item renderer will get the text for its Label
sub-component from the label
property of an item in the data provider.
We could go crazy and add background skins, icons, the ability to customize the which field from the item that the label text comes from, and many more things. We're going to keep it simple for now because this is just a basic example.
For this example, we're creating an item renderer for a List
component, but it will be virtually the exact same process to create an item renderer for a Tree
or GroupedList
component, or a header renderer or footer renderer for a GroupedList
component. You simply need to change the class that you extend.
Implementation Details
Let's start out with the basic framework for our custom item renderer. We want to subclass feathers.controls.renderers.LayoutGroupListItemRenderer
:
package
{
import feathers.controls.renderers.LayoutGroupListItemRenderer;
public class CustomLayoutGroupItemRenderer extends LayoutGroupListItemRenderer
{
public function CustomLayoutGroupItemRenderer()
{
}
}
}
This base class implements the IListItemRenderer
interface, so the data
, index
, and owner
properties that the List
component require on an item renderer are already there for us.
Adding Children
We want to display a Label
component, so let's add a member variable for it:
protected var _label:Label;
Next, we want to create a new instance and add it as a child. We need to override initialize()
function:
override protected function initialize():void
{
this._label = new Label();
this.addChild(this._label);
}
The initialize()
function is called once the very first time that the component is added to the stage. It's a good place to create sub-components and other children and possibly to do things like add event listeners that you don't intend to remove until the component is disposed. In general, it is better to use initialize()
for this sort of thing instead of the constructor.
For more information about the
initialize()
function and other parts of the Feathers architecture, see Anatomy of a Feathers Component.
Parsing the data
Next, we want to access the item renderer's data
property and display something in our Label
component. Override the convenient commitData()
function to do this:
override protected function commitData():void
{
if(this._data)
{
this._label.text = this._data.label;
}
else
{
this._label.text = null;
}
}
For this particular item renderer, we're requiring all items in the data provider to have a label
property that holds the text to display in the Label
component. If we were building a generic item renderer, ideally, we might like to make that field name customizable, like the labelField
property in DefaultListItemRenderer
. However, let's keep it simple for this example.
Don't forget to handle the case where the data property is null
. You don't want any runtime errors causing you trouble.
Adjusting the layout
Let's handle how the Label
sub-component will be positioned and sized within the item renderer. We generally want to use a fluid layout that can handle changes in the dimensions of the item renderer (which are ultimately controlled by the parent List
component). AnchorLayout
is ideal for this situation.
At the beginning of the initialize()
function, let's create our AnchorLayout
instance:
override protected function initialize():void
{
this.layout = new AnchorLayout();
Now, in order to have the AnchorLayout
control the Label
component's positioning and dimensions, we need to pass an AnchorLayoutData
instance to the Label
component. Let's do that next:
override protected function initialize():void
{
this.layout = new AnchorLayout();
var labelLayoutData:AnchorLayoutData = new AnchorLayoutData();
labelLayoutData.top = 0;
labelLayoutData.right = 0;
labelLayoutData.bottom = 0;
labelLayoutData.left = 0;
this._label = new Label();
this._label.layoutData = this._labelLayoutData;
We've constrained the Label
component to all four edges of the item renderer. If the item renderer's width grows or shrinks, the Label
component will be resized appropriately.
With that finished, we now have a fully working item renderer. However, we probably don't want the Label
component to fill the item renderer right up to the edges. We probably want a little space around the edge to allow the labels to breathe when they appear next to each other in the list. Let's add a padding
property to customize this spacing around the edges:
protected var _padding:Number = 0;
public function get padding():Number
{
return this._padding;
}
public function set padding(value:Number):void
{
if(this._padding == value)
{
return;
}
this._padding = value;
this.invalidate(INVALIDATION_FLAG_LAYOUT);
}
When we change a property that requires the component to change something about its appearance, we need to call the invalidate()
function. This will tell the component that it needs to update its appearance before the next time that Starling renders to the screen. We'll explain that constant, INVALIDATION_FLAG_LAYOUT
, in a moment.
For more information about the
invalidate()
function and other parts of the Feathers architecture, see Anatomy of a Feathers Component.
The base class offers a preLayout()
function that you can override to update layout properties on children before the layout code is run. We're going to update the Label
component's AnchorLayoutData
in this function:
override protected function preLayout():void
{
var labelLayoutData:AnchorLayoutData = AnchorLayoutData(this._label.layoutData);
labelLayoutData.top = this._padding;
labelLayoutData.right = this._padding;
labelLayoutData.bottom = this._padding;
labelLayoutData.left = this._padding;
}
If we had additional children in the item renderer, we'd adjust their layoutData
properties in this function too. With AnchorLayoutData
, we're not limited to constraining children to the edges of their parent container. We can also position those children relative to the center of the container, both horizontally and vertical. We can even position children relative to each other too! For complete details, see How to use AnchorLayout
in Feathers containers.
The base class also offers a postLayout()
function that can be overridden to update anything after the layout code has run. We won't be using that one here, though.
Source Code
The complete source code for the CustomLayoutGroupItemRenderer
class is included below:
package
{
import feathers.controls.Label;
import feathers.controls.renderers.LayoutGroupListItemRenderer;
import feathers.layout.AnchorLayout;
import feathers.layout.AnchorLayoutData;
public class CustomLayoutGroupItemRenderer extends LayoutGroupListItemRenderer
{
public function CustomLayoutGroupItemRenderer()
{
}
protected var _label:Label;
protected var _padding:Number = 0;
public function get padding():Number
{
return this._padding;
}
public function set padding(value:Number):void
{
if(this._padding == value)
{
return;
}
this._padding = value;
this.invalidate(INVALIDATION_FLAG_LAYOUT);
}
override protected function initialize():void
{
this.layout = new AnchorLayout();
var labelLayoutData:AnchorLayoutData = new AnchorLayoutData();
labelLayoutData.top = 0;
labelLayoutData.right = 0;
labelLayoutData.bottom = 0;
labelLayoutData.left = 0;
this._label = new Label();
this._label.layoutData = labelLayoutData;
this.addChild(this._label);
}
override protected function commitData():void
{
if(this._data && this._owner)
{
this._label.text = this._data.label;
}
else
{
this._label.text = null;
}
}
override protected function preLayout():void
{
var labelLayoutData:AnchorLayoutData = AnchorLayoutData(this._label.layoutData);
labelLayoutData.top = this._padding;
labelLayoutData.right = this._padding;
labelLayoutData.bottom = this._padding;
labelLayoutData.left = this._padding;
}
}
}
Next Steps
Looking to do more with your custom item renderer? Check out the Feathers Cookbook for "recipes" that show you how to implement typical features in custom item renderers and in other Feathers UI components.
Related Links
feathers.controls.renderers.LayoutGroupListItemRenderer
API Documentationfeathers.controls.renderers.LayoutGroupDataGridCellRenderer
API Documentationfeathers.controls.renderers.LayoutGroupTreeItemRenderer
API Documentationfeathers.controls.renderers.LayoutGroupGroupedListItemRenderer
API Documentationfeathers.controls.renderers.LayoutGroupGroupedListHeaderOrFooterRenderer
API Documentation