If you define a ListView with Custom View Cells in Xamarin Forms, it tries to repeatedly
calculate the scroll position of ListView items when you scroll back and forth
which causes more memory leaks and crash the app. This limitation can be overcome
by rendering a platform specific cell in to Xamarin forms Listview.
Since Xamarin Forms intended to develop more data driven based applications where
performance is matters.
Every Xamarin Forms cell has a renderer for each platform that creates an instance
of a native control. View Cell in Xamarin Forms will be turned as a native DataTemplate
with the help of ViewCellRenderer class.
Create a CustomViewCell class with binding properties in PCL project and Commands
as below:
public class CustomViewCell : ViewCell
{
public static readonly BindableProperty NameProperty =
BindableProperty.Create("Name", typeof(string), typeof(CustomViewCell), "");
public string Name
{
get { return (string)GetValue(NameProperty); }
set { SetValue(NameProperty, value); }
}
public static readonly BindableProperty CategoryProperty =
BindableProperty.Create("Category", typeof(string), typeof(CustomViewCell), "");
public string Category
{
get { return (string)GetValue(CategoryProperty); }
set { SetValue(CategoryProperty, value); }
}
public static readonly BindableProperty ImageFilenameProperty =
BindableProperty.Create("ImageFilename", typeof(string), typeof(CustomViewCell), "");
public string ImageFilename
{
get { return (string)GetValue(ImageFilenameProperty); }
set { SetValue(ImageFilenameProperty, value); }
}
private ICommand imageTappedCommand;
public ICommand ImageTappedCommand
{
get
{
return this.imageTappedCommand ?? (this.imageTappedCommand = new Command(this.OnImageTapped));
}
}
private async void OnImageTapped()
{
// if you are using MVVM, here you can find the parent Binding Context VM
// and use the respective command.Execute
await App.Current.MainPage.DisplayAlert("Alert","Image Tapped","ok");
}
}
Now create a Xaml view in PCL and add this custom view cell to ListView control as
below:
<ListView x:Name="listView" CachingStrategy="RecycleElement">
<ListView.ItemTemplate>
<DataTemplate>
<local:CustomViewCell Name="{Binding Name}" Category="{Binding Category}" ImageFilename="{Binding ImageFilename}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
A Xamarin.Forms ListView control is used to display a list of data, which is populated
through the ItemSource property. The RecycleElement caching strategy attempts
to minimize the ListView memory footprint and execution speed by recycling list
cells.
Each row in list contains three items, Name, Category and image which will be rendered
from the DataTemplate layout.
Create a CustomRenderer in UWP to render the platform specific cell with DataTemplate.
To know more about Custom Renderers, refer my previous article.
[assembly: ExportRenderer(typeof(CustomViewCell), typeof(CustomViewCellRenderer))]
namespace ListViewCell.UWP
{
public class CustomViewCellRenderer : ViewCellRenderer
{
public override Windows.UI.Xaml.DataTemplate GetTemplate(Cell cell)
{
// here you can distinguish the datatemplates without template selector
// if you want to bind multiple templates for a Listview
return App.Current.Resources["listTemplate"] as Windows.UI.Xaml.DataTemplate;
}
}
}
And the layout of DataTemplate in UWP project with binding properties and Commands
as below:
<DataTemplate x:Key="listTemplate">
<Grid Background="LightYellow">
<Grid.Resources>
<local:ImageConverter x:Name="ConcatImageExtensionConverter" />
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.40*" />
<ColumnDefinition Width="0.40*"/>
<ColumnDefinition Width="0.20*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.ColumnSpan="2" Foreground="#7F3300"
FontStyle="Italic" FontSize="22" VerticalAlignment="Top"
Text="{Binding Name}" />
<TextBlock Grid.RowSpan="2" Grid.Column="1"
Foreground="#267F00" FontWeight="Bold" FontSize="12"
VerticalAlignment="Bottom" Text="{Binding Category}" />
<Image Grid.RowSpan="2" Grid.Column="2" HorizontalAlignment="Left"
VerticalAlignment="Center" Source="{Binding ImageFilename, Converter={StaticResource
ConcatImageExtensionConverter}}" Width="50" Height="50"
local:ImageItemTappedCommand.Command="{Binding ImageTappedCommand}"/>
<Line Grid.Row="1" Grid.ColumnSpan="3" X1="0"
X2="1" Margin="30,20,0,0" StrokeThickness="1" Stroke="LightGray"
Stretch="Fill" VerticalAlignment="Bottom" />
</Grid>
</DataTemplate>
Since the DataTemplate has designed in Platform, if you want to bind the commands
from Xamarin Forms PCL project to a specific control in this DataTemplate, just
define the command in CustomViewCell class and attach to control behaviors.
In this case, I have created a behavior for Image Tapped event and Binding the Command
as highlighted above.
Image Tapped event behavior class as below:
public class ImageItemTappedCommand : DependencyObject
{
public static readonly DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached("Command", typeof(ICommand), typeof(ImageItemTappedCommand), new PropertyMetadata(null, CommandPropertyChanged));
public static void SetCommand(DependencyObject attached, ICommand value)
{
attached.SetValue(CommandProperty, value);
}
public static ICommand GetCommand(DependencyObject attached)
{
return (ICommand)attached.GetValue(CommandProperty);
}
private static void CommandPropertyChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
// Attach click handler
(d as Image).Tapped += ImageItemTappedCommand_Tapped;
}
private static void ImageItemTappedCommand_Tapped(object sender, Windows.UI.Xaml.Input.TappedRoutedEventArgs
e)
{
// Get control
var image = (sender as Image);
// Get command
ICommand command = GetCommand(image);
// Execute command
command.Execute(null);
}
}
Download the working sample.