~~REVEAL~~
====== Utilitzant ListView a Android ======
Aquest article segueix del principal [[Android]] en aquesta wiki.
* ''ListView'' és un //widget// obsolet.
* Es manté per //backward compatibility//.
* //Widget// recomanat actual: [[Android RecyclerView]] (però més complicat d'utilitzar).
Referències:
* Article [[Android]] en aquesta wiki.
* [[Android RecyclerView]] en aquesta wiki.
* [[https://github.com/codepath/android_guides/wiki/Using-an-ArrayAdapter-with-ListView|Tutorial d'utilització de ListView d'Android]]
* [[https://gist.github.com/emieza/b918305010be198c037252f402821fef|Codi d'exemple més simple d'utilització d'una ListView]]
{{tag> #Dam #DamMp08 #DamMp08Uf1 #DamMp08Uf01 android MVC java mobile}}
==== Perquè és complicada una ListView? ====
Com veurem ara a l'hora d'implementar-ho, la ''ListView'' resulta una mica més complicada del què ens esperaríem. Això es deu a la particularitat dels dispositius mòbils de la seva escassa RAM. No podem, doncs, implementar una llista amb gran quantitat d'entrades que poden contenir pesants elements multimèdia com fotos d'alta resolució, i que es carreguin totes a la RAM directament. Ens **cal uns tipus de ''Views'' que carreguin només les dades que s'estan visualitzant, i que generin nous ítems només quan l'usuari faci el //scroll//**.
===== Reciclatge d'elements gràfics =====
{{recycling-views.png?direct}}
===== Reciclatge d'elements gràfics (2) =====
{{recycling-items.jpg?direct}}
Com podem veure a la imatge, el sistema farà un **reciclat dels ítems que ha generat prèviament i que ja no estan visibles**, estalviant RAM de forma global.
\\
===== MVC ampliat i Adapter =====
{{ listview-adapter.jpg?direct }}
* Tota ''View'' intenta seguir un paradigma Model - Vista - Controlador.
* En altres entorns segurament trobaríem una connexió més simple:
* Model de dades (Ex. ''ArrayList'') connectat directament a la ''ListView''.
* El codi de Controlador podria estar en altres objectes de l'aplicació (com la ''Activity'') o en una classe derivada de la ''ListView''.
* En Android, degut a que necessitem la gestió del reciclatge dels ítems gràfics, ens **apareix un element extra anomenat ''Adapter''**. Aquest és l'element que coneix la ''View'' i el nostre Model i que ha de saber com **reciclar els ítems per a l'estalvi de RAM**.
==== Layouts ====
Per a cada element (item) de la ''ListView'' es necessita un //layout// personalitzat. El pots crear tu mateix, o simplement adoptar uns //layouts// prefabricats per als items. Al [[https://developer.android.com/reference/android/R.layout|arxiu de recursos android.R.layout]] trobaràs alguns com per exemple [[https://github.com/codepath/android_guides/wiki/Using-an-ArrayAdapter-with-ListView#using-a-basic-arrayadapter|android.R.layout.simple_list_item_1]] que et pot estalviar feina per als casos més habituals.
\\
===== Exemple ArrayList simplificat =====
* Referència: [[https://gist.github.com/emieza/b918305010be198c037252f402821fef|codi d'exemple amb ArrayAdapter]].
* Exemple d'una taula de rècords amb nom del jugador i intents.
* Utilitza un ''list_item'' personalitzat.
* No creem una classe derivada de ''ArrayAdapter'': simplement instanciem un de genèric (línia 43) i sobreescrivim el mètode ''getView''.
**Segueix abaix per veure el codi**
==== Connexió (simplificada) Model - Adapter - View ====
public class MainActivity extends AppCompatActivity {
ArrayList records; // Model Taula de records (ArrayList)
ArrayAdapter adapter; // l'Adapter serà l'intermediari amb la ListView
@Override
protected void onCreate(Bundle savedInstanceState) {
// Creem llista de records, etc...
// Inicialitzem l'Adapter i el connectem amb el Model (records)
adapter = new ArrayAdapter( this, R.layout.list_item, records )
{
@Override
public View getView(int pos, View convertView, ViewGroup container)
{
// "inflem" el Layout a partir de l'XML
convertView = getLayoutInflater().inflate(R.layout.list_item, container, false);
// transferim dades del Model als widgets per a l'element en la posició "pos"
((TextView) convertView.findViewById(R.id.nom)).setText(getItem(pos).nom);
}
};
// Connectem Adapter amb la View
ListView lv = (ListView) findViewById(R.id.recordsView);
lv.setAdapter(adapter);
// Creem la resta d'elements de l'app...
}
}
==== Codi complert taula de rècords ====
package com.example.listilla;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.*;
import androidx.appcompat.app.AppCompatActivity;
import java.util.ArrayList;
public class MainActivity extends AppCompatActivity {
// Model: Record (intents=puntuació, nom)
class Record {
public int intents;
public String nom;
public Record(int _intents, String _nom ) {
intents = _intents;
nom = _nom;
}
}
// Model = Taula de records: utilitzem ArrayList
ArrayList records;
// ArrayAdapter serà l'intermediari amb la ListView
ArrayAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Inicialitzem model
records = new ArrayList();
// Afegim alguns exemples
records.add( new Record(33,"Manolo") );
records.add( new Record(12,"Pepe") );
records.add( new Record(42,"Laura") );
// Inicialitzem l'ArrayAdapter amb el layout pertinent
adapter = new ArrayAdapter( this, R.layout.list_item, records )
{
@Override
public View getView(int pos, View convertView, ViewGroup container)
{
// getView ens construeix el layout i hi "pinta" els valors de l'element en la posició pos
if( convertView==null ) {
// inicialitzem l'element la View amb el seu layout
convertView = getLayoutInflater().inflate(R.layout.list_item, container, false);
}
// "Pintem" valors (també quan es refresca)
((TextView) convertView.findViewById(R.id.nom)).setText(getItem(pos).nom);
((TextView) convertView.findViewById(R.id.intents)).setText(Integer.toString(getItem(pos).intents));
return convertView;
}
};
// busquem la ListView i li endollem el ArrayAdapter
ListView lv = (ListView) findViewById(R.id.recordsView);
lv.setAdapter(adapter);
// botó per afegir entrades a la ListView
Button b = (Button) findViewById(R.id.button);
b.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
for (int i=0;i<3;i++) {
records.add(new Record(100, "Anonymous"));
}
// notificar l'adapter dels canvis al model
adapter.notifyDataSetChanged();
}
});
}
}
===== Exercicis =====
==== Exercici 1 ====
Implementa el codi d'exemple en un nou projecte anomenat "Listilla".
* Crea nou projecte amb una //empty activity//.
* Substitueix el codi a ''ActivityMain'' per l'exemple.
* Arregla el ''package'' perquè concordi amb el teu projecte.
* Afegeix al ''activity_main.xml'' una ''ListView'' anomenada ''recordsView''.
* Crea un nou //layout// amb el nom ''list_item.xml'' que serà el //placeholder// per cada element de la llista. Pots crear-ho amb File -> New -> Layout Resource File
* Transforma el seu layout per defecte a LinearLayout.
* Afegiex 2 ''TextView'' amb IDs ''nom'' i ''intents''.
* Afegeix un botó al //layout// ''activity_main.xml'' amb ID = ''button''. Servirà per afegir ítems al ''ListView'' i comprovar el //scroll// del ''ListView''.
==== Exercici 2 ====
Randomitza la generació d'entrades a la taula, generant nº d'intents variats i noms i cognoms mesclant-los de dues llistes d'uns 15 o 20 elements.
==== Exercici 3 ====
Afegeix una imatge als elements de la llista (imatge fixa). Per fer-ho hauràs de:
* Ves a la view de projecte de l'Android Studio. Visualitza la carepta res -> drawable
* Importar una imatge arrossegant-la dins de ''Drawable''.
* Modificar el ''list_item.xml'' i afegir-hi una ''ImageView'' amb la imatge anterior.
* Modifica el //layout// del ''list_item'' perquè et quedi com la imatge suggerida adjunta.
* Pista: pots combinar diversos //LinearLayout// horitzontals i verticals per aconseguir el resultat desitjat.
* Afegeix diverses imatges als //resources// i aleatoritza l'assignació d'imatges a cada element ''Record''.
{{ android:listilla1.png?200 }}
==== Exercici 4 ====
Afegeix un botó que ordeni la llista del model, i que refresqui la ''ListView''.