Preenchendo um ListView Xamarin.Android com dados

Para adicionar linhas a um ListView você precisa adicioná-lo ao seu layout e implementar um IListAdapter com métodos que as chamadas para preencher a ListView si mesmo. O Android inclui classes internas ListActivity que ArrayAdapter você pode usar sem definir nenhum layout, XML ou código personalizado. A ListActivity classe cria automaticamente uma ListView e expõe uma ListAdapter propriedade para fornecer as exibições de linha a serem exibidas por meio de um adaptador.

Os adaptadores internos usam uma ID de recurso de exibição como um parâmetro que é usado para cada linha. Você pode usar recursos internos, como esses Android.Resource.Layout , para não precisar escrever seus próprios.

Usando ListActivity e ArrayAdapter<String>

O exemplo BasicTable/HomeScreen.cs demonstra como usar essas classes para exibir um ListView em apenas algumas linhas de código:

[Activity(Label = "BasicTable", MainLauncher = true, Icon = "@drawable/icon")]
public class HomeScreen : ListActivity {
   string[] items;
   protected override void OnCreate(Bundle bundle)
   {
       base.OnCreate(bundle);
       items = new string[] { "Vegetables","Fruits","Flower Buds","Legumes","Bulbs","Tubers" };
       ListAdapter = new ArrayAdapter<String>(this, Android.Resource.Layout.SimpleListItem1, items);
   }
}

Manipulando cliques em linhas

Normalmente, um ListView também permitirá que o usuário toque em uma linha para executar alguma ação (como tocar uma música, chamar um contato ou mostrar outra tela). Para responder aos toques do usuário é necessário que haja mais um método implementado no ListActivityOnListItemClick – como este:

Captura de tela de um SimpleListItem

protected override void OnListItemClick(ListView l, View v, int position, long id)
{
   var t = items[position];
   Android.Widget.Toast.MakeText(this, t, Android.Widget.ToastLength.Short).Show();
}

Agora o usuário pode tocar em uma linha e um Toast alerta aparecerá:

Captura de tela da notificação do sistema que aparece quando uma linha é tocada

Implementando um ListAdapter

ArrayAdapter<string> é ótimo por causa de sua simplicidade, mas é extremamente limitado. No entanto, muitas vezes você tem uma coleção de entidades comerciais, em vez de apenas cadeias de caracteres que deseja vincular. Por exemplo, se seus dados consistirem em uma coleção de classes Employee, talvez você queira que a lista exiba apenas os nomes de cada funcionário. Para personalizar o comportamento de um ListView para controlar quais dados são exibidos, você deve implementar uma subclasse de BaseAdapter substituir os quatro itens a seguir:

  • Contagem – Para informar ao controle quantas linhas estão nos dados.

  • GetView – Para retornar um View para cada linha, preenchido com dados. Esse método tem um parâmetro para o ListView passar em uma linha existente e não utilizada para reutilização.

  • GetItemId – Retorna um identificador de linha (normalmente o número da linha, embora possa ser qualquer valor longo que você desejar).

  • this[int] indexador – Para retornar os dados associados a um número de linha específico.

O código de exemplo em BasicTableAdapter/HomeScreenAdapter.cs demonstra como subclasse BaseAdapter:

public class HomeScreenAdapter : BaseAdapter<string> {
   string[] items;
   Activity context;
   public HomeScreenAdapter(Activity context, string[] items) : base() {
       this.context = context;
       this.items = items;
   }
   public override long GetItemId(int position)
  {
       return position;
   }
   public override string this[int position] {  
       get { return items[position]; }
   }
   public override int Count {
       get { return items.Length; }
   }
   public override View GetView(int position, View convertView, ViewGroup parent)
   {
       View view = convertView; // re-use an existing view, if one is available
      if (view == null) // otherwise create a new one
           view = context.LayoutInflater.Inflate(Android.Resource.Layout.SimpleListItem1, null);
       view.FindViewById<TextView>(Android.Resource.Id.Text1).Text = items[position];
       return view;
   }
}

Usando um adaptador personalizado

Usar o adaptador personalizado é semelhante ao built-in ArrayAdapter, passando a context e o string[] de valores para exibir:

ListAdapter = new HomeScreenAdapter(this, items);

Como este exemplo usa o mesmo layout de linha (SimpleListItem1), o aplicativo resultante será idêntico ao exemplo anterior.

Reutilização do modo de exibição de linha

Neste exemplo, há apenas seis itens. Como a tela pode caber em oito, não é necessária a reutilização de linhas. Ao exibir centenas ou milhares de linhas, no entanto, seria um desperdício de memória criar centenas ou milhares de View objetos quando apenas oito cabem na tela por vez. Para evitar essa situação, quando uma linha desaparece da tela, sua exibição é colocada em uma fila para reutilização. À medida que o usuário rola, as ListView chamadas GetView para solicitar que novos modos de exibição sejam exibidos – se disponíveis, ele passa uma exibição não utilizada no convertView parâmetro. Se esse valor for null, seu código deverá criar uma nova instância de exibição, caso contrário, você poderá redefinir as propriedades desse objeto e reutilizá-lo.

O GetView método deve seguir esse padrão para reutilizar modos de exibição de linha:

public override View GetView(int position, View convertView, ViewGroup parent)
{
   View view = convertView; // re-use an existing view, if one is supplied
   if (view == null) // otherwise create a new one
       view = context.LayoutInflater.Inflate(Android.Resource.Layout.SimpleListItem1, null);
   // set view properties to reflect data for the given row
   view.FindViewById<TextView>(Android.Resource.Id.Text1).Text = items[position];
   // return the view, populated with data, for display
   return view;
}

As implementações personalizadas do adaptador devem sempre reutilizar o convertView objeto antes de criar novos modos de exibição para garantir que eles não fiquem sem memória ao exibir listas longas.

Algumas implementações de adaptador (como o CursorAdapter) não têm um GetView método, mas exigem dois métodos NewView diferentes e BindView que impõem a reutilização de linhas separando as responsabilidades de GetView em dois métodos. Há um CursorAdapter exemplo mais adiante no documento.

Ativando a rolagem rápida

A Rolagem Rápida ajuda o usuário a percorrer longas listas, fornecendo um 'identificador' adicional que atua como uma barra de rolagem para acessar diretamente uma parte da lista. Esta captura de tela mostra a alça de rolagem rápida:

Captura de tela da rolagem rápida com uma alça de rolagem

Fazer com que a alça de rolagem rápida apareça é tão simples quanto definir a FastScrollEnabled propriedade como true:

ListView.FastScrollEnabled = true;

Adicionando um índice de seção

Um índice de seção fornece feedback adicional para os usuários quando eles estão rolando rapidamente por uma longa lista – ele mostra para qual "seção" eles rolaram. Para fazer com que o índice de seção apareça, a subclasse Adapter deve implementar a ISectionIndexer interface para fornecer o texto do índice, dependendo das linhas que estão sendo exibidas:

Captura de tela de H aparecendo acima da seção que começa com H

Para implementar ISectionIndexer , você precisa adicionar três métodos a um adaptador:

  • GetSections – Fornece a lista completa de títulos de índice de seção que podem ser exibidos. Esse método requer uma matriz de objetos Java, portanto, o código precisa criar um Java.Lang.Object[] a partir de uma coleção .NET. Em nosso exemplo, ele retorna uma lista dos caracteres iniciais na lista como Java.Lang.String .

  • GetPositionForSection – Retorna a posição da primeira linha para um determinado índice de seção.

  • GetSectionForPosition – Retorna o índice de seção a ser exibido para uma determinada linha.

O arquivo de exemplo SectionIndex/HomeScreenAdapter.cs implementa esses métodos e algum código adicional no construtor. O construtor cria o índice de seção fazendo um loop por cada linha e extraindo o primeiro caractere do título (os itens já devem ser classificados para que isso funcione).

alphaIndex = new Dictionary<string, int>();
for (int i = 0; i < items.Length; i++) { // loop through items
   var key = items[i][0].ToString();
   if (!alphaIndex.ContainsKey(key))
       alphaIndex.Add(key, i); // add each 'new' letter to the index
}
sections = new string[alphaIndex.Keys.Count];
alphaIndex.Keys.CopyTo(sections, 0); // convert letters list to string[]

// Interface requires a Java.Lang.Object[], so we create one here
sectionsObjects = new Java.Lang.Object[sections.Length];
for (int i = 0; i < sections.Length; i++) {
   sectionsObjects[i] = new Java.Lang.String(sections[i]);
}

Com as estruturas de dados criadas, os ISectionIndexer métodos são muito simples:

public Java.Lang.Object[] GetSections()
{
   return sectionsObjects;
}
public int GetPositionForSection(int section)
{
   return alphaIndexer[sections[section]];
}
public int GetSectionForPosition(int position)
{   // this method isn't called in this example, but code is provided for completeness
    int prevSection = 0;
    for (int i = 0; i < sections.Length; i++)
    {
        if (GetPositionForSection(i) > position)
        {
            break;
        }
        prevSection = i;
    }
    return prevSection;
}

Os títulos do índice de seções não precisam mapear 1:1 para suas seções reais. É por isso que o GetPositionForSection método existe. GetPositionForSection dá-lhe a oportunidade de mapear quaisquer índices que estejam na sua lista de índices para quaisquer secções que estejam na sua vista de lista. Por exemplo, você pode ter um "z" em seu índice, mas talvez não tenha uma seção de tabela para cada letra, portanto, em vez de "z" mapear para 26, ele pode mapear para 25 ou 24, ou qualquer índice de seção "z" deve ser mapeado para.