Android

23 fev, 2018

Xamarin Forms – Agrupando itens em uma ListView

Publicidade

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)