~~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. \\ ==== Codi taula de rècords ==== Fixeu-vos en què: * Hi ha els 3 elements: records (model) <-> adapter (ArrayAdapter) <-> listView * L'''adapter'' es pot crear com una classe derivada, o bé un **objecte particularitzat**. En aquest cas és la 2a opció (objecte particularitzat "inline"). * Dintre de ''adapter.getView'' es realitza el **reciclatge**: si ens ve un objecte ''null'', l'inicialitzem amb el ''LayoutInflater''. Si no és ''null'', el reciclem sobreescrivint i modificant les dades. class MainActivity : AppCompatActivity() { // Model: ArrayList de Record (intents=puntuació, nom) class Record(var intents: Int, var nom: String) var records: ArrayList = ArrayList() // ArrayAdapter serà l'intermediari amb la ListView lateinit var adapter: ArrayAdapter override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() setContentView(R.layout.activity_main) ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets -> val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom) insets } // Afegim alguns exemples records.add(Record(33, "Manolo")) records.add(Record(12, "Pepe")) records.add(Record(42, "Laura")) // Inicialitzem l'ArrayAdapter amb el layout pertinent adapter = object : ArrayAdapter(this,R.layout.list_item,records) { override fun getView(pos: Int, convertView: View?, container: ViewGroup): View { // getView ens construeix el layout i hi "pinta" els valors de l'element en la posició pos var convertView = convertView if (convertView == null) { // inicialitzem l'element la View amb el seu layout convertView = getLayoutInflater().inflate(R.layout.list_item, container, false) } // pintem imatge val bitmap = BitmapFactory.decodeStream( assets.open("ieti_logo.png") ) convertView.findViewById(R.id.imageView).setImageBitmap( bitmap ) // "Pintem" valors (quan es refresca) convertView.findViewById(R.id.nom).text = getItem(pos)?.nom convertView.findViewById(R.id.intents).text = getItem(pos)?.intents.toString() return convertView } } // busquem la ListView i li endollem l'ArrayAdapter val lv = findViewById(R.id.recordsView) lv.setAdapter(adapter) } } 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 al layout 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 ==== Afegeix un botó **Afegir rècord** que ens ofereixi un ''Dialog'' per entrar nom i rècord. ==== Exercici 3 ==== Afegeix una imatge als elements de la llista (imatge fixa): {{ android:listilla1.png?200 }} Solució 1: * Afegeix una ''ImageView'' amnb ID "imageView" al ''list_item.xml''. * Arranja el //layout// perquè quedi com a la imatge anterior aproximadament. * Afegeix la carpeta ''app/src/main/**assets**'' al projecte. * Afegeix una foto arrossegant-la sobre la vista de projecte d'Android Studio. * La podràs fer servir amb el codi de l'exemple: val bitmap = BitmapFactory.decodeStream( assets.open("ieti_logo.png") ) convertView.findViewById(R.id.imageView).setImageBitmap( bitmap ) Solució 2: * 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''. ==== Exercici 4 ==== Afegeix un botó que ordeni la llista del model, i que refresqui la ''ListView''.