Lists
Introduction
This tutorial goes over how to work with the List view. It shows how to create static lists, dynamic lists, virtualized lists, flow lists and more. It demonstrates how to bind collections of data to the List view and how to handle common operations such as item selection, adding and updating. There are a number of other views that can present dynamic list data such as the ComboBox and TabPanel but most of the information in this tutorial will be applicable to those views as well.
Static List
If you want to present a static list of items, i.e. list that don’t change during runtime, you can use either a Group or a List view. Group view is useful if you only want to snug items together horizontally or vertically. The List view provides additional functionality such as selection logic, scrolling and the option to have items wrap as they reach the boundaries of the list.
MyStaticList.xml
<MyStaticList>
<List BackgroundColor="White">
<ListItem Size="100;30">
<Label Text="Item 1" AutoSize="True" Alignment="Left"
Margin="10,0,0,0" />
</ListItem>
<ListItem Size="100;30">
<Label Text="Item 2" AutoSize="True" Alignment="Left"
Margin="10,0,0,0" />
</ListItem>
<ListItem Size="100;30">
<Label Text="Item 3" AutoSize="True" Alignment="Left"
Margin="10,0,0,0" />
<CheckBox Alignment="Right" />
</ListItem>
</List>
</MyStaticList>
The items are arranged vertically by default and the size of the list will automatically adjust to the items. If you don’t specify the size of individual list items they will adjust their size to their content by default.
Dynamic List
To display a data collection in a dynamic list we first need to define the data model. The easiest way to do this is by using schemas to generate the necessary classes.
Schema.txt
= Fruit
string Name
Sprite Icon
+ Fruit (Name, Icon)
"Apple", RainbowSquare
"Watermelon", RainbowSquare
"Orange", RainbowSquare
"Strawberry", RainbowSquare
"Grape", RainbowSquare
"Cherry", RainbowSquare
"Mango", RainbowSquare
"Nectarine", RainbowSquare
The schema above generates a global bindable collection Fruits populated with data. Presenting the fruits:
FruitList.xml
<FruitList>
<List Items="{fruit in @Fruits}">
<Label Text="{fruit.Name}" />
</List>
</FruitList>
Three important things are going on here:
-
The first thing we do is to bind the Fruits collection to the list view:
<List Items="{fruit in @Fruits}">
fruit is the name we use to refer to the items in the list (you can use any name you want) and @Fruits refers to our data model, the @ symbol specifies that it refers to our data model and not a local property in the FruitList view.
-
We define the item template which in this case consist of a single Label that will automatically be wrapped by a ListItem view by the framework.
-
Lastly we bind the item property Name to the label:
<Label Text="{fruit.Name}" />
If the Fruits collection is updated anywhere the list will be automatically updated as well. The below example shows a more advanced item template:
<List Items="{fruit in @Fruits}" BackgroundColor="White">
<ListItem Width="150">
<Image Sprite="{fruit.Icon}" Alignment="Left" Offset="10,0,0,0" />
<Label Text="{fruit.Name}" AutoSize="True" Alignment="Left"
Margin="40,0,0,0" />
</ListItem>
</List>
List Operations
Any changes to the list are done through the Fruits collection. Let’s add some buttons that demonstrates common list operations:
FruitList.xml
<FruitList SelectedFruit="t:Fruit">
<Group Orientation="Horizontal">
<Group Spacing="5">
<Button Text="Add" Width="100" Click="AddFruit" />
<Button Text="Remove" Width="100" Click="RemoveFruit" />
<Button Text="Update" Width="100" Click="UpdateSelectedFruit" />
<Button Text="Update All" Width="100" Click="UpdateAllFruits" />
</Group>
<List Items="{fruit in @Fruits}" Margin="10,0,0,0"
BackgroundColor="White" SelectedItem="{SelectedFruit}">
<Label Text="{fruit.Name}" Margin="10,0,0,0" />
</List>
</Group>
</FruitList>
We’ve added a local dependency property called SelectedFruit that is bound to the SelectedItem in the list. We also created some buttons and click handlers to manipulate the list:
FruitList.cs
namespace Delight
{
public partial class FruitList
{
public void AddFruit()
{
Models.Fruits.Add(new Fruit { Name = "New Fruit" });
}
public void RemoveFruit()
{
if (SelectedFruit == null)
return;
Models.Fruits.Remove(SelectedFruit);
}
public void UpdateSelectedFruit()
{
if (SelectedFruit == null)
return;
SelectedFruit.Name += "*";
}
public void UpdateAllFruits()
{
foreach (var fruit in Models.Fruits)
{
fruit.Name += "*";
}
}
}
}
In code the global fruits collection is accessed through Models.Fruits, which can be manipulated from any part of your game and the changes will automatically propagate to any lists that it’s bound to.
A few more list operations that can be useful:
// scroll to item
Models.Fruits.ScrollTo(fruit);
// select item
Models.Fruits.Select(fruit);
// replace list
Models.Fruits.Replace(newFruitList);
Scrollable List
Scrollable lists have a defined viewport size and allows the user to scroll through its content.
<List Items="{fruit in @Fruits}" IsScrollable="True"
Width="150" Height="100" BackgroundColor="White">
<ListItem Height="30">
<Label Text="{fruit.Name}" />
</ListItem>
</List>
To create a scrollable list do the following:
- Set
IsScrollable="True"
on the list. - Set
Width
andHeight
on the list to define the viewport size.
There are a number of properties that are interesting when working with scrollable lists. The below example shows some of them.
<List Items="{fruit in @Fruits}" IsScrollable="True"
Width="150" Height="100" BackgroundColor="White"
ScrollableRegionContentAlignment="Top"
DisableInteractionScrollDelta="1"
HasInertia="False"
VerticalScrollbarVisibility="Auto"
VerticalScrollbarBackgroundColor="#c3c3c3"
VerticalScrollbarHandleColor="#9f9f9f"
VerticalScrollbarBarColor="#c3c3c3"
VerticalScrollbarWidth="6"
VerticalScrollbarBarMargin="0,0,2,0"
VerticalScrollbarOffset="0,0,0,0">
<ListItem Height="30">
<Label Text="{fruit.Name}" />
</ListItem>
</List>
Flow List
In a flow list items are arranged horizontally or vertically and wrap around to the next line when reaching the bounds of the list.
<List Overflow="Wrap" Items="{fruit in @Fruits}" Spacing="5" Width="460"
Orientation="Horizontal">
<ListItem Width="150" Height="150" BackgroundColor="Lavender">
<Label Text="{fruit.Name}" AutoSize="True" />
</ListItem>
</List>
To create the horizontally flowing list we do the following:
- Set
Overflow="Wrap"
which makes the items wrap when they reach the edge of the bounds. - Set
Orientation="Horizontal"
to make the items flow horizontally. - Set
Width
so the list has a defined horizontal size.
To make the list flow vertically you can set orientation to Vertical and specify the Height of the list.
Virtualized List
Virtualized lists only creates objects for the items visible in the viewport which greatly improves performance of lists that are large. To create a virtualized list simply set IsVirtualized=”True”.
<List Items="{fruit in @Fruits}" IsVirtualized="True"
Width="150" Height="100" BackgroundColor="White">
<ListItem Height="30">
<Label Text="{fruit.Name}" />
</ListItem>
</List>
To create the virtualized list we do the following:
- Set
IsVirtualized="True"
on the list. - Set
Width
andHeight
on the list to define the viewport size. - Set
Height
on the list item to define the item size (see section below on how to create dynamically sized virtualized items).
Virtualized lists always have a defined size and are always scrollable. Items need to have a pre-defined size.
Virtualized Lists with Differently Sized Items
The list items in a virtualized list need to have a pre-defined size in order to calculate their position in the list (before they are created). You may have a virtualized list that features differently sized items, e.g. a chat window where each chat entry size depends on the length of the text. To do this you can specify the VirtualItemGetter
method on the list:
FruitList.xml
<List Items="{fruit in @Fruits}" IsVirtualized="True"
Width="150" Height="100" BackgroundColor="White"
VirtualItemGetter="GetVirtualFruitItem">
And implement it in code-behind:
FruitList.cs
namespace Delight
{
public partial class FruitList
{
public VirtualItem GetVirtualFruitItem(Fruit fruit)
{
// calculate dynamic height based on fruit data
int height = fruit.Size;
return VirtualItem.FromHeight(height);
}
}
}
GetVirtualFruitItem() takes the item data as a parameter and returns a virtual item that has a pre-defined size that you can calculate based on the item data.
Multiple Item Templates
A list may have multiple item templates.
FruitList.xml
<List Items="{fruit in @Fruits}" TemplateSelector="FruitItemTemplateSelector"
Width="150" BackgroundColor="White">
<ListItem Id="FruitA" Height="30">
<Label Text="{fruit.Name}" />
</ListItem>
<ListItem Id="FruitB" Height="30">
<Image Sprite="{fruit.Icon}" Alignment="Right" Offset="0,0,10,0" />
<Label Text="{fruit.Name}" FontColor="Blue" />
</ListItem>
</List>
To create a list with multiple templates we do the following:
- Specify multiple
<ListItem>
’s each with a unique ID set. These are the different templates we’ll use. - Specify the
TemplateSelector
method on the list and implement it in code-behind.
FruitList.cs
namespace Delight
{
public partial class FruitList
{
public string FruitItemTemplateSelector(Fruit fruit)
{
switch (fruit.Name)
{
case "Watermelon":
case "Apple":
case "Mango":
case "Cherry":
default:
return "FruitA"; // use the template "FruitA"
case "Orange":
case "Strawberry":
case "Grape":
case "Nectarine":
return "FruitB"; // use the template "FruitB"
}
}
}
}
FruitItemTemplateSelector() takes the item data as a parameter and returns the ID of the template to be used to display the item.
List Paging
Paging allows you to specify how many list items that should be shown at a time and allow for navigation between pages in the list.
<List Items="{fruit in @Fruits}" IsPaged="True" PageSize="3"
Width="150" Height="90" BackgroundColor="White">
<ListItem Height="30">
<Label Text="{fruit.Name}" />
</ListItem>
</List>
To create the paged list we do the following:
- Set
IsPaged="True"
on the list. - Set
PageSize
to specify the number of items per page.
The paged list shows next/previous navigation buttons by default. You can also show page navigation buttons to allow jumping between pages. The buttons to be shown are controlled through the ShowNavigationButtons property.
Custom Page Navigation Buttons
The page navigation buttons can be customized by adding NavigationButton templates to the list view. The below example is an excerpt from the LevelSelectExample view:
<List Items="{level in @Levels}" Overflow="Wrap" Orientation="Horizontal" Width="470" Height="310" Spacing="10"
Offset="0,170,0,0" DeselectAfterSelect="True" Alignment="Top" IsPaged="True" PageSize="6"
ShowNavigationButtons="All"
PageNavigationGroupOffset="0,50,0,0"
PageNavigationGroupSpacing="5">
<ListItem Size="150,150" BackgroundColor="White" BackgroundSprite="LevelSelectItemBg">
<Label Text="{level.Index:0}" FontSize="50" AutoSize="True" Font="AveriaSansLibre-Bold SDF"
FontColor="#ecce2d" Offset="0,0,0,30" />
<Image Sprite="{level.Stars}" PreserveAspect="True" Width="110" Offset="0,20,0,0" />
</ListItem>
<NavigationButton BackgroundSprite="LevelSelectRightArrow" BackgroundColor="#bbbbbb" Highlighted-BackgroundColor="White"
Pressed-BackgroundColor="#bbbbbb"
Width="50" Height="77" Offset="110,0,0,10" />
<NavigationButton BackgroundSprite="LevelSelectPageButton" Pressed-BackgroundSprite="LevelSelectPageButtonPressed" BackgroundColor="#bbbbbb" Highlighted-BackgroundColor="White"
Pressed-BackgroundColor="#bbbbbb" NavigationType="Page"
Width="50" Height="50" DisplayLabel="False" />
</List>
It provides two NavigationButton templates one for the Next/Previous arrows and one for the Page buttons by setting NavigationType=”Page”. The spacing and offset of the page buttons are also specified through the properties PageNavigationGroupOffset and PageNavigationGroupSpacing on the List view.