lunedì 21 marzo 2011

Android: arrayAdapter

L'utilità delle activity, sia per la presentazione dei dati che per l'interazione con l'utente, è stata più volte messa in evidenza nei precedenti articoli. Android 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.
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;
Il 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;
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;
L'interfaccia prevede la firma di altri metodi, vi consiglio pertanto di consultare la documentazione in linea.
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;
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);
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;
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. 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:layout_width="fill_parent"
android:orientation="vertical">
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
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_width="fill_parent"
android:layout_height="wrap_content">
</EditText>
</LinearLayout>
<Button
android:id="@+id/addButton"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:text="Add item"
android:onClick="addItem">
</Button>
<Button
android:id="@+id/clearButton"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
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_width="fill_parent"
android:layout_height="fill_parent">
</ListView>
</LinearLayout>
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. 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"
android:layout_width="fill_parent">
<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_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:paddingLeft="10px"
android:text="Information here...">
</TextView>
</LinearLayout>
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.
package android.mylistview;

import java.util.ArrayList;
import android.os.Bundle;
import android.app.Activity;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.Toast;

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);
}

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();
}
}

}
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. Vedremo a breve come fare anche questo. Per adesso vi lascio lo screenshot dell'applicazione (provata sia sull'emulatore che sul mio dispositivo Android):


Qui, trovate l'archivio compresso con il codice sorgente dell'applicazione. Attraverso il codice QR che segue potete, invece, installare l'applicazione sul vostro dispositivo Android:

qrcode

sabato 12 marzo 2011

Android: 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.
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.
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.
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.

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_width="fill_parent"
android:layout_height="fill_parent">
<Button
android:text="Invia"
android:id="@+id/Button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Button
android:text="Cancella"
android:id="@+id/Button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>


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_width="fill_parent"
android:layout_height="fill_parent">
<Button
android:text="Invia"
android:id="@+id/Button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Button
android:text="Cancella"
android:id="@+id/Button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>


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_width="fill_parent"
android:layout_height="fill_parent">
<Button
android:text="Invia"
android:id="@+id/Button1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
<Button
android:text="Cancella"
android:id="@+id/Button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>


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.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<Button
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"/>
<Button
android:text="Cancella"
android:id="@+id/Button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>


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_width="fill_parent"
android:layout_height="fill_parent">
<Button
android:text="Invia"
android:id="@+id/Button1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"/>
<Button
android:text="Cancella"
android:id="@+id/Button2"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="2"/>
</LinearLayout>


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_width="fill_parent"
android:layout_height="fill_parent">
<LinearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1">
<Button
android:text="Invia"
android:id="@+id/Button1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"/>
<Button
android:text="Cancella"
android:id="@+id/Button2"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="2"/>
</LinearLayout>
<LinearLayout
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1">
<EditText
android:text="Messaggio..."
android:id="@+id/text1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"/>
</LinearLayout>
</LinearLayout>


Nell'ultimo visto possiamo apprezzare la possibilità di nidificare più layout all'interno di un 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.

<?xml version="1.0" encoding="utf-8"?>
<TableLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:stretchColumns="0,1,2,3">
<TableRow
android:layout_width="fill_parent"
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_width="fill_parent"
android:layout_height="fill_parent"
android:id="@+id/row2">
<Button
android:text="9"
android:id="@+id/Button9"
android:layout_weight="0.25"/>
<Button
android:text="8"
android:id="@+id/Button8"
android:layout_weight="0.25"/>
<Button
android:text="7"
android:id="@+id/Button7"
android:layout_weight="0.25"/>
<Button
android:text="+"
android:id="@+id/ButtonPlus"
android:layout_width="fill_parent"
android:layout_weight="0.25"/>
</TableRow>
<TableRow
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="@+id/row3">
<Button
android:text="6"
android:id="@+id/Button6"
android:layout_weight="0.25"/>
<Button
android:text="5"
android:id="@+id/Button5"
android:layout_weight="0.25"/>
<Button
android:text="4"
android:id="@+id/Button4"
android:layout_weight="0.25"/>
<Button
android:text="-"
android:id="@+id/ButtonMinus"
android:layout_weight="0.25"/>
</TableRow>
<TableRow
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="@+id/row4">
<Button
android:text="3"
android:id="@+id/Button3"
android:layout_weight="0.25"/>
<Button
android:text="2"
android:id="@+id/Button2"
android:layout_weight="0.25"/>
<Button
android:text="1"
android:id="@+id/Button1"
android:layout_weight="0.25"/>
<Button
android:text="*"
android:id="@+id/ButtonMul"
android:layout_weight="0.25"/>
</TableRow>
<TableRow
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="@+id/row5">
<Button
android:text="("
android:id="@+id/ButtonPsx"
android:layout_weight="0.25"/>
<Button
android:text="0"
android:id="@+id/Button0"
android:layout_weight="0.25"/>
<Button
android:text=")"
android:id="@+id/ButtonPdx"
android:layout_weight="0.25"/>
<Button
android:text=":"
android:id="@+id/ButtonDiv"
android:layout_weight="0.25"/>
</TableRow>
<TableRow
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="@+id/row6">
<Button
android:text="."
android:id="@+id/ButtonForDecimal"/>
<Button
android:text="C"
android:id="@+id/ButtonForClear"/>
<Button
android:text="="
android:id="@+id/ButtonForTotal"
android:layout_column="2"
android:layout_span="2"/>
</TableRow>
</TableLayout>


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_width="fill_parent"
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"
/>
<Button
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_width="fill_parent"
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>


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_width="fill_parent"
android:layout_height="fill_parent">
<TextView
android:id="@+id/frame1"
android:text="Android"
android:id="@+id/text1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/frame2"
android:text=" Android"
android:id="@+id/text2"
android:layout_gravity="center_horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/frame3"
android:text=" Android"
android:id="@+id/text3"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:visibility="gone"/>
</FrameLayout>


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!

giovedì 10 marzo 2011

Intent e intent-filter

Il concetto di intent è, a mio avviso, il concetto più interessante introdotto da Google per la programmazione di applicazioni su Android. Un intent è un oggetto (generato dall'omonima classe Intent, nel package android.content) che descrive un'azione da far effettuare a un componente su un insieme di dati. Si tratta, in altre parole, di un meccanismo che consente, attraverso lo scambio di oggetti Intent, di avviare altri componenti della stessa applicazione oppure, cosa assai più interessante, di avviare componenti di altre applicazioni!
Come già detto in precedenza, le activity di un'applicazione realizzano una oppure tutte le funzionalità previste per l'interazione con l'utente. Molto dipende dalla complessità dell'applicazione e da come essa viene scritta dal programmatore. Un'activity di un'applicazione può realizzare, con la dovuta efficienza, una precisa funzionalità al punto che la stessa sia poi richiesta all'interno di altre applicazioni.
Se sul dispositivo esiste già un componente capace di portare a termine l'azione necessaria all'applicazione, il programmatore, anziché scrivere altro codice, può riciclare quest'ultimo attraverso l'invocazione di un intent. La risoluzione di un intent può inoltre stabilire la presenza di più componenti capaci di portare a termine la stessa azione. In tal caso verrà offerto all'utente la possibilità di decidere quale componente attivare per far proseguire l'applicazione.
Un intent può avviare, con metodi diversi, activity, service o broadcast receiver. In questo articolo mi occuperò dei metodi e dei meccanismi che Android mette a disposizione del programmatore per l'avvio di activity.

E' possibile ricondurre un'activity in una delle seguenti classi:
  • intent espliciti: sono intent che avviano un componente già noto, rintracciandolo attraverso il nome a lui assegnato;
  • intent impliciti: sono intent che tentano di avviare un componente le cui caratteristiche coincidono con quelle descritte nell'intent stesso;
Il meccanismo risolutivo per gli intent impliciti va sotto il nome di intent resolution. Per stabilire i migliori componenti presenti sul dispositivo e capaci di aiutare l'activity chiamante, il meccanismo di intent resolution cerca di accoppiare le caratteristiche date all'oggetto Intent con quelle dichiarate dalle activity nel proprio manifesto (il file AndroidManifest.xml), con elementi intent-filter.
Gli intent-filter descrivono, allora, le competenze e le capacità delle activity installate sul dispositivo. Un activity senza intent-filter riceverà, pertanto, solo intent espliciti.
L'istanza di un oggetto Intent è caratterizzata dai seguenti campi:
  • ComponentName: è il nome del componente che dovrebbe ricevere l'intent e gestirne, eventualmente, i contenuti extra (spiegherò in avanti come). Si tratta di un parametro usato, quindi, per l'invocazione di intent espliciti. E' possibile passarlo a un costruttore oppure impostarlo in un secondo momento, attraverso i metodi setComponent(), setClass() oppure setClassName(). Un intent esplicito verrà sempre preferito a un intent implicito, ignorando, se presenti nell'oggetto, tutte le altre proprietà in esso contenute. La classe ComponentName offre diversi costruttori (come ad esempio ComponentName(String package_name,String class_name), ComponentName(Context package,String class_name) e ComponentName(Context package, Class class)) ognuno di questi permette di identificare uno specifico componente di un'applicazione. Il metodo getComponent() ritorna il ComponentName impostato per l'intent;
  • Action: rappresenta, con una stringa, l'azione da eseguire. Nella classe Intent sono definite molte azioni di default e altre possono essere aggiunte dal programmatore. I metodi setAction() e getAction() permettono, rispettivamente, di impostare e leggere l'azione espressa nell'intent. Per un elenco completo delle costanti di default vi invito a consultare la documentazione in linea della classe Intent. E' preferibile rispettare, per la descrizione dell'action, la seguente sintassi: package_name.intent.action.ACTION. Alcune action più usate sono ACTION_MAIN (per avviare un'activity segnata come activity principale) e ACTION_EDIT (a cui corrisponde l'azione per la modifica dei dati passati). Un'activity dichiara nel proprio manifesto le azioni che è in grado di gestire attraverso elementi action, a cui va passato il parametro action:name con la stringa dell'azione;
  • Data: indica l'indirizzo (con un URI) e il tipo di dati su cui agire. Può essere impostato con setData(), setType() o setDataAndType(). Le activity dichiarano nel loro manifesto (con elementi data e attributo android:mimeType) le tipologie di dati che sono in grado di manipolare. Il tipo di dati permette di inquadrare meglio l'azione da effettuare che altrimenti rimarrebbe generica. Ad esempio, con l'azione ACTION_EDIT si possono editare immagini e/o testo. Se però indichiamo nel manifesto la proprietà android:mimeType=image/* per l'elemento data del filter-intent ne aumentiamo sicuramente la descrizione (in questo caso l'azione ACTION_EDIT è dunque orientata alla modifica di immagini). Il riferimento ai dati è specificato per mezzo della sintassi scheme://host:port/path. Ogni singolo elemento della sintassi appena vista prevede il proprio attributo da sistemare all'interno del manifesto (se necessario). Esistono infatti, per l'elemento data, gli attributi android:host, android:port, android:path e android:scheme. L'attributo host e port formano assieme l'authority e permette di individuare il content provider;
  • Category: permette di suggerire nell'intent le activity in base al compito che queste hanno all'interno di altre applicazioni. Come per le azioni, la classe Intent definisce molte categorie. Vi invito pertanto a vedere un elenco completo delle categorie disponibili sulla guida in linea per Android. Ad esempio, le activity che figurano all'interno del launcher di Android (quelle che avviano l'applicazione, con l'azione ACTION_MAIN) hanno tutte la categoria CATEGORY_LAUNCHER. I metodi addCategory() e getCategory() permettono, rispettivamente, di aggiungere o leggere il nome di una categoria. La categoria di un un'activity viene dichiarata all'interno del manifesto attraverso l'elemento category e l'attributo android:category (che contiene la stringa che identifica la categoria);
  • Extras: sono coppie chiave-valore associate all'intent attraverso il metodo putExtras(Bundle extras). L'activity ricevente può estrarre i valori aggiunti in fase di richiesta con il metodo getExtras(). La classe Intent dispone di molti metodi set, il valore inserito viene legato a una chiave che ne permette in seguito la restituzione, con uno dei metodi get (dipende dal tipo aggiunto all'intent). Possiamo assegnare all'intent anche un oggetto a patto di dichiarare la classe dell'oggetto come parte dell'interfaccia Serializable (e usare il metodo putExtras(String key,Serializable obj)). Trovate un elenco completo dei metodi per gli extras nella documentazione in linea di Android;
  • Flags: sono impostazioni di vario tipo usate dal sistema operativo per caratterizzare l'avvio di un'activity. Ogni impostazione viene passata con il metodo setFlags(), a cui va passata una costante (definita nella classe Intent) che la rappresenta. Come al solito, trovate nella guida in linea un elenco completo dei flag. Quelli più usati sono FLAG_ACTIVITY_SINGLE_TOP (che permette di non avviare una nuova istanza per l'activity se già presente, l'activity verrà riciclata) e FLAG_ACTIVITY_NEW_TASK (che avvia l'activity all'interno di un nuovo task che ha la stessa affinità);
Dopo aver caratterizzato un intent con le proprietà volute, possiamo avviare la sua risoluzione (che avverrà a run-time) attraverso il metodo startActivity(). Ecco, ad esempio, un pezzo di codice che crea un intent esplicito è lo avvia:
Intent helpIntent=new Intent(requestActivity.this,helpActivity.class);
startActivity(intent);
Trattandosi di un intent esplicito occorre precisare nella richiesta il componente che la riceverà (il secondo parametro passato al costruttore). In questo esempio, invece:
Intent helpIntent=new Intent();
helpIntent.setAction(android.intent.action.ACTION_EDIT);
helpIntent.putExtras(message,userText);
helpIntent.setType(text/plain);
startActivity(helpIntent);
viene tentato di avviare un intent implicito, il componente dovrà avere la capacità di editare un testo.
Il meccanismo di intent-resolution analizza esclusivamente i campi Action, Data e Category dell'oggetto Intent. Gli attributi Extras e Flag non influenzano la ricerca. L'intent-resulotion entra gioco solo se l'intent invocato è di tipo implicito. Per consegnare la richiesta descritta nell'intent a un componente vengono controllati i tre campi dati detti sopra, il test coinvolgerà inevitabilmente tutti campi dati presenti nell'intent-filter. Poiché un elemento intent-filter può contenere più elementi action, category e data, il test sui campi dati sarà superato con successo solo se le proprietà dell'oggetto Intent coincidono con almeno un elemento dell'intent-filter. Ad esempio, se l'intent-filter di un componente descrive più action, il test sul campo dati Action verrà superato solo se l'azione nell'oggetto Intent è fra gli intent-filter del componente (ricordate, infatti, che un oggetto Intent può contenere una sola Action mentre un intent-filter ne può contenere più di una). L'esempio appena visto vale anche per i campi dati Data e Category, che pure possono essere numerosi nell'intent-filter (dipende dalle capacità del componente). Ogni componente, anche se non indicato nell'intent-filter, viene assegnato alla categoria android.intent.category.DEFAULT. Il meccanismo di intent-resolution potrebbe, infine, non trovare nessun componente capace di gestire l'intent. In tal caso verrà lanciata un eccezzione e l'activity richiedente verrà dunque chiusa.

venerdì 4 marzo 2011

Chromium, video rossi su youtube!

Ecco come vedevo i video su YouTube dopo aver aggiornato all'ultima release di Chromium:


La componente blu e quella verde che (insieme a quella rossa) formano il segnale video sono assenti dal flusso riprodotto. In realtà ho scoperto solo dopo che il problema non è stato causato dall'aggiornamento del browser ma dal plugin flash di Adobe per GNU/Linux. L'installazione di altri browser, infatti, non ha risolto il problema. La versione di adobe-flashplugin sul mio Aspire One è la 10.2.152.27.
Per sistemare la situazione disinstallate il plugin con: sudo apt-get remove --purge adobe-flashplugin, quindi riprovate l'installazione con sudo apt-get adobe-flashplugin. Aprite il browser (nel mio caso Chromium) su una pagina di YouTube e avviate la riproduzione di un video. Cliccate con il tasto destro del mouse sul video e selezionate la voce "Impostazioni". Verrà caricata una piccola finestra, togliete il segno di spunta alla voce "Abilita accelerazione hardware".


Attenzione, riuscirete ad accedere a questa impostazione solo durante la prima riproduzione di un video flash. Potete chiudere la finestra, il problema non dovrebbe ripetersi in futuro (per adesso sembra andare bene).

Note: l'installazione di Gnash non ha risolto il problema, il plugin non viene riconosciuto da YouTube che vi invita a installare quello di Adobe. Ho provato anche con l'installazione di vecchie versioni del plugin flash di Adobe, come la 10.0.45.2 e la 10.1.85.3. Per queste versioni il browser riporta un messaggio in alto (the flash plug-in was blocked because it is out of date) ed offre la possibilità, attraverso due bottoni, di riprodurre comunque il video oppure di aggiornare il plugin.