List View
Using a ListView control inside NativeScript app requires some special attention due to the complexity of the NativeScript implementation of the component, with custom item templates, bindings and so on.
The NativeScript modules provides a custom component which simplifies the way native ListView is used.
const listViewModule = require("tns-core-modules/ui/list-view");
import { ItemEventData, ListView } from "tns-core-modules/ui/list-view";
Note: Using the
ListView
component inside aScrollView
orScrollView
inside theListView
's items can lead to a poor user interface performance and can reflect the user experience. For avoiding those issues, we should specify the height explicitly for the ListView in the scenario when the ListView is nested inScrollView
and theScrollView
's height - when the component is used inside theListView
. Example 1 (ListView
inScrollView
):<ScrollView> <StackLayout> <ListView height="150" items=""> <ListView.itemTemplate> <!-- ....... --> </ListView.itemTemplate> </ListView> </StackLayout> </ScrollView>
Example 2 (
ScrollView
inListView
):<ListView items=""> <ListView.itemTemplate> <StackLayout class="list-group-item"> <ScrollView height="150" > <!-- ....... --> </ScrollView> </StackLayout> </ListView.itemTemplate> </ListView>
Basics
The example demonstrates how to set a ListView
component in XML page and how to bind its items property to a collection in the view model. In the sample is shown how to define custom UI while using ListView's itemTemplate
.
<ListView items="{{ myTitles }}"
itemTap="onItemTap"
loaded="{{ onListViewLoaded }}"
separatorColor="orangered" rowHeight="50"
class="list-group" id="listView" row="2">
<ListView.itemTemplate>
<!-- The item template can only have a single root view container (e.g. GriLayout, StackLayout, etc.) -->
<StackLayout class="list-group-item">
<Label text="{{ title || 'Downloading...' }}" textWrap="true" class="title" />
</StackLayout>
</ListView.itemTemplate>
</ListView>
const fromObject = require("tns-core-modules/data/observable").fromObject;
function onNavigatingTo(args) {
const page = args.object;
const vm = fromObject({
// Setting the listview binding source
myTitles: [
{ title: "The Da Vinci Code" },
{ title: "Harry Potter and the Chamber of Secrets" },
{ title: "The Alchemist" },
{ title: "The Godfather" },
{ title: "Goodnight Moon" },
{ title: "The Hobbit" }
]
});
page.bindingContext = vm;
}
exports.onNavigatingTo = onNavigatingTo;
function onListViewLoaded(args) {
const listView = args.object;
}
exports.onListViewLoaded = onListViewLoaded;
function onItemTap(args) {
const index = args.index;
console.log(`Second ListView item tap ${index}`);
}
exports.onItemTap = onItemTap;
import { EventData, fromObject } from "tns-core-modules/data/observable";
import { ListView, ItemEventData } from "tns-core-modules/ui/list-view";
import { Page } from "tns-core-modules/ui/page";
export function onNavigatingTo(args: EventData) {
const page = <Page>args.object;
const vm = fromObject({
// Setting the listview binding source
myTitles: [
{ title: "The Da Vinci Code" },
{ title: "Harry Potter and the Chamber of Secrets" },
{ title: "The Alchemist" },
{ title: "The Godfather" },
{ title: "Goodnight Moon" },
{ title: "The Hobbit" }
]
});
page.bindingContext = vm;
}
export function onListViewLoaded(args: EventData) {
const listView = <ListView>args.object;
}
export function onItemTap(args: ItemEventData) {
const index = args.index;
console.log(`Second ListView item tap ${index}`);
}
Note: The ListView's item template can contain only a single root view container.
In the example above, the items source property (myTitles
) is an array and its members are not observable objects. This means that adding or removing array member won't trigger a property change. To update the UI, you will need to explicitly refresh the listview after an item is added or removed while using the refresh
method.
const listView = page.getViewById("listView");
page.bindingContext.myTitles.push({ title: "Game of Thrones" });
// Manually trigger the update so that the new color is shown.
listView.refresh();
const listView = page.getViewById("listView");
page.bindingContext.myTitles.push({ title: "Game of Thrones" });
// Manually trigger the update so that the new color is shown.
listView.refresh();
Tip: Instead of manually triggering the UI update with the help of ListView's
refresh
method, NativeScript provides theObservableArray
. Using anObservableArray
for your listview's items surce will make its members an observable objects and adding/removing an array item will automatically update the UI.const ObservableArray = require("tns-core-modules/data/observable-array").ObservableArray; // Change the items source from Array to the NativeScript's ObservableArray const titlesArray = new ObservableArray([ { title: "The Da Vinci Code" }, { title: "Harry Potter and the Chamber of Secrets" }, { title: "The Alchemist" }, { title: "The Godfather" }, { title: "Goodnight Moon" }, { title: "The Hobbit" } ]);
Code Behind
Creating a ListView
programmatically and setting up its items
and UI for each item.
const container = page.getViewById("container");
const listView = new listViewModule.ListView();
listView.className = "list-group";
listView.items = listViewArray;
// The itemLoading event is used to create the UI for each item that is shown in the ListView.
listView.on(listViewModule.ListView.itemLoadingEvent, (args) => {
if (!args.view) {
// Create label if it is not already created.
args.view = new Label();
args.view.className = "list-group-item";
}
(args.view).text = listViewArray.getItem(args.index).title;
});
listView.on(listViewModule.ListView.itemTapEvent, (args) => {
const tappedItemIndex = args.index;
const tappedItemView = args.view;
dialogs.alert(`Index: ${tappedItemIndex} View: ${tappedItemView}`)
.then(() => {
console.log("Dialog closed!");
});
});
container.addChild(listView);
const listView = new ListView();
listView.className = "list-group";
listView.items = listViewArray;
// The itemLoading event is used to create the UI for each item that is shown in the ListView.
listView.on(ListView.itemLoadingEvent, (args: ItemEventData) => {
if (!args.view) {
// Create label if it is not already created.
args.view = new Label();
args.view.className = "list-group-item";
}
(<any>args.view).text = listViewArray.getItem(args.index).title;
});
listView.on(ListView.itemTapEvent, (args: ItemEventData) => {
const tappedItemIndex = args.index;
const tappedItemView = args.view;
alert(`Index: ${tappedItemIndex} View: ${tappedItemView}`)
.then(() => {
console.log("Dialog closed!");
});
});
Events
In the example is shown how to set up itemTap
and loadMoreItems
events as well as how to set up the needed callback methods in the Code-Behind.
<ListView row="0" items="{{ listArray }}" itemTap="onItemTap" loadMoreItems="onLoadMoreItems" class="list-group">
<ListView.itemTemplate>
<StackLayout class="list-group-item">
<Label text="{{ title || 'Downloading...' }}" textWrap="true" class="title" />
</StackLayout>
</ListView.itemTemplate>
</ListView>
// The event will be raise when an item inside the ListView is tapped.
function onItemTap(args) {
const index = args.index;
dialogs.alert(`ListView item tap ${index}`).then(() => {
console.log("Dialog closed!");
});
}
exports.onItemTap = onItemTap;
// The event will be raised when the ListView is scrolled so that the last item is visible. This even is intended to be used to add additional data in the ListView.
function onLoadMoreItems(args) {
if (loadMore) {
console.log("ListView -> LoadMoreItemsEvent");
setTimeout(() => {
listArray.push(
moreListItems.getItem(
Math.floor(Math.random() * moreListItems.length)
)
);
listArray.push(
moreListItems.getItem(
Math.floor(Math.random() * moreListItems.length)
)
);
listArray.push(
moreListItems.getItem(
Math.floor(Math.random() * moreListItems.length)
)
);
listArray.push(
moreListItems.getItem(
Math.floor(Math.random() * moreListItems.length)
)
);
listArray.push(
moreListItems.getItem(
Math.floor(Math.random() * moreListItems.length)
)
);
}, 3000);
loadMore = false;
}
}
exports.onLoadMoreItems = onLoadMoreItems;
// The event will be raise when an item inside the ListView is tapped.
export function onItemTap(args: ItemEventData) {
const index = args.index;
alert(`ListView item tap ${index}`).then(() => {
console.log("Dialog closed!");
});
}
// The event will be raised when the ListView is scrolled so that the last item is visible.
// This even is intended to be used to add additional data in the ListView.
export function onLoadMoreItems(args: ItemEventData) {
if (loadMore) {
console.log("ListView -> LoadMoreItemsEvent");
setTimeout(() => {
listArray.push(
moreListItems.getItem(
Math.floor(Math.random() * moreListItems.length)
)
);
listArray.push(
moreListItems.getItem(
Math.floor(Math.random() * moreListItems.length)
)
);
listArray.push(
moreListItems.getItem(
Math.floor(Math.random() * moreListItems.length)
)
);
listArray.push(
moreListItems.getItem(
Math.floor(Math.random() * moreListItems.length)
)
);
listArray.push(
moreListItems.getItem(
Math.floor(Math.random() * moreListItems.length)
)
);
}, 3000);
loadMore = false;
}
}
Multiple Templates Selector Function
The example shows, how to specify the item template selector as a function in the code-behind file
In case your item template selector involves complicated logic which cannot be expressed with an expression, you can create an item template selector function in the code behind of the page in which the ListView resides. The function receives the respective data item, the row index and the entire ListView items collection as parameters. It has to return the the key of the template to be used based on the supplied information.
<ListView row="3" items="{{ listArray }}" class="list-group" itemTemplateSelector="selectItemTemplate">
<ListView.itemTemplates>
<template key="even">
<StackLayout class="list-group-item" style.backgroundColor="white">
<Label text="{{ 'Name: ' + name }}" class="h2" textWrap="true"/>
<Label text="{{ 'Age: ' + age }}"/>
</StackLayout>
</template>
<template key="odd">
<StackLayout class="list-group-item" style.backgroundColor="gray">
<Label text="{{ 'Name: ' + name }}" class="h2" textWrap="true" />
<Label text="{{ 'Age: ' + age }}"/>
</StackLayout>
</template>
</ListView.itemTemplates>
</ListView>
function selectItemTemplate(item, index, items) {
return index % 2 === 0 ? "even" : "odd";
}
export function selectItemTemplate(item, index, items) {
return index % 2 === 0 ? "even" : "odd";
}
<ListView row="1" items="{{ listArray }}" class="list-group" itemTemplateSelector="selectItemTemplateAge">
<ListView.itemTemplates>
<template key="green">
<StackLayout class="list-group-item" style.backgroundColor="green">
<Label text="{{ 'Name: ' + name }}" class="h2" textWrap="true"/>
<Label text="{{ 'Age: ' + age }}"/>
</StackLayout>
</template>
<template key="red">
<StackLayout class="list-group-item" style.backgroundColor="red">
<Label text="{{ 'Name: ' + name }}" class="h2" textWrap="true" />
<Label text="{{ 'Age: ' + age }}"/>
</StackLayout>
</template>
</ListView.itemTemplates>
</ListView>
function selectItemTemplateAge(item, index, items) {
return item.age > 18 ? "green" : "red";
}
export function selectItemTemplateAge(item, index, items) {
return item.age > 18 ? "green" : "red";
}
Multiple Templates
The example shows, how to define multiple item templates and an item template selector in the XML
The itemTemplateSelector
can be an expression specified directly in XML. The context of the expression is the data item for each row.
You can use the special value $index
in the item template selector expression which represents the row index.
<ListView row="1" items="{{ listArray }}" class="list-group" itemTemplateSelector="$index % 2 === 0 ? 'even' : 'odd'">
<ListView.itemTemplates>
<template key="even">
<StackLayout class="list-group-item" style.backgroundColor="white">
<Label text="{{ 'Name: ' + name }}" class="h2" textWrap="true"/>
<Label text="{{ 'Age: ' + age }}"/>
</StackLayout>
</template>
<template key="odd">
<StackLayout class="list-group-item" style.backgroundColor="gray">
<Label text="{{ 'Name: ' + name }}" class="h2" textWrap="true" />
<Label text="{{ 'Age: ' + age }}"/>
</StackLayout>
</template>
</ListView.itemTemplates>
</ListView>
Native Component
Android | iOS |
---|---|
android.widget.ListView | UITableView |