Relleno de un control ListView de Xamarin.Android con datos
Para agregar filas a un elemento ListView
, debe agregarlo al diseño e implementar una instancia de IListAdapter
con métodos que ListView
llama para rellenarse a sí mismo. Android incluye clases ListActivity
y ArrayAdapter
integradas que puede usar sin definir ningún código o XML de diseño personalizado. La clase ListActivity
crea automáticamente una instancia de ListView
expone una propiedad ListAdapter
para proporcionar las vistas de fila que se van a mostrar mediante un adaptador.
Los adaptadores integrados toman un identificador de recurso de vista como parámetro que se usa para cada fila. Puede usar recursos integrados, como los de Android.Resource.Layout
, por lo que no es necesario escribir otros propios.
Uso de ListActivity y ArrayAdapter<String>
En el ejemplo BasicTable/HomeScreen.cs se muestra cómo usar estas clases para mostrar una instancia de ListView
en solo unas pocas líneas 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);
}
}
Control de clics de fila
Normalmente, ListView
también permitirá al usuario tocar una fila para realizar alguna acción (como reproducir una canción, llamar a un contacto o mostrar otra pantalla). Para responder a los toques del usuario, debe haber un método más implementado en ListActivity
: OnListItemClick
, como este:
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();
}
Ahora el usuario puede tocar una fila y aparecerá una alerta Toast
:
Implementación de una instancia de ListAdapter
ArrayAdapter<string>
es genial debido a su simplicidad, pero es extremadamente limitado. Pero a menudo tiene una colección de entidades empresariales, en lugar de solo cadenas que quiere enlazar.
Por ejemplo, si los datos constan de una colección de clases Employee, es posible que quiera que la lista solo muestre los nombres de cada empleado. A fin de personalizar el comportamiento de ListView
para controlar qué datos se muestran, debe implementar una subclase de BaseAdapter
que invalide los cuatro elementos siguientes:
Count: para indicar al control cuántas filas hay en los datos.
GetView: para devolver una vista para cada fila, rellenada con datos. Este método tiene un parámetro para que
ListView
pase una fila existente sin usar para volverla a utilizar.GetItemId: devuelve un identificador de fila (normalmente el número de fila, aunque puede ser cualquier valor largo que prefiera).
Indizador this[int]: para devolver los datos asociados a un número de fila determinado.
En el código de ejemplo de BasicTableAdapter/HomeScreenAdapter.cs se muestra cómo crear una subclase de 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;
}
}
Uso de un adaptador personalizado
El uso del adaptador personalizado es similar a la instancia integrada de ArrayAdapter
, y se pasan context
y string[]
de los valores que se van a mostrar:
ListAdapter = new HomeScreenAdapter(this, items);
Como en este ejemplo se usa el mismo diseño de fila (SimpleListItem1
), la aplicación resultante tendrá un aspecto idéntico al ejemplo anterior.
Reutilización de la vista de fila
En este ejemplo solo hay seis elementos. Como en la pantalla caben ocho, no es necesario volver a usar filas. Pero al mostrar cientos o miles de filas, sería un desperdicio de memoria crear cientos o miles de objetos View
cuando solo caben ocho en la pantalla a la vez. Para evitar esta situación, cuando una fila desaparece de la pantalla, su vista se coloca en una cola para volver a usarse. Cuando el usuario se desplaza, ListView
llama a GetView
para solicitar nuevas vistas que mostrar: si están disponibles, pasa una vista sin usar en el parámetro convertView
. Si este valor es null, el código debe crear una instancia de vista; de lo contrario, puede volver a establecer las propiedades de ese objeto y reutilizarlo.
El método GetView
debe seguir este patrón para reutilizar vistas de fila:
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;
}
Las implementaciones del adaptador personalizado siempre deben reutilizar el objeto convertView
antes de crear vistas para asegurarse de que no se agote la memoria al mostrar listas largas.
Algunas implementaciones del adaptador (como CursorAdapter
) no tienen un método GetView
, sino que requieren dos métodos diferentes, NewView
y BindView
, que aplican la reutilización de filas mediante la separación de las responsabilidades de GetView
en dos métodos. Más adelante en el documento verá un ejemplo de CursorAdapter
.
Habilitación del desplazamiento rápido
El desplazamiento rápido ayuda al usuario a desplazarse por listas largas proporcionando un "identificador" adicional que actúa como una barra de desplazamiento para acceder directamente a una parte de la lista. En esta captura de pantalla se muestra el controlador de desplazamiento rápido:
Para que el controlador de desplazamiento rápido aparezca solo hay que establecer la propiedad FastScrollEnabled
en true
:
ListView.FastScrollEnabled = true;
Adición de un índice de sección
Un índice de sección proporciona comentarios adicionales para los usuarios cuando se desplazan rápidamente por una larga lista: muestra a qué "sección" se han desplazado. Para que el índice de sección aparezca, la subclase Adapter debe implementar la interfaz ISectionIndexer
para proporcionar el texto del índice en función de las filas que se muestran:
Para implementar ISectionIndexer
, debe agregar tres métodos a un adaptador:
GetSections: proporciona la lista completa de títulos de índice de sección que se pueden mostrar. Este método necesita una matriz de objetos de Java, por lo que el código debe crear una instancia de
Java.Lang.Object[]
a partir de una colección de .NET. En el ejemplo, devuelve una lista de los caracteres iniciales de la lista comoJava.Lang.String
.GetPositionForSection: devuelve la primera posición de fila para un índice de sección determinado.
GetSectionForPosition: devuelve el índice de sección que se va a mostrar para una fila determinada.
El archivo de ejemplo SectionIndex/HomeScreenAdapter.cs
implementa esos métodos y algún código adicional en el constructor. Para crear el índice de sección, el constructor recorre en bucle todas las filas y extrae el primer carácter del título (para que esto funcione los elementos ya deben estar ordenados).
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]);
}
Con las estructuras de datos creadas, los métodos ISectionIndexer
son muy sencillos:
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;
}
Los títulos de índice de sección no necesitan asignar 1:1 a las secciones reales. Por este motivo existe el método GetPositionForSection
.
GetPositionForSection
le ofrece la oportunidad de asignar los índices que se encuentran en la lista de índices a las secciones que se encuentren en la vista de lista. Por ejemplo, es posible que tenga una "z" en el índice, pero que no tenga una sección de tabla para cada letra, por lo que, en lugar de asignar "z" a 26, se puede asignar a 25 o 24, o a cualquier índice de sección al que se debe asignar "z".