Create a ListView With 3 Columns Xamarin Forms

I want to create a customListView in Xamarin Forms that shows 3 columns and N rows (I will get the source from a web service). Each cell has an icon and a description. I have found several examples but no one uses columns.

Upvotes: 1

Views: 8259

Answers (2)

Nikola Irinchev
Nikola Irinchev

Reputation: 2186

A much easier solution than the proposed is to use the DevExpress Grid control. It has support for headers, row virtualization, pull-to-refresh, load on demand and a bunch of other useful features.

Upvotes: 0

Adam
Adam

Reputation: 16199

Depending on what you want, you can create a ListView then have a Grid for each Item, where you define the 3 columns. This will allow you to keep the extra features of the ListView like PullToRefresh.

If you want an automatic GridView type control, I did build one off ChaseFlorell's example as shown in this forum post: https://forums.xamarin.com/discussion/61925/how-to-make-the-dynamic-grid-view-and-make-it-clickable

This way it makes it data bindable, rather than having to explicitly define each one.

First is the Grid Control

<Grid xmlns="http://xamarin.com/schemas/2014/forms"
         xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
         x:Class="Mobile.Controls.GridView">

</Grid>

public partial class GridView : Grid
{
    public GridView()
    {
        InitializeComponent();

        for (var i = 0; i < MaxColumns; i++)
            ColumnDefinitions.Add(new ColumnDefinition());
    }

    public static readonly BindableProperty CommandParameterProperty = BindableProperty.Create<GridView, object>(p => p.CommandParameter, null);
    public static readonly BindableProperty CommandProperty = BindableProperty.Create<GridView, ICommand>(p => p.Command, null);
    public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create<GridView, IEnumerable<object>>(p => p.ItemsSource, null, BindingMode.OneWay, null, (bindable, oldValue, newValue) =>  { ((GridView)bindable).BuildTiles(newValue); });

    private int _maxColumns = 2;
    private float _tileHeight = 0;

    public Type ItemTemplate { get; set; } = typeof(DocumentTypeTemplate);

    public int MaxColumns
    {
        get { return _maxColumns; }
        set { _maxColumns = value; }
    }

    public float TileHeight
    {
        get { return _tileHeight; }
        set { _tileHeight = value; }
    }

    public object CommandParameter
    {
        get { return GetValue(CommandParameterProperty); }
        set { SetValue(CommandParameterProperty, value); }
    }

    public ICommand Command
    {
        get { return (ICommand)GetValue(CommandProperty); }
        set { SetValue(CommandProperty, value); }
    }

    public IEnumerable<object> ItemsSource
    {
        get { return (IEnumerable<object>)GetValue(ItemsSourceProperty); }
        set { SetValue(ItemsSourceProperty, value);  }
    }

    public void BuildTiles(IEnumerable<object> tiles)
    {
        try
        {
            if (tiles == null || tiles.Count() == 0)
                Children?.Clear();

            // Wipe out the previous row definitions if they're there.
            RowDefinitions?.Clear();

            var enumerable = tiles as IList ?? tiles.ToList();
            var numberOfRows = Math.Ceiling(enumerable.Count / (float)MaxColumns);

            for (var i = 0; i < numberOfRows; i++)
                RowDefinitions?.Add(new RowDefinition { Height = TileHeight });

            for (var index = 0; index < enumerable.Count; index++)
            {
                var column = index % MaxColumns;
                var row = (int)Math.Floor(index / (float)MaxColumns);

                var tile = BuildTile(enumerable[index]);

                Children?.Add(tile, column, row);
            }
        }
        catch { // can throw exceptions if binding upon disposal
        }
    }

    private Layout BuildTile(object item1)
    {
        var buildTile = (Layout)Activator.CreateInstance(ItemTemplate, item1);
        buildTile.InputTransparent = false;

        var tapGestureRecognizer = new TapGestureRecognizer
        {
            Command = Command,
            CommandParameter = item1,
            NumberOfTapsRequired = 1                
        };

        buildTile?.GestureRecognizers.Add(tapGestureRecognizer);


        return buildTile;
    }
}

Then define a template

<Grid xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="Mobile.TypeTemplate">

    <Label Text="{Binding Name}" />

</Grid>

public partial class TypeTemplate : Grid
    {
        public TypeTemplate()
        {
            InitializeComponent();
        }

        public TypeTemplate(object item)
        {
            InitializeComponent();
            BindingContext = item;
        }

    }

Then use the control

 <control:GridView HorizontalOptions="FillAndExpand"
                    Grid.Row="1"
                    VerticalOptions="FillAndExpand"
                    RowSpacing="20"
                    ColumnSpacing="20"
                    MaxColumns="2"
                    ItemsSource="{Binding ListOfData}"
                    CommandParameter="{Binding}"
                    Command="{Binding ClickCommand}"
                    IsClippedToBounds="False">
    <control:GridView.TileHeight>
      <OnPlatform x:TypeArguments="x:Single"
                  iOS="60"
                  Android="60"
                  WinPhone="90" />
    </control:GridView.TileHeight>
  </control:GridView>

Upvotes: 2

Related Questions