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)




