Milèstre BV
Sep 10, 2018

Xamarin Forms: dynamic list


When you do have a page that makes use of a ScrollView you can not use a ListView in it. It will result in strange presentation. For instance using this code:

<?xml version="1.0" encoding="utf-8" ?>

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"

             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"

             xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"

             prism:ViewModelLocator.AutowireViewModel="True"

             xmlns:i18n="clr-namespace:TenCate_App.Helpers;assembly=TenCate_App"

             xmlns:mr="clr-namespace:MR.Gestures;assembly=MR.Gestures"

             xmlns:ffimageloading="clr-namespace:FFImageLoading.Forms;assembly=FFImageLoading.Forms"

             x:Class="TenCate_App.Views.ProductPage" x:Name="ProductPageRef"

             Title="{i18n:Translate ProductPage_Title}" ControlTemplate="{StaticResource PageTemplate}"

             NavigationPage.BackButtonTitle="">

    <ScrollView>

        <Grid Padding="0">

            <Grid.RowDefinitions>

                <RowDefinition Height="90" />

                <RowDefinition Height="35" />

                <RowDefinition Height="Auto" />

            </Grid.RowDefinitions>

 

            <StackLayout Grid.Row="2" Padding="0" BackgroundColor="White" VerticalOptions="FillAndExpand">

                <Label Margin="10" Text="{Binding ProductItem.Name}" Style="{StaticResource NameLabelStyle}" />

                <Label Margin="10" Text="{i18n:Translate ProductDescription}" Style="{StaticResource SectionLabelStyle}" />

                <Label Margin="10,0,10,0" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" LineBreakMode="WordWrap"

                            Text="{Binding ProductItem.Body}" Style="{StaticResource InfoLabelValueStyle}" />

                <Label Margin="10,10,10,0" Text="{i18n:Translate ProductBenefits}" Style="{StaticResource SectionLabelStyle}" />

                <ListView x:Name="lstFeaturess" VerticalOptions="FillAndExpand" Margin="0" HeightRequest="{Binding ProductItem.FeaturesHeight}"

                          SeparatorVisibility="None" ItemsSource="{Binding ProductItem.FeatureItems}" HasUnevenRows="True">

                    <ListView.ItemTemplate>

                        <DataTemplate>

                            <ViewCell>

                                <Grid Padding="10,0,14,0">

                                    <Grid.ColumnDefinitions>

                                        <ColumnDefinition Width="20" />

                                        <ColumnDefinition Width="*" />

                                    </Grid.ColumnDefinitions>

                                    <Image Grid.Column="0" HeightRequest="12" HorizontalOptions="Start" Source="arrow_right_active.png" />

                                    <Label Grid.Column="1" Text="{Binding Title}" Style="{StaticResource InfoLabelStyle}" />

                                </Grid>

                            </ViewCell>

                        </DataTemplate>

                    </ListView.ItemTemplate>

                </ListView>

will result in this presentation:

Using ListView in a ScrollView

To be able to present a dynamic list we are going to use a custom Grid control where the rows are create dynamically.

The XAML of this new custom grid control looks like:

<?xml version="1.0" encoding="UTF-8"?>

<Grid xmlns="http://xamarin.com/schemas/2014/forms"

     xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"

     x:Class="TenCate_App.Views.DynamicGridView"

     x:Name="DynamicGrid" RowSpacing="0">

 

    <Grid.ColumnDefinitions>

        <ColumnDefinition Width="20"/>

        <ColumnDefinition Width="*"/>

    </Grid.ColumnDefinitions>

 

</Grid>

And the code behind:

using System.Collections.Generic;

using TenCate_App.Models;

using Xamarin.Forms;

 

namespace TenCate_App.Views

{

    public partial class DynamicGridView : Grid

    {

        public DynamicGridView()

        {

            InitializeComponent();

        }

 

        public static readonly BindableProperty DynamicRowsProperty =

            BindableProperty.Create(nameof(DynamicRows), typeof(List<Item>), typeof(DynamicGridView), null,

                BindingMode.OneWay, null, OnDynamicRowsChanged);

 

        private static void OnDynamicRowsChanged(BindableObject bindable, object oldvalue, object newvalue)

        {

            var control = (DynamicGridView)bindable;

            if (control != null)

            {

                if (newvalue is List<Item> dynamicRows)

                {

                    var rowNumber = -1;

                    Style imageStyle = Application.Current.Resources["ItemIconStyle"] as Style;

                    Style labelStyle = Application.Current.Resources["ItemLabelStyle"] as Style;

                    foreach (var dynamicRow in dynamicRows)

                    {

                        var image = new Image {Source = "arrow_right_active.png"};

                        image.Style = imageStyle;

                        var valueLabel = new Label { Text = dynamicRow.Title };

                        valueLabel.Style = labelStyle;

 

                        rowNumber++;

                        control.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });

                        control.Children.Add(image, 0, rowNumber);

                        control.Children.Add(valueLabel, 1, rowNumber);

                    }

                }

            }

        }

 

        public List<Item> DynamicRows

        {

            get => (List<Item>)GetValue(DynamicRowsProperty);

            set => SetValue(DynamicRowsProperty, value);

        }

    }

}



Now we are going to use this custom Grid control in our page withe the ScrollView:
 

<?xml version="1.0" encoding="utf-8" ?>

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"

             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"

             xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"

             prism:ViewModelLocator.AutowireViewModel="True"

             xmlns:i18n="clr-namespace:TenCate_App.Helpers;assembly=TenCate_App"

     xmlns:localCtrl="clr-namespace:TenCate_App.Views;assembly=TenCate_App"

             xmlns:mr="clr-namespace:MR.Gestures;assembly=MR.Gestures"

             xmlns:ffimageloading="clr-namespace:FFImageLoading.Forms;assembly=FFImageLoading.Forms"

             x:Class="TenCate_App.Views.ProductPage" x:Name="ProductPageRef"

             Title="{i18n:Translate ProductPage_Title}" ControlTemplate="{StaticResource PageTemplate}"

             NavigationPage.BackButtonTitle="">

    <ScrollView>

        <Grid Padding="0">

            <Grid.RowDefinitions>

                <RowDefinition Height="90" />

                <RowDefinition Height="35" />

                <RowDefinition Height="Auto" />

            </Grid.RowDefinitions>

 

            <StackLayout Grid.Row="2" Padding="0" BackgroundColor="White" VerticalOptions="FillAndExpand">

                <Label Margin="10" Text="{Binding ProductItem.Name}" Style="{StaticResource NameLabelStyle}" />

                <Label Margin="10" Text="{i18n:Translate ProductDescription}" Style="{StaticResource SectionLabelStyle}" />

                <Label Margin="10,0,10,0" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" LineBreakMode="WordWrap"

                            Text="{Binding ProductItem.Body}" Style="{StaticResource InfoLabelValueStyle}" />

                <Label Margin="10,10,10,0" Text="{i18n:Translate ProductBenefits}" Style="{StaticResource SectionLabelStyle}" />

                <localCtrl:DynamicGridView DynamicRows="{Binding ProductItem.FeatureItems}"></localCtrl:DynamicGridView>


And this will be the presentation:

Dynamic List based on a Grid Control