Dev (Back & Front)ARTIGO

Xamarin Forms – Agrupando itens em uma ListView

O autor mostra neste artigo, agrupar itens em uma view ListView no Xamarin Forms.

Hoje veremos como podemos agrupar itens em uma view ListView no Xamarin Forms.

Já escrevi alguns artigos sobre a ListView do Xamarin Forms. Se você está chegando agora e quer saber mais sobre a ListView, acompanhe meus artigos:

Vou implementar uma Agenda de Contatos, onde iremos agrupar a lista pela letra inicial do nome do contato. Então vamos à parte prática.

Recursos usados:

Criando o projeto no Visual Studio 2017 Community

  • Abra o Visual Studio Community 2017 e clique em New Project;
  • Selecione Visual C#, o template Cross Plataform e a seguir, Cross Plataform App (Xamarin.Forms);
  • Informe o nome XF_Agenda e clique no botão OK;

  • A seguir, selecione Blank App e marque as plataformas para quais deseja gerar os projetos;
  • Marque a opção Xamarin.Forms, e em Code Sharing Strategy, marque .NET Standard, que substitui a opção de projetos PCL a partir do VS 2017 update 15.5.

Definindo o modelo de domínio e o repositório de dados

  • Crie uma pasta Models no projeto portátil via menu Project -> New Folder;
  • Na pasta Models crie a classe Contato, que representa o nosso modelo de domínio, ou seja, representa um contato:
public class Contato
{
    public string Nome { get; set; }
    public string Email { get; set; }
    public string Telefone { get; set; }
}

Ainda na pasta Models, crie a classe ContatoRepository, que representa o nosso repositório de dados onde vamos definir os dados usados na aplicação:

public class ContatoRepository
    {
        public IList<Contato> GetContatos { get; private set; }
        public ContatoRepository()
        {
            GetContatos = new List<Contato> {
                new Contato { Nome = "Alex Silveira", Email = "egestas@anequeNullam.co.uk", Telefone="9985-5623" },
                new Contato { Nome = "Wilson Martin", Email = "a.tortor@Sed.net",  Telefone="9985-5623" },
                new Contato { Nome = "Osmar Moss", Email = "tristique@faucibusutnulla.net", Telefone="9985-5623" },
                new Contato { Nome = "Yasmim Dudley", Email = "montes.nascetur.ridiculus@fringillaest.ca", Telefone="9985-5623" },
                new Contato { Nome = "Yoshio Anthony", Email = "ut.aliquam.iaculis@pharetraNam.edu" , Telefone="9985-5623"},
                new Contato { Nome = "Valentina Poole", Email = "auctor@consectetuer.org" , Telefone="9985-5623"},
                new Contato { Nome = "Armando Tillman", Email = "facilisis.vitae.orci@liberolacusvarius.com" , Telefone="9985-5623"},
                new Contato { Nome = "Klaus Hickman", Email = "Pellentesque.habitant@tristiqueaceleifend.org" , Telefone="9985-5623" },
                new Contato { Nome = "Levi Marshall", Email = "imperdiet.ullamcorper@Quisque.com" , Telefone="9985-5623" },
                new Contato { Nome = "Norberto Boone", Email = "adipiscing@anteipsum.ca" , Telefone="9985-5623" },
                new Contato { Nome = "Emerlindo Mendez", Email = "aliquet.molestie.tellus@Nam.net" , Telefone="9985-5623" },
                new Contato { Nome = "Marcos Compton", Email = "Etiam.bibendum.fermentum@malesuadaIntegerid.co.uk" , Telefone="9985-5623" },
                new Contato { Nome = "Braulio Chapman", Email = "lacinia.orci@aliquetdiamSed.ca" , Telefone="9985-5623" },
                new Contato { Nome = "Heleno Roberson", Email = "gravida@Nunc.edu" , Telefone="9985-5623" },
                new Contato { Nome = "Yuri Herrera", Email = "velit@erat.org" , Telefone="9985-5623" },
                new Contato { Nome = "Lucas Brown", Email = "magnis@Cumsociis.org" , Telefone="9985-5623" },
                new Contato { Nome = "Gilson Reilly", Email = "vel@NullamenimSed.com" , Telefone="9985-5623" },
                new Contato { Nome = "Arsenio Suarez", Email = "ridiculus.mus.Aenean@tellusfaucibusleo.co.uk" , Telefone="9985-5623" },
                new Contato { Nome = "Igor Mclaughlin", Email = "ut.lacus.Nulla@Aliquamnec.edu" , Telefone="9985-3023" },
                new Contato { Nome = "Carla Craft", Email = "Etiam.gravida.molestie@rutrummagna.ca" , Telefone="9985-3023" },
                new Contato { Nome = "Benedito Carson", Email = "adipiscing@enimMauris.edu" , Telefone="9985-3023" },
                new Contato { Nome = "Roberto Reynolds", Email = "commodo@sapienmolestie.edu" , Telefone="9985-3023" },
                new Contato { Nome = "Rivaldo Mcguire", Email = "sem.elit.pharetra@nuncrisus.com" , Telefone="9985-3023" },
                new Contato { Nome = "William Fuller", Email = "bibendum@ultrices.com" , Telefone="9985-3023" },
                new Contato { Nome = "Helio Shaffer", Email = "lectus.pede@nisi.org" , Telefone="9985-2123" },
                new Contato { Nome = "Armando Chapman", Email = "erat@Donecsollicitudin.edu" , Telefone="9985-2123" },
                new Contato { Nome = "Josia Adams", Email = "justo.Praesent.luctus@purus.org" , Telefone="9985-2123" },
                new Contato { Nome = "Denis Webb", Email = "sit.amet.consectetuer@Loremipsumdolor.org" , Telefone="9985-2123" },
                new Contato { Nome = "Jacob Singleton", Email = "sem.consequat@vehiculaPellentesque.co.uk" , Telefone="9985-2123" },
                new Contato { Nome = "Carina Tucker", Email = "molestie@erosturpis.ca" , Telefone="9985-2123" },
                new Contato { Nome = "Felix Holder", Email = "sollicitudin.a@Curae.co.uk" , Telefone="9985-1123" },
                new Contato { Nome = "Mateus Reid", Email = "Etiam.bibendum@Donecat.edu" , Telefone="9985-1123" },
                new Contato { Nome = "Anabel Noel", Email = "rhoncus.Donec@vel.edu" , Telefone="9985-1123" },
                new Contato { Nome = "Karina Dunlap", Email = "lectus@risusQuisque.co.uk" , Telefone="9985-1123" },
                new Contato { Nome = "Silvio Ewing", Email = "cubilia@afeugiattellus.ca" , Telefone="9985-1123" },
                new Contato { Nome = "Lucas Reed", Email = "id.risus@Aliquam.edu" , Telefone="9985-1123" },
                new Contato { Nome = "Geraldo Huff", Email = "non.arcu.Vivamus@fames.edu" , Telefone="9985-0923" },
                new Contato { Nome = "Fernando Carroll", Email = "ut.nisi.a@elit.edu" , Telefone="9985-0923" },
                new Contato { Nome = "Leonardo Hamilton", Email = "vitae@penatibusetmagnis.net" , Telefone="9985-0923" },
                new Contato { Nome = "Myles Knowles", Email = "vitae.aliquam@magna.org" , Telefone="9985-0923" },
                new Contato { Nome = "Cristina Schmidt", Email = "imperdiet.dictum.magna@vitaeerat.org" , Telefone="9985-0923" },
                new Contato { Nome = "Thais Ball", Email = "Cras.eu@ataugue.net" , Telefone="9985-0923" },
                new Contato { Nome = "Renato Mclean", Email = "leo@vitaenibh.net" , Telefone="9985-2323" },
                new Contato { Nome = "Celio Rogers", Email = "eros.turpis.non@ettristique.co.uk" , Telefone="9985-1023" },
                new Contato { Nome = "Otavio Estes", Email = "vel@ac.edu" , Telefone="9985-7723"},
            };
        }
    }

Definindo a ViewModel: ContatoListaViewModel

Crie uma pasta chamada ViewModels no projeto portátil e a seguir, crie a classe ContatoListaViewModel que representa a lógica da nossa View:

public class ContatoListaViewModel
    {
        public IList<Contato> Items { get; private set; }
        public int ItemsCount { get; private set; }
        public string MeuNumero { get; set; } = "+55 (11) 1111-1111";
        public ContatoListaViewModel()
        {
            var repo = new ContatoRepository();
            Items = repo.GetContatos.OrderBy(c => c.Nome).ToList();
            ItemsCount = repo.GetContatos.Count;
        }
  }

O código retorna a lista de contatos na propriedade Items, o total de contatos na propriedade ItemsCount e o número de telefone defininido em MenuNumero.

Definindo o código da página MainPage

Abra o arquivo MainPage.xaml e inclua o código abaixo:

<?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:local="clr-namespace:XF_Agenda"
             x:Class="XF_Agenda.MainPage"
             Title="Agenda">
    <ListView x:Name="LvwContatos"
        Margin="5" 
        ItemsSource="{Binding Items}"
        Header="{Binding}"
        Footer="{Binding}" 
        HasUnevenRows="True">

        <ListView.ItemTemplate>
            <DataTemplate>
                <TextCell  Text="{Binding Nome}" Detail="{Binding Email, StringFormat='E-mail: {0}'}"/>
            </DataTemplate>
        </ListView.ItemTemplate>

        <ListView.HeaderTemplate>
            <DataTemplate>
                <ContentView BackgroundColor="Beige">
                    <Label Margin="10" HorizontalOptions="CenterAndExpand" Text="{Binding MeuNumero, StringFormat='Meu número: {0}'}" TextColor="Black"/>
                </ContentView>
            </DataTemplate>
        </ListView.HeaderTemplate>

        <ListView.FooterTemplate>
            <DataTemplate>
                <ContentView BackgroundColor="Aquamarine">
                    <Label Margin="10" HorizontalOptions="CenterAndExpand" Text="{Binding ItemsCount, StringFormat='Contatos : {0}'}"  TextColor="Black"/>
                </ContentView>
            </DataTemplate>
        </ListView.FooterTemplate>

    </ListView>
</ContentPage>

No código XAML definimos uma ListView onde a sua propriedade ItemsSource está fazendo o databinding com a propriedade Items da nossa ViewModel ContatoListaViewModel.

Para exibir os contatos, usamos um ItemTemplate e a célula TextCell:

TextCell – usada para exibir texto com uma segunda linha como texto detalhe. Para isso, basta definir as propriedades:

  • Text: para exibir a primeira linha de texto em fonte maior;
  • Detail: para exibir uma segunda linha de texto em fonte menor;

Definimos também o Header e Footer usando a notação {Binding} sem definir um Source ou Path. Fazendo assim, o XAML entende que o objeto que será usado para o Binding será o mesmo do BindingContext atual. Como o ListView propaga esse objeto para os templates Header e Footer, isso não é necessário.

A seguir, definimos o HeaderTemplate e FooterTemplate configurados para exibir as propriedades MeuNumero e ItemsCount.

Agora vamos implementar o código no arquivo MainPage.xaml.cs:

Abra o arquivo MainPage.xaml.cs criado no projeto e a seguir defina o código abaixo:

using Xamarin.Forms;
using XF_AgendaContatos.ViewModel;
namespace XF_AgendaContatos
{
    public partial class MainPage : ContentPage
    {
        public MainPage()
        {
            InitializeComponent();
            BindingContext = new ContatoListViewModel();
        }
    }
}

No código, estamos criando uma instância da nossa ViewModel e atribuindo ao BindingContext.

Executando o projeto, iremos obter o seguinte resultado: (nosso projeto padrão é o projeto Android).

Muito bem, agora vamos agrupar as informações da nossa Agenda de forma a organizar os nomes pela primeira letra do nome.

Agrupando as informações da Agenda

Para poder agrupar as informações da nossa agenda, primeiro vamos ter que habilitar o nosso ListView para aceitar o agrupamento definindo as seguintes propriedades:

  • IsGroupingEnable = True
  • GroupDisplayBinding = “{Binding Key}”

No arquivo MainPage.xaml:

 ...
  <ListView x:Name="LvwContatos"
        Margin="5" 
        IsGroupingEnabled="True"      
        GroupDisplayBinding="{Binding Key}"      
        ItemsSource="{Binding Items}"
        Header="{Binding}"
        Footer="{Binding}" 
        HasUnevenRows="True">

Após isso, vamos alterar o código da nossa ViewModel ContatoListaViewModel definindo o código para agrupar as informações com base na primeira letra do nome:

public class ContatoListaViewModel
    {
        public IList<Contato> Items { get; private set; }
        public List<ObservableGroupCollection<string,Contato>> DadosAgrupados { get; set; }
        public int ItemsCount { get; private set; }
        public string MeuNumero { get; set; } = "+55 (11) 1111-1111";
        public ContatoListaViewModel()
        {
            var repo = new ContatoRepository();
            Items = repo.GetContatos.OrderBy(c => c.Nome).ToList();
            DadosAgrupados = Items.OrderBy(p => p.Nome)
                             .GroupBy(p => p.Nome[0].ToString())
                             .Select(p => new ObservableGroupCollection<string, Contato>(p)).ToList();
            ItemsCount = repo.GetContatos.Count;
        }
    }

Definimos uma nova propriedade chamada DadosAgrupados() que é do tipo List<ObservableGroupCollection<string,Contato>> onde estamos ordenando os nomes pela primeira letra do nome.

Para dar suporte à implementação da propriedade, precisamos criar a classe ObservableGroupCollection na pasta ViewModels:

 public class ObservableGroupCollection<S, T> : ObservableCollection<T>
    {
        private readonly S _key;
        public ObservableGroupCollection(IGrouping<S, T> group)
            : base(group)
        {
            _key = group.Key;
        }
        public S Key
        {
            get { return _key; }
        }
    }

Essa classe herda de ObservableCollection, permitindo assim, adicionar ou remover itens de um grupo existente. Ela também define a primeira letra para realizar o agrupamento.

Para o nosso exemplo, Key será do tipo string e a coleção será do tipo IEnumerable<Contato>.

Agora basta alterar a propriedade ItemsSource do ListView para a nova propriedade DadosAgrupados:

<ListView x:Name="LvwContatos"
    Margin="5" 
   IsGroupingEnabled="True" 
   GroupDisplayBinding="{Binding Key}" 
   ItemsSource="{Binding DadosAgrupados}"
   Header="{Binding}"
   Footer="{Binding}" 
   HasUnevenRows="True">

Executando o projeto iremos obter o seguinte resultado:

Vemos agora as informações da agenda agrupadas em ordem alfabética pela primeira letra do nome.

Pegue o código usado no projeto aqui: XF_Agenda.zip (somente o projeto compartilhado)

é referência em Visual Basic no Brasil e autor dos livros "Aprenda Rápido: ASP" e "ASP, ADO e Banco de Dados na Internet". Mantenedor do site macoratti.net.

Ver perfil