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:
- Xamarin.Forms – Trabalhando com ListView
- Xamarin Forms – Acessando a base de filmes do Netflix
- Xamarin Forms – DataBinding com ListView e MVVM
- Xamarin Forms – Apresentando a view ListView (Youtube)
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)