Sviluppare Applicazioni per Android
ArrayAdapter ndroid disegna i componenti di una schermata attraverso oggetti della classe View, raccolti all'interno di oggetti ViewGroup (che possono raccogliere, a loro volta, altri oggetti View e ViewGroup). Esistono diverse specializzazioni degli oggetti detti sopra. View è la classe base per i widgets, così vengono chiamati gli elementi dell'interfaccia grafica. Alcune specializzazioni della classe View sono: la classe Button (per i bottoni), la classe TextView (per il testo), la classe ImageView (per le immagini). ViewGroup, invece, è la classe base per i layout, gli schemi che descrivono la disposizione degli oggetti sullo schermo (le activity). Alcune specializzazioni della classe ViewGroup sono: la classe LinearLayout (per un layout lineare), la classe ListView (per un layout a elenco), la classe TableLayout (per un layout tabellare). Le specializzazioni dette sopra ereditano dalle rispettive classi padre numerosi metodi e costanti. Pertanto, durante la scrittura del codice, vi invito a consultare la documentazione in linea se siete alla ricerca di metodi che non trovate documentati all'interno della classe! La presentazione di informazioni all'interno di un'activity, per alcune applicazioni, è spesso legata a una sorgente di dati. In tal caso è particolarmente importante collegare quest'ultima ai meccanismi che hanno il compito di presentare le informazioni all'interno degli oggetti ViewGroup dell'activity.
ArrayAdapter Adapter è l'interfaccia pubblica usata da Android per accedere a una sorgente di dati e istanziare oggetti View per ogni record. In questa interfaccia, pertanto, sono raccolte le firme dei metodi che permettono di gestire le azioni dette sopra. Per l'accesso a un record, ad esempio, esistono i metodi: public abstrac Obejct getitem(int position): che ritorna i dati di un record nella posizione indicata da position; public abstract long getitemid(int position): che ritorna la chiave del record nella posizione indicata da position; Per il conteggio dei record, invece, l'interfaccia prevede i metodi: public abstract getcount(): che ritorna il numero di record contenuti nell'oggetto Adapter; public abstract boolean isempty(): che ritorna true se l'oggetto Adapter non ha record, altrimenti false;
ArrayAdapter l riferimento a un oggetto View, che rappresenta un record dell'oggetto istanziato da Adapter, viene ottenuto con il metodo: public abstract View getview(int position, View convertview, ViewGroup parent): dove position indica la posizione del record all'interno della lista, convertview è il riferimento a una precedente View non più visibile (a causa dello scrolling e quindi riutilizzabile) e parent, infine, è il riferimento all'oggetto ViewGroup che contiene le singole View dei record;
ArrayAdapter Spesso la sorgente di dati da visualizzare è soggetta a variazioni e l'oggetto ViewGroup che ne permette la visualizzazione sul display va dunque aggiornato. A tale proposito l'interfaccia Adapter prevede i seguenti metodi per la gestione del suddettoevento: public abstract void registeredatasetobserver(datasetobserver observer): che registra un oggetto DataSetOberver come ascoltatore che verrà chiamato non appena si verifica una modifica ai dati; public abstract void unregisterdatasetobserver(datasetobserver observer): che cancella l'oggetto DataSetOberver impostato dal metodo visto prima, rendendo l'oggetto ViewGroup insensibile alle modifiche applicate sui dati;
ArrayAdapter AdapterView è la classe astratta che, estendendendo ViewGroup, implementa i meccanismi per la comunicazione fra un oggetto ViewGroup e un Adapter. La disposizione delle singole View viene lasciata alle successive specializzazioni di AdapterView (come ad esempio ListView, Gallery e Spinner). Un AdapterView, riassumendo, si occupa di: riempire il layout principale (dell'oggetto ViewGroup) con i dati, usando una specializzazione di Adapter (per accedere alla sorgente di dati); gestire le interazioni dell'utente con i dati presentati;
ArrayAdapter Pertanto, a seconda dello schema da adottare per le View è prevedibile aspettarsi, all'interno del package di Android, più specializzazioni e implementazioni dell'interfaccia Adapter. L'obiettivo di ognuna di queste specializzazioni è l'aggiunta di nuove firme di metodi orientati alla gestione del particolare layout. Ad esempio, per organizzare i record di una sorgente di dati secondo una lista di record, nel package di Android troviamo: la classe ListView, che estende ViewGroup (per il layout); l'interfaccia ListAdapter che estende l'interfaccia Adapter aggiungendo le firme dei metodi utili alla gestione dei dati organizzati secondo una lista scorrevole (i metodi aggiunti sono: isenabled(int row), che ritorna true se l'elemento della lista è abilitato, altrimenti torna false e areallitemsenabled(), che ritorna true se tutti gli elementi della lista sono abilitati, altrimenti torna false); la classe ArrayAdapter implementa l'interfaccia ListAdapter (potete infatti verificare la presenza dei metodi isenabled() e areallitemsenabled() all'intero della classe che effettivamente li implementa);
ArrayAdapter La classe ArrayAdapter, del package android.widget, si interfaccia con un array di oggetti (la sorgente di dati) e istanzia per ogni record una View. Esistono diversi costruttori di ArrayAdapter, come ad esempio public ArrayAdapter (Context context, int resource, int textviewresourceid, T[] object), dove: context è il riferimento a una risorsa o classe dell'applicazione; resource è il riferimento al layout dell'oggetto ViewGroup; textviewresourceid è il riferimento al layout che conterrà le View da istanziare per ogni occorrenza dell'array di oggetti; T[] è l'array di oggetti, quindi la sorgente di dati;
ArrayAdapter L'assegnazione dell'oggetto ArrayAdapter all'oggetto ListView (il contenitore per le View) avviene con il metodo setadapter(listadapter adapter). Vediamo con un esempio come applicare le cose fin qui dette. Supponiamo di voler realizzare un'applicazione in grado di gestire una lista di valori, le operazioni utili (per il momento) saranno svolte attraverso la pressione di due bottoni: uno per l'aggiunta di un nuovo elemento alla lista e uno per la cancellazione di tutti gli elementi della lista. Per l'aggiunta di un nuovo elemento, inoltre, sarà presente un'area di testo per l'input.
ArrayAdapter Realizziamo con il seguente codice xml (file main.xml) il layout principale dell'applicazione: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/mainlayout" android:layout_height="wrap_content" android:orientation="vertical"> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_height="wrap_content" android:orientation="horizontal"> <TextView android:id="@+id/labelfornewitem" android:text="new item:" android:layout_width="wrap_content" android:layout_height="wrap_content"> </TextView> <EditText android:id="@+id/newitem" android:layout_height="wrap_content"> </EditText> </LinearLayout>
ArrayAdapter android:id="@+id/addbutton" android:layout_height="wrap_content" android:text="add item" android:onclick="additem"> </Button> android:id="@+id/clearbutton" android:layout_height="wrap_content" android:text="clear all items" android:onclick="clearall"> </Button> <ListView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/viewofitems" android:layout_height="fill_parent"> </ListView> </LinearLayout>
ArrayAdapter Dovendo in seguito specificare nel codice le azioni per gli eventi legati alla pressione di uno dei due bottoni ho deciso di utilizzare, per gli elementi Button, l'attributo android:onclick. La stringa riferita da questo attributo all'oggetto istanziato in fase di inflating ha l'importante compito di indicare allo stesso il metodo da invocare alla pressione della view (in questo caso il bottone). Tali metodi, dunque, vanno implementati all'interno della classe che descrive l'activity. Il layout principale mostrato sopra prevede infine un oggetto ViewGroup, in questo caso un oggetto ListView. Sarà questo componente ad occuparsi delle View relative agli elementi aggiunti.
ArrayAdapter Ogni singola View verrà presentata secondo uno schema descritto in un file xml a parte che per il nostro esempio è il file row.xml: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/record" android:layout_height="wrap_content" > <ImageView android:id="@+id/itemimage" android:src="@drawable/item" android:layout_height="wrap_content" android:layout_width="wrap_content"> </ImageView> <TextView android:id="@+id/itemlabel" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:paddingleft="10px" android:text="information here..."> </TextView> </LinearLayout>
ArrayAdapter Ogni nuovo elemento aggiunto sarà quindi descritto da un'immagine e da un testo a lato. Non ci resta che collegare tutto all'interno del codice. In questo esempio la sorgente di dati viene descritta con un oggetto della classe ArrayList (del package java.util), i cui metodi add() e clear() permettono la gestione della lista costruita così come descritto già sopra. Tali metodi saranno allora invocati all'interno dei metodi additem() e clearall() dell'activity.
ArrayAdapter Ogni nuovo elemento aggiunto sarà quindi descritto da un'immagine e da un testo a lato. Non ci resta che collegare tutto all'interno del codice. In questo esempio la sorgente di dati viene descritta con un oggetto della classe ArrayList (del package java.util), i cui metodi add() e clear() permettono la gestione della lista costruita così come descritto già sopra. Tali metodi saranno allora invocati all'interno dei metodi additem() e clearall() dell'activity. public class ListViewActivity extends Activity { private ArrayList<String> arrayofitems; private ArrayAdapter<String> arrayofitemsadapter; @Override public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); arrayofitems=new ArrayList<String>(); ListView listview=(listview)findviewbyid(r.id.viewofitems); arrayofitemsadapter=new ArrayAdapter<String>(this,R.layout.record,R.id.itemLabel,arrayOfItems); listview.setadapter(arrayofitemsadapter); }
ArrayAdapter public void additem(view v) { EditText input=(edittext)findviewbyid(r.id.newitem); String newitem=input.gettext().tostring(); if(newitem.trim().equals((string)"")) Toast.makeText(this,"Hey... i've a null item!",toast.length_long).show(); else { arrayofitems.add(newitem); arrayofitemsadapter.notifydatasetchanged(); Toast.makeText(this,"New item in the list!",toast.length_long).show(); input.settext(""); } } public void clearall(view v) { if(!arrayofitems.isempty()) { arrayofitems.clear(); arrayofitemsadapter.notifydatasetchanged(); Toast.makeText(this,"Bye bye old items!",toast.length_long).show(); } } }
ArrayAdapter Tralasciando le fasi dedicate al setup del layout principale, all'interno del metodo oncreate() viene: creata la sorgente di dati per la lista di elementi; ottenuto un riferimento all'oggetto ListView; istanziato l'oggetto ArrayAdapter, fornendo i riferimenti ai layout (quello principale e quello di riga) e alla sorgente di dati; collegato l'oggetto ArrayAdapter all'oggetto ListView. E' particolarmente importante, nei metodi add() e clearall(), invocare sull'oggetto ArrayAdapter il metodo notifydatasetchanged() per notificare un cambiamento nella sorgente di dati (l'aggiunta di un nuovo elemento oppure la cancellazzione dell'intera lista) e provocare l'aggionamento dell'oggetto ViewGroup (ListViewArrayAdapter nell'associazione fra elementi della sorgente di dati e il layout per la View. La stringa aggiunta viene legata di default a un elemento TextView. Nessun problema, quindi, per l'esempio appena visto. Gli item aggiunti sono stringhe e una TextView è sufficiente a raccoglierli. Se l'informazione da aggiungere è strutturata diversamente occorre necessariamente sovrascrivere il metodo getview() implementato nella classe ArrayAdapter e personalizzare allora la View da restituire.
ArrayAdapter
ArrayAdapter Tralasciando le fasi dedicate al setup del layout principale, all'interno del metodo oncreate() viene: creata la sorgente di dati per la lista di elementi; ottenuto un riferimento all'oggetto ListView; istanziato l'oggetto ArrayAdapter, fornendo i riferimenti ai layout (quello principale e quello di riga) e alla sorgente di dati; collegato l'oggetto ArrayAdapter all'oggetto ListView. E' particolarmente importante, nei metodi add() e clearall(), invocare sull'oggetto ArrayAdapter il metodo notifydatasetchanged() per notificare un cambiamento nella sorgente di dati (l'aggiunta di un nuovo elemento oppure la cancellazzione dell'intera lista) e provocare l'aggionamento dell'oggetto ViewGroup (ListViewArrayAdapter nell'associazione fra elementi della sorgente di dati e il layout per la View. La stringa aggiunta viene legata di default a un elemento TextView. Nessun problema, quindi, per l'esempio appena visto. Gli item aggiunti sono stringhe e una TextView è sufficiente a raccoglierli. Se l'informazione da aggiungere è strutturata diversamente occorre necessariamente sovrascrivere il metodo getview() implementato nella classe ArrayAdapter e personalizzare allora la View da restituire.
View e Layout L'interfaccia grafica di un'applicazione Android è descritta attraverso oggetti della classe View e ViewGroup (chiamati anche widget). La descrizione può avvenire sia attraverso file xml che attraverso codice java, in quest'ultimo caso l'interfaccia grafica può essere creata o manipolata a run-time (approccio che va sotto il nome di programmazione imperativa). Il primo modo di procedere è anche detto programmazione dichiarativa, l'interfaccia grafica viene infatti dichiarata in un file xml il cui nome è l'id che ne permette il riferimento. Il metodo findviewbyid(int ResourceId) della classe Activity ritorna un riferimento a un oggetto View rintracciato, all'interno delle risorse di progetto, attraverso il suo id. In questo articolo mi occuperò per buona parte della programmazione dichiarativa delle interfacce grafiche per applicazioni Android. Prima di procedere è opportuno sottolineare un importante questione fra i due modi di programmazione offerti da Android. La programmazione dichiarativa offre notevoli vantaggi per la descrizione di interfacce grafiche e la definizione di risorse poiché usa file esterni (xml, nel caso di Android) al codice java. Ogni modifica, infatti, non richiederà la compilazione dell'intero progetto. Cosa che invece accade necessariamente nella programmazione imperativa a fronte di una modifica nel codice. L'uso di una delle due metodologie va comunque valutato in fase di progettazione dell'applicazione.
View e Layout Android raccoglie gli elementi di una interfaccia grafica attraverso oggetti della classe ViewGroup, una specializzazione della classe View. Un oggetto ViewGroup può contenere a sua volta altri oggetti View e/o ViewGroup. Un modo particolarmente utile di sintetizzare l'interfaccia grafica, indipendentemente dall'approccio usato, consiste nella sua rappresentazione gerarchica ad albero. Android disegna l'interfaccia grafica a partire dall'elemento radice di tale albero, analizzando prima il sottoalbero sinistro, quindi il nodo centrale ed infine il sottoalbero destro. La visita del suddetto albero, così come l'ho appena descritta, è detta in-order. Questo modo di procedere, disegna gli oggetti dell'albero a partire dal basso verso l'alto. In caso di sovrapposizione fra oggetti può accadare che l'ultimo oggetto disegnato copra il precedente! Il processo compiuto da Android che a partire dalla visita dell'albero genera gli oggetti dell'interfaccia, istanziandoli man mano, è detto inflating e segue un preciso algoritmo che può essere anche personalizzato.
View e Layout Anche se una view descrive l'oggetto dotandolo di dimensioni e misure per lo scostamento, l'oggetto ViewGroup che fa da contenitore ne fissa per diritto le proprietà (occupando nell'albero una posizione a un livello superiore). Ne consegue che non sempre le proprietà volute da una view vengano poi garantite dall'oggetto ViewGroup contenitore se quest'ultimo impone le proprie caratteristiche. Alla luce di quanto appena detto, allora, è preferibile assegnare le proprietà generali, quelle in comune a tutti gli elementi View e/o ViewGroup, ai nodi ViewGroup di livello superiore. Un oggetto della classe View ritorna con i metodi getleft() e gettop() le coordinate del vertice in alto a sinistra del componente da disegnare. I metodi getmeasuredheight() e getmeasuredwidth() della classe View ritornano, invece, le dimensioni desiderate dal componente, mentre i metodi getwidth() e getheight() quelle effettive. Più avanati vedremo come impostare queste proprietà all'interno di una view assegnata come layout per l'applicazione. Vi ricordo che tale assegnazione avviene grazie al metodo setcontentview() (della classe Activity), a cui va passato l'elemento radice di un oggetto ViewGroup. Il layout dell'applicazione viene disegnato, durante la visita all'albero delle View, in due passi: measure pass e layout pass. Vengono cioè invocati i metodi per l'accesso alle coordinate e alle dimensioni visti sopra. Per layout si intende, quindi, il modo di organizzare e raccogliere gli oggetti View e ViewGroup, Android ne mette a disposizioni alcuni di default: LinearLayout, RelativeLayout, TableLayout e FrameLayout.
View e Layout Ognuno di questi, come già accennato sopra, può impostare delle proprietà generali valide, poi, per tutte le view contenute. Alcune di queste sono passate agli oggetti attraverso gli attributi android:layout_height e android:layout_width per fissare, rispettivamente, l'altezza e la larghezza del layout. Possiamo caratterizzare queste proprietà con le costanti fill_parent e wrap_parent, oppure con misure fisse. La costante fill_parent indica la volontà del componente di impegnare tutto lo spazio disponibile mentre con wrap_parent il componente occuperà lo spazio minimo. Altre proprietà, infine, sono tipiche del layout e saranno prese in considerazione solo se l'oggetto creato in fase di inflating le prevede. Di seguito vi parlo dei principali layout di Android.
View e Layout LinearLayout Si tratta di un layout che dispone le view lungo la direzione stabilita dalla proprietà android:layout_orientation, che può assumere i valori horizontal (le view sono disposte su colonne) e vertical (le view sono disposte su righe). Qualora il numero di view sia tale da andare oltre le dimensioni dello schermo vi suggerisco di ricorrere all'elemento ScrollView (un oggetto ViewGroup), che aggiunge una barra laterale per lo scorrimento. Il tag da usare all'interno del file xml è LinearLayout. Ecco un esempio di LinearLayout il cui flusso è orientato orizzontalmente: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_height="fill_parent"> android:text="invia" android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content"/> android:text="cancella" android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content"/> </LinearLayout>
View e Layout
View e Layout Qui, invece, lo stesso layout con orientazione verticale: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_height="fill_parent"> android:text="invia" android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content"/> android:text="cancella" android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content"/> </LinearLayout>
View e Layout
View e Layout In questi due esempi possiamo subito apprezzare la validità della costante wrap_content. Il componente, in questi esempi un bottone, occupa lo spazio minimo che coincide con quello necessario a contenere la label del bottone. Particolarmente utile è il seguente esempio: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_height="fill_parent"> android:text="invia" android:id="@+id/button1" android:layout_height="wrap_content"/> android:text="cancella" android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content"/> </LinearLayout>
View e Layout
View e Layout Che fine ha fatto il secondo bottone? Tutto deriva dall'algoritmo usato da Android per disegnare il layout. Infatti, visitando l'albero delle view con la modalità in-order il sistema operativo in fase di inflating istanzia l'oggetto per disegnare il primo bottone assegnando per quest'ultimo tutta la larghezza messa a disposizione dal contenitore (attributo android:layout_width=fill_parent del primo bottone). Pertanto, quando si troverà a visitare l'altra view per istanziare il secondo bottone, lo spazio a disposizione è già stato consumato interamente dal componente che lo ha preceduto. Il risultato è quello in figura, il secondo bottone non è visibile nel layout! Un LinearLayou rispetta i margini delle view contenute, questi possono essere assegnati alle rispettive view attraverso gli attributi android:layout_marginbottom, android:layout_margintop, androi:layout_marginleft e android:layout_marginright per i rispettivi bordi. Mentre con l'attributo android:layout_margin possiamo assegnare un unico valore valido per tutti i bordi del componente.
View e Layout <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_height="fill_parent"> android:text="invia" android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginleft="25px" android:layout_marginright="25px"/> android:text="cancella" android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content"/> </LinearLayout>
View e Layout
View e Layout Un LinearLayout permette l'assegnazione di un opportuno peso da assegnare a una view attraverso l'attributo android:layout_weight. L'intero, oppure il decimale, dato al componente come peso lo autorizza a espandersi fino a impegnare lo spazio offerto dal contenitore, in relazione agli altri pesi dati agli altri componenti. In altre parole, a seconda dell'orientazione del layout, lo spazio lungo le righe o le colonne viene ripartito fra le view del layout. La view con il peso più grande occuperà meno spazio (oppure, quella con il decimale più grande occuperà più spazio, usate decimali compresi fra 0 ed 1). <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_height="fill_parent"> android:text="invia" android:id="@+id/button1" android:layout_height="wrap_content" android:layout_weight="1"/> android:text="cancella" android:id="@+id/button2" android:layout_height="wrap_content" android:layout_weight="2"/> </LinearLayout>
View e Layout
View e Layout Un LinearLayout, infine, permette di stabilire attraverso l'attributo android:layout_gravity dove e come inserire una view nel layout. I possibili valori sono: top, bottom, left, right, center_vertical, fill_vertical, center_horizontal, fill_horizontal, center, fill, clip_vertical e clip_horizontal. In altre parole, lo spazio che il contenitore mette a disposizione della view verrà impegnato seguendo il significato di uno degli attributi detti sopra. <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_height="fill_parent"> <LinearLayout android:orientation="horizontal" android:layout_height="fill_parent" android:layout_weight="1"> android:text="invia" android:id="@+id/button1" android:layout_height="wrap_content" android:layout_weight="1"/>
View e Layout android:text="cancella" android:id="@+id/button2" android:layout_height="wrap_content" android:layout_weight="2"/> </LinearLayout> <LinearLayout android:orientation="vertical" android:layout_height="fill_parent" android:layout_weight="1"> <EditText android:text="messaggio..." android:id="@+id/text1" android:layout_height="wrap_content" android:layout_gravity="center_vertical"/> </LinearLayout> </LinearLayout>
View e Layout
View e Layout TableLayout Con TableLayout le view figlie vengono posizionate all'interno di righe e colonne di una tabella. Ogni riga può avere un certo numero di colonne, anche nullo se necessario. Una cella può inoltre descrivere altri oggetti ViewGroup. Il tag da usare all'interno del file xml è TableLayout. Per l'aggiunta di una riga, invece, va usato TableRow. Gli attributi android:layout_column e android:layout_span permettono, rispettivamente, di assegnare una view ad una precisa colonna della tabella oppure di estendere una view anche ad altre celle successive.
View e Layout <?xml version="1.0" encoding="utf-8"?> <TableLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_height="fill_parent" android:stretchcolumns="0,1,2,3"> <TableRow android:layout_height="fill_parent" android:id="@+id/row1"> <TextView android:text="risultato..." android:id="@+id/output" android:layout_gravity="center_horizontal" android:layout_column="0" android:layout_span="4" android:textstyle="bold"/> </TableRow> <TableRow android:layout_height="fill_parent" android:id="@+id/row2"> android:text="9" android:id="@+id/button9" android:layout_weight="0.25"/>
android:text="8" android:id="@+id/button8" android:layout_weight="0.25"/> android:text="7" android:id="@+id/button7" android:layout_weight="0.25"/> android:text="+" android:id="@+id/buttonplus" android:layout_weight="0.25"/> </TableRow> <TableRow android:layout_height="fill_parent" android:id="@+id/row3"> android:text="6" android:id="@+id/button6" android:layout_weight="0.25"/> android:text="5" android:id="@+id/button5" android:layout_weight="0.25"/> View e Layout
android:text="4" android:id="@+id/button4" android:layout_weight="0.25"/> android:text="-" android:id="@+id/buttonminus" android:layout_weight="0.25"/> </TableRow> <TableRow android:layout_height="fill_parent" android:id="@+id/row4"> android:text="3" android:id="@+id/button3" android:layout_weight="0.25"/> android:text="2" android:id="@+id/button2" android:layout_weight="0.25"/> android:text="1" android:id="@+id/button1" android:layout_weight="0.25"/> View e Layout
android:text="*" android:id="@+id/buttonmul" android:layout_weight="0.25"/> </TableRow> <TableRow android:layout_height="fill_parent" android:id="@+id/row5"> android:text="(" android:id="@+id/buttonpsx" android:layout_weight="0.25"/> android:text="0" android:id="@+id/button0" android:layout_weight="0.25"/> android:text=")" android:id="@+id/buttonpdx" android:layout_weight="0.25"/> android:text=":" android:id="@+id/buttondiv" android:layout_weight="0.25"/> </TableRow> View e Layout
<TableRow android:layout_height="fill_parent" android:id="@+id/row6"> android:text="." android:id="@+id/buttonfordecimal"/> android:text="c" android:id="@+id/buttonforclear"/> android:text="=" android:id="@+id/buttonfortotal" android:layout_column="2" android:layout_span="2"/> </TableRow> </TableLayout> View e Layout
View e Layout RelativeLayout Con l'oggetto RelativeLayout (usate l'omonimo tag nel file xml) possiamo descrivere un layout le cui view vengono disposte sullo schermo in relazione ad altre view appartenenti allo stesso layout. I possibili valori sono molti e tutti indicano l'id della view di riferimento, oppure abilitano un particolare allineamento. Una view può essere allineata rispetto al contenitore attraverso gli attributi android:layout_alignparentright, android:layout_alignparentleft, android:layout_alignparenttop e android:layout_alignparentbottom, a cui va passato il valore booleano true. Altre possibili soluzioni sono: android:layout_aligntop, android:layout_alignbotton, android:layout_alignleft e android:layout_alignright a cui va passato l'id della view di riferimento. Per una lista completa dei possibili attributi vi invito a consultare la guida in linea.
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/layout1" android:layout_height="fill_parent"> <TextView android:layout_height="wrap_content" android:layout_width="wrap_content" android:id="@+id/label" android:text="select a value:" android:layout_alignparentleft="true" /> android:layout_width="wrap_content" android:id="@+id/buttonnext" android:text="next" android:layout_below="@+id/label" android:layout_height="wrap_content" android:layout_alignleft="@+id/label" android:layout_alignright="@+id/label"/> <SeekBar android:layout_height="wrap_content" android:id="@+id/bar" android:layout_torightof="@+id/label" android:layout_aligntop="@+id/label" android:layout_alignbottom="@+id/label"/> </RelativeLayout>
View e Layout FrameLayout E' l'oggetto (classe FrameLayout, usate l'omonimo tag per segnalarlo nel file xml) per layout più semplice, raccoglie le view disegnandole a partire dal vertice in alto a sinistra dello schermo. Non esistono metodi e attributi capaci di intervenire sulla collocazione delle view che verranno inevitabilmente sovrapposte fra loro. L'attributo android:visibility permette di nascondere una view, attivabile in un secondo momento attraverso il metodo setvisibility() a cui va passata la costante FrameLayout.VISIBLE. Con la costante FrameLayout.GONE, invece, disabilitiamo la visione di una view.
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_height="fill_parent"> <TextView android:id="@+id/frame1" android:text="android" android:id="@+id/text1" android:layout_height="wrap_content"/> <TextView android:id="@+id/frame2" android:text=" Android" android:id="@+id/text2" android:layout_gravity="center_horizontal" android:layout_height="wrap_content"/> <TextView android:id="@+id/frame3" android:text=" Android" android:id="@+id/text3" android:layout_height="wrap_content" android:visibility="gone"/> </FrameLayout>
View e Layout Nell'esempio visto sopra possiamo notare l'inutulità dell'attributo android:layout_gravity. Il FrameLayout, come detto sopra, non permette di agire sul posizionamento delle View. La sovrapposizione è adesso evidente!
GridView
GridView GridView è un oggetto ViewGroup, come ListView. La particolarità di questo componente è ovviamente il layout applicato agli elementi di una sorgente di dati. GridView, infatti, dispone i dati all'interno di una griglia scorrevole. L'oggetto può essere dichiarato all'interno dei file xml, attraverso l'omonimo tag GridView, che all'interno del codice Java, attraverso l'omonima classe del package android.widget. GridView dispone di diversi attributi per la regolazione delle proprietà di layout: android:columnwidth specifica la larghezza delle colonne, la stessa cosa viene fatta all'interno del codice con il metodo setcolumnwidth(int); android:horizontalspacing specifica lo spazio vuoto fra le righe della griglia, nel codice Java va usato il metodo sethorizontalspacing(int); android:verticalspacing specifica lo spazio vuoto fra le colonne della griglia, nel codice Java va usato il metodo setverticalspacing(int); android:stretchmode specifica come gli oggetti devono occupare lo spazio messo a disposizione dalla griglia, nel codice Java va usato il metodo setstretchmode(int) a cui va passata una delle possibili costanti definite all'interno della classe (come ad esempio NO_STRETCH e STRETCH_COLUMN_WIDTH);
GridView Nell'esempio che vi propongo l'oggetto GridView viene utilizzato per la raccolta di immagini che compongono un puzzle. Il click su una immagine riassegna all'oggetto nella griglia una nuova immagine, prendendola dal set di pezzi che compone il puzzle. Il layout principale dell'activity descrive un bottone per il reset (che riassegna nuovi tasselli alla griglia) e la griglia per le immagini. Vi riporto il pezzo di codice che descrive l'oggetto GridView: xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/puzzlegridview" android:layout_height="fill_parent" android:columnwidth="62dp" android:numcolumns="3" android:verticalspacing="2dp" android:horizontalspacing="2dp" android:stretchmode="columnwidth" android:gravity="center"
GridView public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); GridView gridview=(gridview) findviewbyid(r.id.puzzlegridview); adapterforimage=new ImageAdapter(this); gridview.setadapter(adapterforimage); gridview.setonitemclicklistener(new OnItemClickListener() { } }); } public void onitemclick(adapterview<?> parent, View v, int positi on, long id) { adapterforimage.moveimage(position); adapterforimage.notifydatasetchanged();
GridView Dopo l'assegnazione del layout principale (con setcontentview()) il codice procede con la ricerca della view dell'oggetto GridView, a cui viene assegnato l'adapter per la sorgente di dati. Viene, poi, descritto il gestore per gli eventi (che in questo esempio viene chiamato al tocco della View, metodo onitemclicklistener()). Particolarmente importante è il metodo getview() della classe ImageAdapter (che estende BaseAdapter, una classe astratta che è alla base di altri Adapter). Il compito di questo metodo è la restituzione della View da inserire nella griglia. public View getview(int position, View convertview, ViewGroup parent) { ImageView imageviewofpiece; if (convertview==null) { imageviewofpiece=new ImageView(mContext); imageviewofpiece.setlayoutparams(new GridView.LayoutParams(100,100)); imageviewofpiece.setscaletype(imageview.scaletype.fit_xy); imageviewofpiece.setpadding(1,1,1,1); } else imageviewofpiece=(imageview)convertview; imageviewofpiece.setimageresource(table[position]); return imageviewofpiece; }
GridView Per far avanzare le immagini toccate a quelle successive (in modo pseudo-casuale) ho scritto il seguente metodo: public void moveimage(int position) { if(i==pieces.length-1) i=0; else i++; table[position]=pieces[i]; nclick++; if(nclick==3) { i=(int)(math.random()*(pieces.length-1)); nclick=0; } }
GridView I primi tre click spostano di una posizione l'indice che punta alla successiva immagine dell'array. Dopo il terzo click l'indice assume un valore casuale, incrementato di una posizione per i nuovi prossimi tre click e così via... Viene effettuato anche un controllo ciclico sull'indice che non deve mai superare la capacità dell'array (diminuita di uno). Questo il puzzle completo: