...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo...

91
Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno di tre anni fa Google ha rilasciato una versione preliminare del kit di sviluppo di Android, il suo nuovo sistema operativo dedicato agli smartphone. Futurologi e semplici appassionati si divisero immediatamente tra entusiastici e scettici. I detrattori, in particolar modo, hanno sempre visto in Android un esperimento, e non qualcosa di reale al quale i produttori di dispositivi avrebbero creduto. A loro favore ha deposto il fatto che, per un periodo piuttosto lungo, nessuno smartphone equipaggiato con Android ha fatto capolino sul mercato, benché il sistema ed i suoi strumenti di sviluppo fossero ormai disponibili da parecchio tempo. La tecnica di Google, in realtà, era ed è ancora quella di sempre: far venire l’acquolina in bocca (e far parlare di sé) con versioni preliminari dei suoi software e dei suoi servizi. Nel caso di Android, molti sviluppatori sono stati fidelizzati e fatti appassionare ad un sistema che, allora, non era ancora sul mercato. Nel frattempo le cose sono cambiate: Android è stato consolidato, e molti produttori di dispositivi mobili hanno aderito o stanno aderendo all’alleanza capeggiata da Google. Grazie alle strategie di Google, esiste oggi una comunità molto ampia di sviluppatori, estremamente produttiva, che altri sistemi mobili non possono vantare. Migliaia di applicazioni sono state sviluppate, e molte altre lo saranno nei prossimi tempi. Il sistema appare inoltre stabile ed offre potenzialità molto ampie. Come è fatto Android Android, essendo un sistema operativo di moderna fattura, è abbastanza complesso. Anche se il suo target sono i dispositivi mobili, l’architettura di Android ha poco da invidiare a quelle dei comuni sistemi per desktop o laptop. Tale architettura è presentata schematicamente in Figura 1. Figura 1 - L'architettura di Android Come si evince dalla figura, Google ha attinto a piene mani dal mondo Open Source. Il cuore di ogni sistema Android, tanto per cominciare, è un kernel Linux, versione 2.6. Direttamente nel kernel sono inseriti i driver per il controllo dell’hardware del dispositivo: driver per la tastiera, lo schermo, il touch screen, il Wi-Fi, il Bluetooth, il controllo dell’audio e così via. Sopra il kernel poggiano le librerie fondamentali, anche queste tutte mutuate dal mondo Open Source. Da citare sono senz’altro OpenGL, per la grafica, SQLite, per la gestione dei dati, e WebKit, per la visualizzazione delle pagine Web. L’architettura prevede poi una macchina virtuale ed una libreria fondamentale che, insieme, costituiscono la piattaforma di sviluppo per le applicazioni Android. Questa macchina virtuale si chiama Dalvik, e sostanzialmente è una Java Virtual Machine. Come verificheremo più tardi, alcune

Transcript of ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo...

Page 1: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

A cura di Carlo Pelliccia

Lezione 1

Primi passi con Android Meno di tre anni fa Google ha rilasciato una versione preliminare del kit di sviluppo di Android, il suo nuovo sistema operativo dedicato agli smartphone. Futurologi e semplici appassionati si divisero immediatamente tra entusiastici e scettici. I detrattori, in particolar modo, hanno sempre visto in Android un esperimento, e non qualcosa di reale al quale i produttori di dispositivi avrebbero creduto. A loro favore ha deposto il fatto che, per un periodo piuttosto lungo, nessuno smartphone equipaggiato con Android ha fatto capolino sul mercato, benché il sistema ed i suoi strumenti di sviluppo fossero ormai disponibili da parecchio tempo. La tecnica di Google, in realtà, era ed è ancora quella di sempre: far venire l’acquolina in bocca (e far parlare di sé) con versioni preliminari dei suoi software e dei suoi servizi. Nel caso di Android, molti sviluppatori sono stati fidelizzati e fatti appassionare ad un sistema che, allora, non era ancora sul mercato. Nel frattempo le cose sono cambiate: Android è stato consolidato, e molti produttori di dispositivi mobili hanno aderito o stanno aderendo all’alleanza capeggiata da Google. Grazie alle strategie di Google, esiste oggi una comunità molto ampia di sviluppatori, estremamente produttiva, che altri sistemi mobili non possono vantare. Migliaia di applicazioni sono state sviluppate, e molte altre lo saranno nei prossimi tempi. Il sistema appare inoltre stabile ed offre potenzialità molto ampie. Come è fatto Android Android, essendo un sistema operativo di moderna fattura, è abbastanza complesso. Anche se il suo target sono i dispositivi mobili, l’architettura di Android ha poco da invidiare a quelle dei comuni sistemi per desktop o laptop. Tale architettura è presentata schematicamente in Figura 1.

Figura 1 - L'architettura di Android

Come si evince dalla figura, Google ha attinto a piene mani dal mondo Open Source. Il cuore di ogni sistema Android, tanto per cominciare, è un kernel Linux, versione 2.6. Direttamente nel kernel sono inseriti i driver per il controllo dell’hardware del dispositivo: driver per la tastiera, lo schermo, il touch screen, il Wi-Fi, il Bluetooth, il controllo dell’audio e così via. Sopra il kernel poggiano le librerie fondamentali, anche queste tutte mutuate dal mondo Open Source. Da citare sono senz’altro OpenGL, per la grafica, SQLite, per la gestione dei dati, e WebKit, per la visualizzazione delle pagine Web. L’architettura prevede poi una macchina virtuale ed una libreria fondamentale che, insieme, costituiscono la piattaforma di sviluppo per le applicazioni Android. Questa macchina virtuale si chiama Dalvik, e sostanzialmente è una Java Virtual Machine. Come verificheremo più tardi, alcune

Page 2: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 1

Primi passi con Android

Pagina 2

delle caratteristiche di Dalvik e della sua libreria non permettono di identificare immediatamente la piattaforma Java disponibile in Android con una di quelle di riferimento (Java SE, Java ME). Nel penultimo strato dell’architettura è possibile rintracciare i gestori e le applicazioni di base del sistema. Ci sono gestori per le risorse, per le applicazioni installate, per le telefonate, il file system ed altro ancora: tutti componenti di cui difficilmente si può fare a meno. Infine, sullo strato più alto dell’architettura, poggiano gli applicativi destinati all’utente finale. Molti, naturalmente, sono già inclusi con l’installazione di base: il browser ed il player multimediale sono dei facili esempi. A questo livello si inseriranno anche le applicazioni che, insieme, impareremo a sviluppare nell’arco di questo corso. Installazione dell’Android SDK Per sviluppare applicazioni che siano in grado di girare su sistemi Android, è necessario installare sul proprio PC un apposito kit di sviluppo (SDK), che sia completo di emulatore, librerie e documentazione. L’Android SDK è disponibile gratuitamente per sistemi Windows, Linux e MacOS X. È possibile scaricarlo collegandosi all’indirizzo: http://developer.android.com/sdk/ Vi verrà proposto di scaricare la più recente versione disponibile. Procedete pure al download del pacchetto adatto al vostro sistema. Al momento della stesura di questa lezione, la versione scaricabile dalla pagina è la r05 (che sta per release 5), ma se ne trovate di più recenti fate pure: non dovrebbero differire troppo da quella presa qui a riferimento. L’installazione del kit è veramente semplice. L’unica cosa di cui bisogna accertarsi, prima di procedere, è di soddisfare i requisiti di base. In particolare, è richiesto che il sistema disponga già di un Java SDK (JDK) versione 5 o successiva. È strettamente indispensabile soddisfare questo requisito, poiché Android si programma in Java, e senza un JDK non è possibile compilare il codice. Dopo aver verificato i requisiti, è possibile procedere. Prendete l’archivio ZIP scaricato da Internet e scompattatelo dove meglio preferite. Accedete alla cartella così ottenuta e lanciate l’applicazione SDK Setup, utile per completare l’installazione e la configurazione dello strumento di sviluppo.

Figura 2 - L'SDK, al primo avvio, propone l'installazione delle diverse piattaforme Android disponibili.

Verrà caricata una GUI che vi permette di gestire i device virtuali ed i componenti aggiuntivi. Appena installato, l’SDK è praticamente vuoto. Contiene solo gli strumenti di sviluppo, ed al suo interno non c’è traccia di alcuna versione del sistema Android. Al primo avvio, dunque, viene eseguita un’analisi e proposto all’utente di scaricare all’interno dell’SDK le diverse piattaforme

Page 3: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 1

Primi passi con Android

Pagina 3

Android disponibili, che potranno essere successivamente utilizzate per lo sviluppo ed il test della applicazioni. Se non viene proposto automaticamente il download di questi componenti, si può procedere manualmente selezionando la voce “Available Packages” e selezionando le piattaforme desiderate. Procedete al download e all’installazione automatica dei componenti proposti automaticamente o selezionati manualmente. Gestione dei device virtuali Il kit di sviluppo comprende un emulatore che ci consentirà di provare le nostre creazioni sul PC, prima di installarle su un reale dispositivo equipaggiato con Android. Per sviluppare le applicazioni, quindi, dobbiamo imparare ad interagire con questo emulatore. Il primo concetto che si deve assimilare è quello che ha nome Android Virtual Device (AVD), cioè dispositivo virtuale Android. Nel nostro PC possiamo creare e configurare quanti dispositivi virtuali vogliamo. È come avere tanti differenti smartphone da utilizzare per i propri test, solo che invece di dispositivi di plastica e silicio si tratta di macchine virtuali, fatte cioè di puro software, da eseguire attraverso l’emulatore. In questo modo è anche possibile avviare contemporaneamente sullo stesso PC due o più dispositivi virtuali, ad esempio per testare un’applicazione che fa interagire più smartphone, come una chat o un gioco multiplayer. Nell’SDK di Android selezionate la voce “Virtual Devices”. Accederete così all’elenco dei device virtuali configurati. Inizialmente l’elenco è vuoto. Create uno o più device virtuali, con il tasto “New”.

Figura 3 - La maschera di creazione di un nuovo dispositivo virtuale.

Le informazioni da fornire sono:

Name: il nome che si vuole attribuire al dispositivo virtuale, ad esempio “Device1”. Target: la piattaforma Android che sarà installata nel dispositivo. L’elenco a tendina

permette di scegliere fra le differenti piattaforme scaricate ed integrate all’interno del proprio SDK.

SD Card: qui è possibile dotare il dispositivo virtuale di una scheda di memoria virtuale. È possibile specificare sia il percorso di un file di immagine di una scheda di memoria, se si

Page 4: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 1

Primi passi con Android

Pagina 4

vuole riutilizzare una memoria virtuale esistente, sia una dimensione di spazio, per creare una nuova memoria virtuale.

Skin: la risoluzione del dispositivo. Si può specificare manualmente o selezionare fra quelle predefinite.

Hardware: permette di impostare alcuni requisiti hardware del dispositivo, come la densità dei pixel, la presenza di un accelerometro e così via.

Una volta creato, il nuovo dispositivo virtuale entrerà a far parte dell’elenco gestito dall’SDK, e potrà quindi essere utilizzato per eseguire il debug ed il test delle applicazioni. L’emulatore Android Se non siete già pratici nell’utilizzo di Android, prima di iniziare a programmare è meglio che ci prendiate confidenza. Esplorando le applicazioni di base potrete così entrare nell’ottica del sistema, per imparare i principi di funzionamento e di design delle sue applicazioni. Potete avviare un dispositivo virtuale dall’interno dell’SDK, selezionandone uno creato in precedenza ed attivando il tasto “Start”. Qualche istante di pazienza (al primo lancio anche qualcosa in più) e l’emulatore caricherà e renderà disponibile il dispositivo virtuale Android selezionato. Con il mouse è possibile simulare il touchscreen del dispositivo, cliccando sullo schermo. Fatevi un giro e prendete pure confidenza con l’ambiente.

Figura 4 - L'emulatore Android.

Come prima cosa utilizzate le applicazioni di base, come il browser o la rubrica: vi aiuteranno molto nel comprendere i principi di utilizzo del sistema. Poi passate a del materiale più tecnico: il menù principale contiene la voce “Dev Tools”, che raccoglie una serie di strumenti dedicati a chi Android vuole programmarlo, e non solo farci un giro di prova. Tra questi spicca l’emulatore di terminale, che permette di avere una shell di sistema per un’interazione di più basso livello con il dispositivo. ADT per Eclipse Benché l’Android SDK disponga di script che automatizzano l’installazione delle applicazioni, il lancio dell’emulatore ed il debug del codice, lavorare in un ambiente integrato, con ogni opzione a portata di clic, è sicuramente più facile. Specie quando l’ambiente integrato si chiama Eclipse. Nel

Page 5: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 1

Primi passi con Android

Pagina 5

sito di Android contattato in precedenza è disponibile anche un plug-in per la celebre piattaforma di sviluppo Open Source. Questo add-on si chiama Android Development Tools for Eclipse, che abbreviato diventa ADT. Il modulo, al momento della stesura di questa lezione, funziona con le più recenti versioni di Eclipse, che sono la 3.3, la 3.4 e la 3.5. Può essere installato direttamente dall’interno della piattaforma di sviluppo. Avviate Eclipse ed eseguite il wizard per l’installazione di nuovi componenti. In Eclipse 3.5 lo si fa con la voce di menù “Help » Install New Software”. Giunti a destinazione, specificate come fonte del plug-in il seguente indirizzo: https://dl-ssl.google.com/android/eclipse/ Digitate l’indirizzo, premete invio ed attendente che venga presentata la lista dei componenti disponibili per il download. Selezionateli tutti e procedete. Il plug-in per lo sviluppo del software Android sarà automaticamente scaricato ed installato.

Figura 5 - Installazione del plug-in ADT in Eclipse 3.5.

Dopo il riavvio di Eclipse, recatevi immediatamente nella schermata delle preferenze dell’ambiente (voce di menù “Window » Preferences”). Qui troverete disponibile la nuova categoria “Android”, nell’elenco sulla sinistra. Selezionatela.

Page 6: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 1

Primi passi con Android

Pagina 6

Figura 6 - La schermata di configurazione dell'ADT.

Impostate il percorso del vostro Android SDK: è necessario affinché Eclipse possa agganciare il kit di sviluppo. Durante questa fase dovreste anche ricevere un pop-up per l’accettazione della licenza del plug-in. Ciao, Mondo Androide! È venuto il momento di utilizzare ADT e l’emulatore per programmare la nostra prima applicazione Android. Naturalmente sarà una variante del classico “Ciao, Mondo!”. Avviate Eclipse. Grazie ad ADT disponete ora di una nuova categoria di progetto, chiamata “Android Project”. Create un progetto di questo tipo.

Figura 7 - Eclipse dispone ora di una categoria per progetti e file Android.

Page 7: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 1

Primi passi con Android

Pagina 7

Figura 8 - La maschera di creazione di un progetto Android.

Nel wizard di creazione del progetto specificate la seguente configurazione:

Project name: CiaoMondoAndroide Build target: selezionate la più recente fra le piattaforme Android installate. Application name: Ciao Mondo Package name: mieapplicazioni.helloandroid Create Activity: CiaoMondoAndroideActivity Min SDK Version: lasciate bianco

Il progetto, a questo punto, può essere creato, azionando il tasto “Finish”. Eclipse popolerà automaticamente il progetto, inserendo le librerie di Android e la struttura di base dei progetti per questa piattaforma. In uno slancio di generosità, Eclipse provvederà anche alla creazione della prima classe della soluzione, chiamata CiaoMondoAndroideActivity (come specificato alla voce “Create Activity”) ed inserita nel pacchetto it.ioprogrammo.helloandroid (come alla voce “Package name”). Aprite il codice della classe e modificatelo alla seguente maniera: package mieapplicazioni.helloandroid; import android.app.Activity; import android.os.Bundle; import android.widget.TextView; public class CiaoMondoAndroideActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);

Page 8: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 1

Primi passi con Android

Pagina 8

TextView tv = new TextView(this); tv.setText("Ciao, Mondo Androide!"); setContentView(tv); } }

Ora selezionate la radice del progetto “CiaoMondoAndroide”, selezionate la voce di menù “Run » Run” e nella finestra di selezione della tipologia di applicazione scegliete “Android Application”.

Figura 9 - Il progetto deve essere eseguito come una “Android Application”.

L’emulatore verrà caricato. Eclipse provvederà automaticamente ad installare al suo interno l’applicazione “CiaoMondoAndroide”, per poi avviarla non appena l’operazione sarà completata. È fatta: il vostro primo software per Android sta girando davanti ai vostri occhi.

Figura 10 - L'applicazione “CiaoMondoAndroide” eseguita nell'emulatore.

Successivamente, accedendo alle configurazioni di esecuzione (voce di menù “Run » Run Configurations”), sarà possibile alterare i parametri di avvio dell’emulatore e dell’applicazione. Tra questi, anche il dispositivo virtuale sul quale sarà installato ed avviato il software. Fate qualche esperimento. Provate, ad esempio, a creare differenti AVD, collaudando così il software con schermi di differenti dimensioni e proporzioni. Un altro esperimento interessante, che si consiglia di compiere prima di procedere oltre, è l’utilizzo del debugger di Eclipse con l’applicazione Android. Ponete un breakpoint sulla classe realizzata ed avviate di nuovo emulatore ed applicazione, questa volta in modalità debug. Dalvik e le librerie Android Superata la prova del primo progetto Android, torniamo ad occuparci dei concetti fondamentali per la programmazione in questo ambiente. Come abbiamo appreso e dimostrato, la piattaforma di sviluppo è di natura Java. Tuttavia si tratta di una piattaforma particolare e personalizzata, che vale la pena approfondire. La macchina virtuale, chiamata Dalvik, sembra essere una Java Virtual Machine, ma in realtà non lo è del tutto. Mi spiego meglio: una Java Virtual Machine esegue del codice bytecode, giusto? Ecco, la Dalvik Virtual Machine non esegue bytecode standard, ma un

Page 9: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 1

Primi passi con Android

Pagina 9

altro linguaggio, chiamato DEX (Dalvik EXecutable), studiato appositamente per una migliore resa in uno smartphone. Con l’Android SDK ed Eclipse, ad ogni modo, ci sembrerà di utilizzare una regolare Java Virtual Machine. L’ambiente di sviluppo, infatti, provvede automaticamente alla generazione del codice DEX, ri-compilando il bytecode che a sua volta è frutto di una prima comune compilazione Java. Per lo sviluppatore, ad ogni modo, è tutto trasparente. Questa peculiarità di Dalvik, quindi, non influenzerà il nostro modo di programmare. La stessa considerazione, invece, non può essere fatta riguardo la libreria di base che affianca Dalvik. Aprite il documento al percorso docs/reference/packages.html, nel vostro Android SDK. È l’indice dei package Java compresi nella libreria di base. Scorretela velocemente e traete pure le prime conclusioni. C’è parecchio della Standard Edition di Java, ma non c’è tutto. Ad esempio non ci sono AWT e Swing. I pacchetti fondamentali, però, ci sono tutti, ed appaiono in larga misura identici a come li vuole Sun. Davvero poco viene dalla Micro Edition, praticamente nulla. La piattaforma Java ME è stata snobbata da Android, che le ha preferito una libreria più simile a quella di un sistema desktop. Non passano poi inosservati i tanti package con prefisso android che, naturalmente, sono esclusivi di questa speciale piattaforma. Servono per l’interazione diretta con le funzionalità del sistema sottostante. Ad esempio: il package android.widget contiene i componenti custom di Android per la costruzione delle interfacce grafiche (in CiaoMondoAndroide abbiamo usato TextView); nel pacchetto android.graphics ci sono le funzioni primitive per la grafica di più basso livello; in android.location ci sono gli strumenti per interagire con un eventuale ricevitore GPS compreso nel dispositivo. Ciascuno dei pacchetti android, naturalmente, meriterebbe una trattazione estesa e completa, tanti sono i possibili campi di applicazione. Ne emerge il profilo di una piattaforma di sviluppo complessa, perché molto ricca, ma semplice, perché ordinata e perché condivide parecchio con l’edizione tradizionale di Java. Il consiglio, naturalmente, è quello di tenere sempre a portata di mano la documentazione delle API di Android. Fatevi poi guidare dalla curiosità: date pure una prima occhiata alle classi che più stuzzicano la vostra fantasia. Principi di programmazione Chi programma con Java ME sa che le MIDlet sono il mattone fondamentale delle applicazioni MIDP; chi crea applicazioni Web con Java EE non può ignorare cosa sia una Servlet; persino i programmatori meno esperti sanno che le applicazioni Java, per girare in un browser, devono essere inglobate in una Applet. Tutto questo per dire che ciascun ambiente, Java e non, dispone dei suoi mattoni fondamentali, che lo sviluppatore può estendere ed implementare per trovare un punto di aggancio con la piattaforma. Android non sfugge alla regola, anzi la amplifica. A seconda di quel che si intende fare è disponibile un diverso modello. Android fornisce quattro mattoni di base:

Attività Le attività sono quei blocchi di un’applicazione che interagiscono con l’utente utilizzando lo schermo ed i dispositivi di input messi a disposizione dallo smartphone. Comunemente fanno uso di componenti UI già pronti, come quelli presenti nel pacchetto android.widget, ma questa non è necessariamente la regola. La classe dimostrativa CiaoMondoAndroideActivity è un’attività. Le attività sono probabilmente il modello più diffuso in Android, e si realizzano estendendo la classe base android.app.Activity.

Servizio Un servizio gira in sottofondo e non interagisce direttamente con l’utente. Ad esempio può riprodurre un brano MP3, mentre l’utente utilizza delle attività per fare altro. Un servizio si realizza estendendo la classe android.app.Service.

Page 10: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 1

Primi passi con Android

Pagina 10

Broadcast Receiver Un Broadcast Receiver viene utilizzato quando si intende intercettare un particolare evento, attraverso tutto il sistema. Ad esempio lo si può utilizzare se si desidera compiere un’azione quando si scatta una foto o quando parte la segnalazione di batteria scarica. La classe da estendere è android.content.BroadcastReceiver.

Content Provider I Content Provider sono utilizzati per esporre dati ed informazioni. Costituiscono un canale di comunicazione tra le differenti applicazioni installate nel sistema. Si può creare un Content Provider estendendo la classe astratta android.content.ContentProvider.

Un’applicazione Android è costituita da uno o più di questi elementi. Molto frequentemente, contiene almeno un’attività, ma non è detto che debba sempre essere così. I pacchetti APK Le applicazioni Android sono distribuite sotto forma di file APK (Android Package). Al loro interno vengono raccolti gli eseguibili in formato DEX, le eventuali risorse associate ed una serie di descrittori che delineano il contenuto del pacchetto. In particolare, nel cosiddetto manifesto, vengono dichiarate le attività, i servizi, i provider ed i receiver compresi nel pacchetto, in modo che il sistema possa agganciarli ed azionarli correttamente. Torniamo, in Eclipse, sul progetto CiaoMondoAndroide. Al suo interno troverete un file chiamato AndroidManifest.xml, fatto come segue: <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="mieapplicazioni.helloandroid" android:versionCode="1" android:versionName="1.0"> <application android:icon="@drawable/icon" android:label="@string/app_name"> <activity android:name=".CiaoMondoAndroideActivity" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>

È il manifesto descrittore citato poco fa. Al suo interno potete e dovete dichiarare i componenti del vostro software. Eclipse, all’atto di creazione del progetto, ha già eseguito su di esso alcune configurazioni iniziali. Ad esempio ha registrato l’attività CiaoMondoAndroideActivity, ha specificato le proprietà generali dell’applicazione ed ha anche generato ed impostato delle icone per il programma (res/drawable-qualcosa/icon.png). Ovviamente queste scelte possono essere alterate, e nuovi componenti possono essere aggiunti al progetto. Con lo speciale editor visuale messo a disposizione da Eclipse, vi risulterà tutto molto semplice: è sufficiente fare un po’ di pratica ed approfondire di volta in volta l’aspetto d’interesse. Una volta che il lavoro è stato completato, è possibile esportare il file APK da distribuire ai fortunati possessori di un sistema Android. Prima di distribuire in giro il pacchetto è però necessario apporre su di esso una firma digitale. In caso contrario, Android non potrà installarne i contenuti. Questo è l’unico vincolo imposto dal sistema. Il fatto che un pacchetto debba essere firmato non deve preoccupare lo sviluppatore: non è necessario che una certification authority riconosca la chiave utilizzata per la firma. Di conseguenza è possibile firmare un pacchetto APK anche

Page 11: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 1

Primi passi con Android

Pagina 11

servendosi di un certificato “fatto in casa”. In parole semplici: non bisogna pagare nessuno perché i nostri software siano autorizzati, possiamo fare tutto da noi. In Eclipse, ancora una volta, è questione di pochi clic: selezionate la radice del progetto, attivate la voce di menù “File » Export” e scegliete il wizard “Export Android Application”.

Figura 11 - Selezione del wizard di esportazione di un'applicazione Android.

Al secondo step del wizard di generazione del pacchetto, vi verrà chiesto da dove prelevare la firma digitale. Solitamente gli oggetti di questo tipo vengono raccolti e conservati all’interno di un keystore. In un keystore, cioè, ci sono più firme digitali. Se non avete mai formato un keystore in precedenza, o se semplicemente ne volete iniziare uno nuovo, selezionate l’opzione “Create new keystore”.

Figura 12 - La selezione o creazione di un keystore.

Il keystore verrà conservato all’interno di un file, il cui percorso va obbligatoriamente specificato. Scegliete dove riporre il keystore (nel caso in Figura 12, la directory è C:\keystores) e date un nome a vostro piacimento a file (android_keystore, nel caso in immagine). Non c’è bisogno di usare un’estensione particolare per il nome del file. È invece buona pratica proteggere i propri keystore con una password, in modo che le nostre firme digitali non possano essere utilizzate nel caso in cui qualcuno ci rubi il file. Pertanto abbiate cura di impostare una password sufficientemente sicura. Siccome il keystore appena creato è vuoto, il passo successivo del wizard ci fa creare una chiave, cioè una firma digitale. Dobbiamo inserire il nome della chiave (detto alias), la password per l’utilizzo della chiave, una validità in anni (di solito si usa il valore 25) ed i dati anagrafici di base del firmatario (nome e cognome).

Page 12: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 1

Primi passi con Android

Pagina 12

Figura 13 - La maschera per la creazione di una nuova chiave di firma digitale.

Superata la fase di creazione o selezione del keystore e della chiave, il wizard fa scegliere dove salvare il pacchetto APK che sarà generato. Scegliete la destinazione e concludete l’operazione. È fatta: il pacchetto è stato generato e firmato. Potete ora installarlo su un dispositivo Android reale, in plastica, metallo e silicio.

Page 13: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

A cura di Carlo Pelliccia

Lezione 2

Gestione delle risorse Se c’è un aspetto di Android dal quale si evince la modernità di questo sistema, è la sua maniera di gestire le risorse ed i dati. Nelle piattaforme di sviluppo meno moderne, spesso e volentieri, le risorse esterne come i dati di configurazione, i messaggi di interfaccia, le immagini o altro materiale simile, sono trattate senza alcun riguardo speciale. Android, invece, richiede che i progetti siano organizzati in una certa maniera. La corretta gestione delle risorse, in questa piattaforma, è importante tanto quanto la stesura del codice. In un certo senso, con Android non si può imparare a programmare se prima non si apprende come organizzare e richiamare le risorse. La struttura dei progetti Android Avviamo Eclipse e torniamo sul progetto CiaoMondoAndroide, realizzato nella lezione precedente per dimostrare le funzionalità di base del kit di sviluppo per Android. Quando abbiamo creato il progetto, Eclipse ha predisposto per noi un albero di cartelle, all’interno del quale sono stati generati automaticamente diversi file. Guardate la Figura 1, che mostra la situazione del file system all’atto di creazione del progetto.

Figura 1 - L'organizzazione di default di un progetto Android.

Tra i file generati automaticamente c’è AndroidManifest.xml, cioè il descrittore dell’applicazione, che già abbiamo iniziato a conoscere. Torneremo ad approfondirlo mano a mano che gli argomenti trattati ce ne daranno occasione. Oltre al descrittore c’è il file default.properties, poco rilevante per noi, poiché serve esclusivamente al sistema di build automatico. Ci sono poi delle directory: src, assets, res e gen. La prima, src, è quella dove dobbiamo andare a realizzare i package e le classi della nostra applicazione. Le cartelle res e assets servono per ospitare le risorse esterne necessarie all’applicazione, come le immagini, i file audio ed altro ancora. La cartella res, in particolar modo, gode di una speciale struttura predefinita, formata dalle sotto-directory drawable, layout e values. Le cartelle del gruppo drawable servono per le immagini utilizzate dal software, mentre layout e values ospitano dei speciali file XML utili per definire in maniera dichiarativa l’aspetto dell’applicazione ed i valori utilizzati al suo interno. Oltre a src, assets e res c’è infine la cartella gen, che contiene la speciale classe chiamata R. Invocando questa classe è possibile richiamare via codice le risorse memorizzate sotto la directory res. Impareremo oggi stesso come farlo. Sappiate

Page 14: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 2

Gestione delle risorse

Pagina 2

comunque che la classe R viene generata automaticamente dal sistema e non deve mai essere modificata a mano. Le risorse in Android La speciale cartella res di un qualunque progetto Android ospita le risorse gestite dal sistema. Dentro di essa lo sviluppatore può inserire una o più delle seguenti sotto-directory:

anim. Questa directory ospita i file XML che descrivono delle animazioni. drawable. Questa directory deve essere usata per le immagini necessarie al software. Sono

supportati i formati PNG e JPEG. layout. Questa directory ospita i file XML che descrivono il layout delle interfacce utente. values. Questa directory ospita i file XML che descrivono stringhe e altri parametri utilizzati

dall’applicazione. xml. Questa directory ospita file XML di tipo qualsiasi, che il sistema interpreta e rende

semplici da acquisire. raw. Questa directory ospita file binari di tipo qualsiasi (ad esempio audio o filmati) che

l’applicazione poi carica. Ciascuna di queste directory può comparire, anche più di una volta, accompagnata da un qualificatore. I principali qualificatori possibili sono:

Lingua e regione. Questo qualificatore si formula servendosi del codice ISO a due caratteri di una lingua (it per italiano, en per inglese, fr per francese, de per tedesco ecc.). Opzionalmente si può aggiungere anche la regione, aggiungendo alla sigla della lingua un underscore, una r ed il codice ISO a due caratteri della regione (it_rIT indica italiano e Italia, mentre it_rCH indica italiano e svizzera).

Dimensioni dello schermo. Valori possibili per il qualificatore sono small (piccolo), normal (normale) e large (grande).

Orientamento dello schermo. Valori possibili sono port (schermo più alto che largo), land (schermo più largo che alto) e square (schermo quadrato).

Densità dei pixel dello schermo. Valori possibili sono ldpi (bassa densità, grosso modo intorno ai 120 dpi), mdpi (media densità, intorno ai 160 dpi), hdpi (alta densità, intorno ai 220 dpi) e nodpi (speciale qualificatore che ospita le immagini che non devono essere scalate in base alla densità).

Quando si vuole abbinare un qualificatore ad una cartella di risorse, ad esempio a drawable, è sufficiente inserirne il valore in coda al nome della cartella, al seguente modo: drawable-small Più qualificatori, purché di gruppi diversi, possono essere associati ad una sola cartella: drawable-small-land-mdpi-it Ciascuna cartella può comparire più di una volta, con qualificatori diversi. Ad esempio è lecito avere sotto res tutte le seguenti cartelle insieme: values

Page 15: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 2

Gestione delle risorse

Pagina 3

values-it values-fr values-en Quando si richiama una risorsa (tra poco vedremo come si fa), Android sceglie automaticamente da quale directory andarla a pescare, cercando in base al tipo della risorsa e ai qualificatori abbinati alla cartella che la contiene. In questa maniera diventa molto semplice gestire l’adattamento delle risorse in base alla lingua del dispositivo (internazionalizzazione) o alle sue caratteristiche hardware (come le dimensioni dello schermo). Gestione dei valori Il primo tipo di risorse che impareremo a manipolare sono i valori. Si tratta di coppie chiave-valore dichiarate all’interno dei file XML che sono al percorso di progetto res/values. Eclipse, per default, crea a questo percorso il file strings.xml, pensato per raccogliere le stringhe usate dall’applicazione che sarà sviluppata. Ad ogni modo potete rinominare il file o aggiungerne quanti altri ne volete, al fine di categorizzare al meglio i valori necessari alla vostra applicazione. L’importante è che tutti i file presenti nella cartella values seguano il modello: <?xml version="1.0" encoding="utf-8"?> <resources> <!-- valori qui --> </resources>

All’interno del tag <resources> … </resources> è possibile dichiarare differenti tipi di valori. Supponiamo di voler dichiarare un valore di tipo stringa chiamato nome e con contenuto Carlo: <?xml version="1.0" encoding="utf-8"?> <resources> <string name="nome">Carlo</string> </resources>

Ci sono numerosi tipi di dati supportati. Ecco un elenco completo:

Stringhe Si usa il tag <string> e servono per definire i messaggi testuali che saranno usati all’interno dell’applicazione. Colori Si usa il tag <color> con valori espressi in forma esadecimale secondo i modelli #RRGGBB o #AARRGGBB (AA sta per il canale alpha, che regola la trasparenza del colore). Ad esempio: <color name="rosso">#FF0000</color> Misure e dimensioni Si usa il tag <dimen> e con valori numerici decimali accompagnati da un’unità di misura che può essere px (pixel), in (pollici), mm (millimetri), pt (punti a risoluzione 72dpi), dp (punti di densità) e sp (punti di densità e scala). Ad esempio: <dimen name="lato">180px</dimen>

Page 16: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 2

Gestione delle risorse

Pagina 4

Rettangoli di colore Si usa il tag <drawable>. I valori possibili sono colori esadecimali come nel caso del tag <color>. Ad esempio: <drawable name="verde">#00FF00</drawable> Array di interi Si usa il tag <integer-array>. Gli elementi dell’array vanno espressi con più occorrenze del tag annidato <item>. Ad esempio: <integer-array name="numeriPrimi"> <item>2</item><item>3</item> <item>5</item><item>7</item> </integer-array> Array di stringhe Si usa il tag <string-array>. Anche in questo caso si usa il tag <item>. Ad esempio: <string-array name="nomi"> <item>Carlo</item> <item>Claudia</item> <item>Nami</item> </string-array> Stili e temi Si usa il tag <style>. Servono per creare degli stili di disegno ed impaginazione, un po’ come fanno i fogli di stile CSS nel caso di HTML. Dentro il tag <style> vanno inseriti dei tag <item> con le singole voci che compongono lo stile. Gli stili possono essere applicati alle interfacce grafiche. Ad esempio: <style name="titolo"> <item name="android:textSize">18sp</item> <item name="android:textColor">#000088</item> </style>

Usando Eclipse, comunque, non c’è bisogno di imparare l’elenco a memoria: qualsiasi file XML posto sotto la directory res/values viene automaticamente lavorato con un apposito editor.

Page 17: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 2

Gestione delle risorse

Pagina 5

Figura 2 - L'editor di Eclipse per i file XML nella directory values.

In un file di valori si può inserire qualunque mistura degli elementi presentati sopra. La prassi, tuttavia, suggerisce di ripartire i differenti tipi di valore nei seguenti file:

arrays.xml, per gli array. colors.xml, per i colori. dimens.xml, per le dimensioni. strings.xml, per le stringhe. styles.xml, per gli stili.

Richiamare le risorse da XML Come scritto in apertura, la modernità di Android può essere evinta proprio dalla sua maniera di gestire le risorse. Le piattaforme di una volta non concedevano sistemi agevolati, finendo così per favorire l’accoppiamento fra codice e dati. Tuttora non è raro vedere dei sorgenti in Java, in C o in qualsiasi altro linguaggio, con valori e messaggi digitati direttamente dentro il codice. Questa pratica non è corretta ed è sconsigliata da ogni manuale: è sempre meglio separare i dati dal codice, perché in questa maniera il software è più facile sia da realizzare sia da mantenere. Android intende favorire la pratica del disaccoppiamento fra dati e codice, e lo fa attraverso gli strumenti che si stanno prendendo in considerazione in questa sede. I valori dichiarati nei file XML sotto values, così come tutte le altre risorse della cartella res e delle sue annidate, sono trattati dal sistema in maniera speciale. Il kit di sviluppo, infatti, fornisce delle agevolazioni per richiamare le risorse dalle varie parti del software. Sostanzialmente un’applicazione Android è costituita da file dichiarativi XML e da classi Java. Sia in un caso sia nell’altro, ci sono scorciatoie per richiamare le risorse incluse in res. Cominciamo dal caso XML e prendiamo a riferimento il più importante dei file di questo tipo: AndroidManifest.xml. Quando, al suo interno, si dichiarano i dettagli dell’applicazione, è possibile scrivere qualcosa come: <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="mypackage" android:versionCode="1" android:versionName="1.0"> <application android:label="LaMiaApplicazione"> ... </application> </manifest>

Il nome dell’applicazione, cioè LaMiaApplicazione, è stato in questo caso digitato direttamente dentro il codice XML. Con Android questo è corretto, tuttavia si può fare di meglio. Si può includere il titolo dell’applicazione nel file res/values/strings.xml, alla seguente maniera: <?xml version="1.0" encoding="utf-8"?>

Page 18: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 2

Gestione delle risorse

Pagina 6

<resources> <string name="app_name">LaMiaApplicazione</string> </resources>

A questo punto il descrittore dell’applicazione può essere riscritto come segue: <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="mypackage" android:versionCode="1" android:versionName="1.0"> <application android:label="@string/app_name"> ... </application> </manifest>

Anziché scrivere “LaMiaApplicazione”, si è usato il riferimento @string/app_name. Questa scorciatoia, come intuibile, viene sostituita dalla risorsa di tipo stringa con nome app_name, che nel file strings.xml abbiamo dichiarato essere proprio “LaMiaApplicazione”. La regola generale per richiamare una risorsa in un file XML, quindi, è basata sul modello: @tipo/nome I tipi validi sono:

@array, per gli array. @color, per i colori. @dimen, per le dimensioni. @drawable, per i valori drawable, ma anche per le immagini messe in res/drawable. @layout, per richiamare i layout presenti nella cartella res/layout. @raw, per i file nella cartella res/raw (cfr. box laterale). @string, per le stringhe. @style, per gli stili.

Con @drawable, in particolar modo, è possibile riferire sia i valori dichiarati con i tag <drawable> in res/values, sia le immagini conservate nella cartella res/drawable (o nelle sue varianti). Ad esempio, se in res/drawable viene messa un’icona chiamata icon.png, darà possibile richiamarla con la formula @drawable/icon. Ad esempio lo si può fare in AndroidManifest.xml, per associare l’icona all’applicazione: <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="mypackage" android:versionCode="1" android:versionName="1.0"> <application android:label="@string/app_name" android:icon="@drawable/icon"> ...

</application> </manifest>

Richiamare le risorse da Java Valori e risorse possono essere richiamati da codice Java servendosi della classe android.content.res.Resources. Stando all’interno di una attività, cioè di una classe che estende android.app.Activity, è sufficiente richiamare il metodo getResources() per ottenere il punto d’accesso alle risorse dell’applicazione: Resources res = getResources();

Page 19: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 2

Gestione delle risorse

Pagina 7

Una volta ottenuto l’oggetto, è possibile invocare su di esso la seguente serie di metodi:

public int getColor(int id) Restituisce il colore avente l’identificativo di risorsa specificato.

public float getDimension(int id) Restituisce la dimensione avente l’identificativo di risorsa specificato.

public Drawable getDrawable(int id) Restituisce l’oggetto disegnabile avente l’identificativo di risorsa specificato.

public int[] getIntArray(int id) Restituisce l’array di interi avente l’identificativo di risorsa specificato.

public String getString(int id) Restituisce la stringa avente l’identificativo di risorsa specificato.

public String[] getStringArray(int id) Restituisce l’array di stringhe avente l’identificativo di risorsa specificato.

Tutti questi metodi agganciano la risorsa desiderata attraverso un identificativo numerico (int id). Ma come fare a conoscere gli ID associati alle risorse e ai valori inseriti nella cartella res? Lo si può fare passando per la speciale classe autogenerata R. Al suo interno sono contenute delle sottoclassi statiche, una per ciascuna tipologia di risorsa presente nel progetto: R.string, R.drawable, R.color e così via. In ciascuno di questi gruppi vengono introdotti gli ID numerici che corrispondono alle risorse e ai valori conservati in res e nelle sue sotto-cartelle. Proviamo con un esempio pratico: facciamo il caso che nel file res/values/strings.xml sia stata dichiarata la stringa app_name. Per richiamarla da codice Java, stando all’interno di una attività, si deve fare alla seguente maniera: Resources res = getResources(); String appName = res.getString(R.string.app_name); Ciao Mondo Reloaded Mettiamo a frutto le nozioni acquisite in questa lezione assemblando per la seconda volta un esempio del tipo “Ciao, Mondo!”. Questa volta, però, useremo la più corretta pratica delle risorse esterne per il nome dell’applicazione e per il messaggio presentato sullo schermo. Andiamo a modificare il progetto “CiaoMondoAndroide” realizzato nel corso della lezione precedente. Inseriamo il seguente contenuto al percorso di progetto res/values/strings.xml: <?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">Ciao Mondo</string> <string name="message">Ciao, Mondo Androide!</string> </resources>

Sono state dichiarate due stringhe: app_name con valore “Ciao Mondo” e message con valore “Ciao, Mondo Androide!”.

Page 20: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 2

Gestione delle risorse

Pagina 8

Andiamo ora su AndroidManifest.xml: <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="mieapplicazioni.helloandroid" android:versionCode="1" android:versionName="1.0"> <application android:icon="@drawable/icon" android:label="@string/app_name"> <activity android:name=".CiaoMondoAndroideActivity" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>

Il nome dell’applicazione, citato ben due volte all’interno del file, è stato richiamato mediante il riferimento @string/app_name. Allo stesso modo l’icona per l’applicazione, creata automaticamente da Eclipse al percorso res/drawable/icon.png, è stata riferita attraverso la dicitura @drawable/icon. Adesso tocca alla classe CiaoMondoAndroideActivity, il cui codice è riportato di seguito: package mieapplicazioni.helloandroid; import android.app.Activity; import android.content.res.Resources; import android.os.Bundle; import android.widget.TextView; public class CiaoMondoAndroideActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Resources res = getResources(); String message = res.getString(R.string.message); TextView tv = new TextView(this); tv.setText(message); setContentView(tv); } }

Il messaggio “Ciao, Mondo Androide!”, questa volta, è stato caricato attraverso la classe Resources ed il riferimento R.string.message.

Differenza tra res e assets La differenza tra le cartelle res e assets è poco evidente, eppure c’è. La directory res è pensata per gestire le risorse in maniera struttura, ed infatti è suddivisa in sottocartelle. Tutte le risorse posizionate in res vengono prese in esame dal sistema di build e riferite nella speciale classe R. Quelle dentro res, dunque, sono delle risorse gestite. Sotto assets, invece, è possibile depositare qualsiasi file si desideri senza che il sistema di build esegua un’analisi preventiva e crei il riferimento in R. Le risorse esterne conservate nella directory assets possono essere caricate servendosi della classe android.content.res.AssetManager. Nella maggior parte dei casi, comunque, non c’è bisogno di ricorrere alla cartella assets, poiché res offre una maniera semplificata e completa per l’accesso alle risorse.

Page 21: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

A cura di Carlo Pelliccia

Lezione 3

Le attività Le applicazioni Android, come si è accennato durante la prima lezione, si compongono di quattro mattoni fondamentali: le attività (activity), i servizi (service), i broadcast receiver ed i content provider. Ogni applicazione è formata da uno o più di questi mattoni. Non è detto che li contenga tutti: ad esempio potrebbe essere costituita da due attività e da un servizio, senza avere né broadcast receiver né content provider. Nella stragrande maggioranza dei casi, comunque, le applicazioni comprendono almeno un’attività. Le attività, di conseguenza, sono il più fondamentale dei componenti di base delle applicazioni Android. Cos’è un’attività Stando alla documentazione ufficiale, un’attività è “una singola e precisa cosa che l’utente può fare”. Proviamo ad indagare le implicazioni di questa affermazione. Partiamo dal fatto che l’utente, per fare qualcosa, deve interagire con il dispositivo. Domandiamoci come avvenga, nel caso di uno smartphone, l’interazione tra l’uomo e la macchina. Un ruolo essenziale, naturalmente, è svolto dai meccanismi di input come la tastiera ed il touchscreen, che permettono all’utente di specificare il proprio volere. Le periferiche di input, tuttavia, da sole non bastano. Affinché l’utente sappia cosa può fare e come debba farlo, ma anche affinché il software possa mostrare all’utente il risultato elaborato, è necessario un canale aggiuntivo. Nella maggior parte dei casi questo canale è il display. Nella superficie dello schermo il software disegna tutti quegli oggetti con cui l’utente può interagire (bottoni, menù, campi di testo), e sempre sullo schermo viene presentato il risultato dell’elaborazione richiesta. Il ragionamento ci porta alla conclusione che, per fare qualcosa con il dispositivo, è necessario usare lo schermo. Esiste perciò un parallelo tra il concetto di attività, in Android, e quello di finestra, in un sistema desktop, benché non siano esattamente la stessa cosa. In generale, ad ogni modo, possiamo assumere con tranquillità che le attività sono quei componenti di un’applicazione Android che fanno uso del display e che interagiscono con l’utente. Dal punto di vista del programmatore, poi, possiamo spingerci oltre e semplificare ulteriormente. In maniera più pragmatica, un’attività è una classe che estende android.app.Activity. L’autore del codice, realizzando l’attività, si serve dei metodi ereditati da Activity per controllare cosa appare nel display, per assorbire gli input dell’utente, per intercettare i cambi di stato e per interagire con il sistema sottostante. Ciclo di vita di un’attività In un sistema desktop il monitor è sufficientemente spazioso da poter mostrare più finestre simultaneamente. Perciò non è affatto raro lavorare con più programmi contemporaneamente attivi, le cui finestre vengono affiancate o sovrapposte. Gli smartphone, invece, funzionano diversamente. Prima di tutto il display è piccolo, e pertanto ha poco senso affiancare due o più finestre di applicazioni differenti. Poi non bisogna dimenticare che le risorse di calcolo sono modeste, e perciò non è buona cosa tenere simultaneamente in vita troppi programmi. Per questi motivi le attività di Android hanno carattere di esclusività. È possibile mandare in esecuzione più attività simultaneamente, ma soltanto un’attività alla volta può occupare il display. L’attività che occupa il display è in esecuzione ed interagisce direttamente con l’utente. Le altre, invece, sono ibernate e tenute nascoste in sottofondo, in modo da ridurre al minimo il consumo delle risorse di calcolo. L’utente, naturalmente, può ripristinare un’attività ibernata e riprenderla da dove l’aveva interrotta, riportandola in primo piano. L’attività dalla quale si sta allontanando, invece, sarà ibernata e mandata in sottofondo al posto di quella ripristinata. Il cambio di attività può anche avvenire a causa di un evento esterno. Il caso più ricorrente è quello della telefonata in arrivo: se il telefono squilla mentre si sta usando la calcolatrice, quest’ultima sarà automaticamente ibernata e mandata in sottofondo. L’utente, conclusa la chiamata, potrà richiamare l’attività interrotta e riportarla in vita, riprendendo i calcoli esattamente da dove li aveva interrotti.

Page 22: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 3 Le attività

Pagina 2

Siccome le attività ibernate, in termini di risorse di calcolo, non consumano nulla, in Android il concetto di chiusura delle attività è secondario e tenuto nascosto all’utente. Ciò, di solito, spiazza chi è al suo primo confronto con la programmazione dei dispositivi portatili. Le attività di Android non dispongono di un bottone “x”, o di un tasto equivalente, con il quale è possibile terminarle. L’utente, di conseguenza, non può chiudere un’attività, ma può solo mandarla in sottofondo. Questo, comunque, non significa che le attività non muoiano mai, anzi! Per prima cosa le attività possono morire spontaneamente, perché hanno terminato i loro compiti. Insomma, anche se il sistema non ci fornisce automaticamente un bottone “chiudi”, possiamo sempre includerlo noi nelle nostre applicazioni. In alternativa, la distruzione delle attività è completamente demandata al sistema. I casi in cui un’attività può terminare sono due:

L’attività è ibernata ed il sistema, arbitrariamente, decide che non è più utile e perciò la distrugge.

Il sistema è a corto di memoria, e per recuperare spazio inizia ad uccidere bruscamente le attività in sottofondo.

Esistono poi dei task manager di terze parti che permettono di uccidere le attività in sottofondo, ma non sono previsti nel sistema di base. I differenti passaggi di stato di un’attività attraversano alcuni metodi della classe Activity che, come programmatori, possiamo ridefinire per intercettare gli eventi di nostro interesse.

Figura 1 - Ciclo di vita di un'attività.

Page 23: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 3 Le attività

Pagina 3

La figura illustra la sequenza di chiamate ai metodi di Activity eseguite durante i passaggi di stato dell’attività. Entriamo nel dettaglio:

protected void onCreate(android.os.Bundle savedInstanceState) Richiamato non appena l’attività viene creata. L’argomento savedInstanceState serve per riportare un eventuale stato dell’attività salvato in precedenza da un’altra istanza che è stata terminata. L’argomento è null nel caso in cui l’attività non abbia uno stato salvato. protected void onRestart() Richiamato per segnalare che l’attività sta venendo riavviata dopo essere stata precedentemente arrestata. protected void onStart() Richiamato per segnalare che l’attività sta per diventare visibile sullo schermo. protected void onResume() Richiamato per segnalare che l’attività sta per iniziare l’interazione con l’utente. protected void onPause() Richiamato per segnalare che l’attività non sta più interagendo con l’utente. protected void onStop() Richiamato per segnalare che l’attività non è più visibile sullo schermo. protected void onDestroy() Richiamato per segnalare che l’applicazione sta per essere terminata.

La prassi richiede che, come prima riga di codice di ciascuno di questi metodi, si richiami l’implementazione di base del metodo che si sta ridefinendo. Ad esempio: @Override protected void onStart() { super.onStart(); // proprio codice a seguire }

È importante non dimenticare questa regola, altrimenti le attività sviluppate potrebbero non funzionare correttamente. Descrivere un’attività Dopo che si è creata un’attività, la si deve registrare all’interno del descrittore dell’applicazione (il file AndroidManifest.xml) affinché il sistema sappia della sua esistenza. Per farlo si usa un tag <activity> all’interno del tag <application>: <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package=" mypackage.mysubpackage " ... > <application ... > <activity android:name=".MyActivity" ... > ... </activity> ...

</application> </manifest>

Page 24: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 3 Le attività

Pagina 4

Con l’attributo android:name si specifica il nome della classe registrata come attività. Si può esprimere sia il suo percorso completo (ad esempio mypackage.mysubpackage.MyActivity) sia il nome relativo rispetto al package dichiarato nel tag <manifest> sovrastante (ad esempio .MyActivity). Altri attributi possono opzionalmente essere inseriti nel tag <activity>, allo scopo di meglio dettagliare l’attività che si sta registrando. Tra le tante cose che si possono fare, una delle più importanti è quella di attribuire una label, cioè un’etichetta, all’attività. Si tratta, sostanzialmente, di un titolo che il sistema userà per riferirsi all’attività e per presentarla all’utente. L’attributo da utilizzare è android:label. Come si è spiegato nella lezione precedente, in casi come questo è possibile sia scrivere la stringa direttamente nell’XML sia fare riferimento ad una stringa memorizzata in un file strings.xml sotto la directory res/values. Nel primo caso, quindi, si userà una formula del tipo: <activity android:name=".MyActivity" android:label="La mia attività">

Nel secondo caso, invece, si farà alla seguente maniera: <activity android:name=".MyActivity" android:label="@string/my_activity_label">

Un altro attributo spesso usato con il tag <activity> è android:icon, che permette di specificare un’icona per l’attività. In questo caso si usa sempre il riferimento ad una immagine presente nella cartella res/drawable, qualcosa come: <activity android:name=".MyActivity" android:icon="@drawable/my_activity_icon"> Se non si specifica alcuna icona, l’attività eredita automaticamente l’icona definita nel sovrastante tag <application>. All’interno della coppia di tag <activity> … </activity>, invece, possono essere allacciate delle relazioni particolari fra l’attività e l’applicazione e fra l’attività ed il sistema. In particolar modo, è possibile collegare all’attività un intent-filter: <activity ... > <intent-filter> ... </intent-filter> </activity>

Nel dizionario di Android, un intent è “la descrizione di un’operazione che deve essere eseguita”. Più semplicemente, gli intent sono dei messaggi che il sistema manda ad un’applicazione quando si aspetta che questa faccia qualcosa. Di come funzionano gli intent e di cosa si compongono torneremo certamente a parlare in futuro. Per ora ci basta sapere che le attività, attraverso un intent-filter, possono essere attivate in risposta ad uno specifico evento. Gli intent-filter accettano figli di tre tipi:

<action android:name="nome-azione"> Individua gli intent che richiedono l’azione specificata. <category android:name="nome-categoria"> Individua gli intent che appartengono alla categoria specificata.

Page 25: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 3 Le attività

Pagina 5

<data android:mimeType="nome-tipo-mime"> <data android:scheme="nome-schema-url"> Individua gli intent che portano dati del tipo specificato.

Le azioni, le categorie ed i tipi di dato che possono essere usati sono, naturalmente, moltissimi. Per ora ci interessa sapere che un’attività dotata di un intent-filter che include l’azione android.intent.action.MAIN e la categoria android.intent.category.LAUNCHER viene identificata come l’attività principale dell’applicazione. Ciò significa che l’applicazione sarà elencata nel menù di sistema e che, quando l’utente l’avvierà, sarà lanciata proprio l’attività marcata in tale maniera. Ecco perché, in tutti gli esempi dei mesi precedenti, abbiamo sempre usato una formulazione del tipo: <activity android:name=".MyActivity" ... > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity>

Oltre all’attività principale, possiamo registrare in AndroidManifest.xml quante attività vogliamo, da lanciare secondo necessità, come vedremo tra due paragrafi. ActivityDemo Fermiamoci un attimo con la teoria e mettiamo insieme in un esempio pratico i concetti appresi finora. Lo scopo è dimostrare il ciclo di vita delle attività, attraverso una activity che registri in un log tutti i suoi cambi di stato. Creiamo il progetto ActivityDemo, con package di riferimento mieapplicazioni.activitydemo, ed inseriamo al suo interno la seguente omonima attività: package mieapplicazioni.activitydemo; import android.app.Activity; import android.os.Bundle; import android.util.Log; public class ActivityDemoActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.i("ActivityDemo", "Richiamato onCreate() con bundle " + savedInstanceState); } @Override protected void onRestart() { super.onRestart(); Log.i("ActivityDemo", "Richiamato onRestart()"); } @Override protected void onStart() { super.onStart(); Log.i("ActivityDemo", "Richiamato onStart()"); } @Override protected void onResume() { super.onResume(); Log.i("ActivityDemo", "Richiamato onResume()"); } @Override protected void onPause() {

Page 26: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 3 Le attività

Pagina 6

super.onPause(); Log.i("ActivityDemo", "Richiamato onPause()"); } @Override protected void onStop() { super.onStop(); Log.i("ActivityDemo", "Richiamato onStop()"); } @Override protected void onDestroy() { super.onDestroy(); Log.i("ActivityDemo", "Richiamato onDestroy()"); } }

Questo codice, oltre a mostrarci la prassi corretta da applicare quando si ridefiniscono i metodi che intercettano i cambi di stato dell’attività, ci fa fare conoscenza con la classe android.util.Log. Come è facile intuire, la classe contiene dei metodi statici per scrivere nel log di sistema. Il metodo i(), usato nell’esempio, salva delle righe di log di livello INFO. Altri metodi disponibili sono v() per il livello VERBOSE, d() per il livello DEBUG, w() per il livello WARN, ed e() per il livello ERROR. Registriamo l’attività nel manifesto dell’applicazione, marcandola come attività principale di lancio: <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="mieapplicazioni.activitydemo" android:versionCode="1" android:versionName="1.0"> <application android:icon="@drawable/icon" android:label="@string/app_name"> <activity android:name=".ActivityDemoActivity" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>

Non dimentichiamo di includere un file res/values/strings.xml con le risorse riferite nel manifesto: <?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">ActivityDemo</string> </resources>

L’applicazione è ora pronta per essere avviata. Lanciate l’emulatore e fate qualche prova. Mandate l’attività in secondo piano, magari azionando qualche altra applicazione fra quelle disponibili nel sistema. Quindi date un’occhiata ai log emessi, per verificare il ciclo di vita dell’attività. Usando Eclipse i log possono essere consultati nella prospettiva di lavoro chiamata “DDMS”, nella scheda “LogCat”.

Figura 2 - Nella scheda “LogCat” è possibile seguire i cambi di stato dell'applicazione.

Page 27: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 3 Le attività

Pagina 7

Sotto-attività Come spiegato in apertura, un’applicazione Android può contenere più di un’attività. In questo caso una soltanto sarà marcata come attività principale di lancio. Le altre saranno invece delle sotto-attività, che l’attività principale potrà lanciare quando ce n’è bisogno. Realizzare una sotto-attività è semplice tanto quanto realizzare l’attività principale: ancora una volta è sufficiente estendere android.app.Activity. Le attività secondarie vanno poi registrate nel file AndroidManifest.xml, senza però applicare l’intent-filter con l’azione e la categoria usate invece dall’attività principale. L’attività principale può lanciare delle sotto-attività ricorrendo al metodo startActivity(). Questo accetta come argomento un oggetto di tipo android.content.Intent che, come è facile intuire, rappresenta un intent. Con questo intent bisogna esprimere quale sotto-attività deve essere avviata. Immaginiamo di voler lanciare la sotto-attività rappresentata dalla classe MySubActivity. Tutto quello che dovremo fare, dall’interno dell’attività principale, è formulare un’istruzione del tipo: startActivity(new Intent(this, new MySubActivity));

La sotto-attività verrà lanciata e prenderà lo schermo al posto di quella principale. SubActivityDemo Mettiamo in pratica quanto descritto nel paragrafo precedente. Il progetto si chiama SubActivityDemo, ed il package di riferimento è mieapplicazioni.subactivitydemo. All’interno del package andremo ad inserire due attività: MainActivity e SubActivity. La prima, come il suo nome lascia intendere, sarà poi registrata come attività principale e conterrà un tasto che permetterà il lancio della seconda. SubActivity conterrà invece un tasto per poter essere chiusa e favorire così il ritorno all’attività principale. Cominciamo con MainActivity: package mieapplicazioni.subactivitydemo; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.Button; public class MainActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Button button = new Button(this); button.setText("Lancia SubActivity"); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { startSubActivity(); } }); setContentView(button); } private void startSubActivity() { Intent intent = new Intent(this, SubActivity.class); startActivity(intent); } }

Quanto spiegato nel paragrafo precedente è stato direttamente messo in pratica all’interno del metodo startSubActivity(). Andiamo quindi a studiare il codice di SubActivity:

Page 28: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 3 Le attività

Pagina 8

package mieapplicazioni.subactivitydemo; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.Button; public class SubActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Button button = new Button(this); button.setText("Termina SubActivity"); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { finish(); } }); setContentView(button); } }

Da questo codice apprendiamo una nozione nuova: un’attività, sia principale sia secondaria, può terminare e chiudersi spontaneamente invocando il proprio metodo finish(). Registriamo adesso le due attività nel descrittore dell’applicazione: <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="mieapplicazioni.subactivitydemo" android:versionCode="1" android:versionName="1.0"> <application android:icon="@drawable/icon" android:label="@string/app_name"> <activity android:name=".MainActivity" android:label="@string/main_activity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".SubActivity" android:label="@string/sub_activity" /> </application> </manifest>

Per completare il lavoro abbiamo bisogno di un file res/values/strings.xml: <?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">SubActivityDemo</string> <string name="main_activity">MainActivity</string> <string name="sub_activity">SubActivity</string> </resources>

Abbiamo completato. Non resta che eseguire l’esempio e provare il lancio e la chiusura della sotto-attività.

Page 29: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

A cura di Carlo Pelliccia

Lezione 4

Widget e Layout (Java) Dopo aver scoperto cosa sono e come funzionano le attività, studiamo ora come si costruiscono e si gestiscono le interfacce utente in Android. Attraverso un percorso che durerà alcune lezioni, apprenderemo i principi e le pratiche utili per costruire oggetti grafici in grado di interagire con chi impugna il device. Si tratta di un passaggio cruciale del percorso di apprendimento: tutti i dispositivi mobili di nuova generazione puntano tantissimo sull’interazione con l’utente. Un’applicazione, per essere “cool”, deve essere chiara e di bell’aspetto. Android fornisce numerosi strumenti per raggiungere questo obbiettivo: scopriamoli ed impariamo ad usarli. View e ViewGroup I primi due concetti da assorbire si chiamano View e ViewGroup, e corrispondono alla maniera di Android di classificare ed organizzare ciò che è sullo schermo. I bottoni, i campi di testo, le icone e tutti gli altri congegni di un’interfaccia grafica sono oggetti View. I ViewGroup, invece, sono dei contenitori che possono mettere insieme più oggetti View. I ViewGroup, inoltre, sono a loro volta degli oggetti View, e di conseguenza un possono contenere altri ViewGroup. Grazie a questa intuizione è possibile organizzare i componenti sullo schermo secondo uno schema ad albero, come quello in figura.

Figura 1 - La maniera di Android di organizzare i componenti sullo schermo, attraverso componenti View e ViewGroup.

I componenti View estendono tutti la classe base android.view.View. Nella libreria standard di Android ci sono già molti componenti di questo tipo, soprattutto nel pacchetto android.widget. Oltre ai widget di base, ad ogni modo, è sempre possibile estendere la classe View e realizzare i propri componenti custom. Il più delle volte non c’è bisogno di farlo, poiché quelli forniti da Android bastano per tutte le principali necessità. È comunque importante che sia data questa possibilità. La classe android.view.ViewGroup è una speciale estensione di View. Come accennato in precedenza, e come rappresentato in figura, un ViewGroup è una speciale View che può contenere altre View. Per questo motivo gli oggetti ViewGroup dispongono di diverse implementazioni del metodo addView(), che permette proprio di aggiungere una nuova View al gruppo:

public void addView(View child) Aggiunge un oggetto View al gruppo. public void addView(View child, int index) Aggiunge un oggetto View al gruppo, specificandone la posizione attraverso un indice (index).

Page 30: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 4

Widget e Layout (Java)

Pagina 2

public void addView(View child, int width, int height) Aggiunge un oggetto View al gruppo, specificandone larghezza (width) ed altezza (height). public void addView(View child, ViewGroup.LayoutParams params) Aggiunge un oggetto View al gruppo, applicando una serie di parametri di visualizzazione ed organizzazione del componente (params). public void addView(View child, int index, ViewGroup.LayoutParams params) Aggiunge un oggetto View al gruppo, specificandone la posizione attraverso un indice (index) ed applicando una serie di parametri di visualizzazione ed organizzazione del componente (params).

ViewGroup è una classe astratta. Pertanto non può essere istanziata direttamente. Come nel caso di View, è possibile realizzare il proprio ViewGroup custom, ma il più delle volte conviene scegliere fra le tante implementazioni messe a disposizione dalla libreria Android. Queste implementazioni differiscono nella maniera di presentare i componenti che sono al loro interno: alcuni li mettono uno dopo l’altro, altri li organizzano in una griglia, altri ancora possono essere usati per avere una gestione a schede dello schermo, e così via. Ovviamente conosceremo presto tutte le principali implementazioni di ViewGroup. Una volta che, combinando oggetti View e ViewGroup, si è ottenuta l’interfaccia utente che si desidera, è necessario che questa venga mostrata sullo schermo. Come abbiamo scoperto mediante alcuni esempi preliminari, le attività (cioè gli oggetti android.app.Activity) mettono a disposizione un metodo setContentView(), disponibile nelle seguenti forme:

public void setContentView(View view) Mostra sullo schermo l’oggetto View specificato. public void setContentView(View view, ViewGroup.LayoutParams params) Mostra sullo schermo l’oggetto View specificato, applicando una serie di parametri di visualizzazione ed organizzazione del componente (params).

Widget Con il termine widget (congegno) si indicano quei componenti di base per l’interazione con l’utente, come i bottoni, le check box, le liste, i campi di testo e così via. I widget predefiniti di Android estendono tutti (direttamente o indirettamente) la classe View, e sono conservati nel package android.widget. Esaminiamone alcuni in una veloce panoramica: android.widget.TextView Permette di mostrare del testo all’utente. Il messaggio da visualizzare può essere impostato con il metodo setText(), che può accettare come parametro sia una stringa sia un riferimento a risorsa preso dal gruppo R.string.

Figura 2 - L'aspetto di un componente TextView.

Page 31: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 4

Widget e Layout (Java)

Pagina 3

android.widget.EditText Estende TextView e permette all’utente di modificare il testo mostrato. Il testo digitato può essere recuperato con il metodo getText(), che restituisce un oggetto del tipo android.text.Editable. Gli oggetti Editable sono simili alle stringhe, ed infatti implementano l’interfaccia java.lang.CharSequence.

Figura 3 - L'aspetto di un componente EditText.

android.widget.Button Realizza un bottone che l’utente può premere o cliccare. Il componente espande TextView, e per questo è possibile impostare il testo mostrato al suo interno con il metodo setText(), sia con parametro stringa sia con riferimento a risorsa del gruppo R.string.

Figura 4 - L'aspetto di un componente Button.

android.widget.ImageView Un componente utile per mostrare un’immagine, ad esempio un’icona. Metodi utili per impostare l’immagine mostrata sono: setImageBitmap(), che accetta un oggetto di tipo android.graphics. Bitmap; setImageDrawable(), che accetta un argomento android.graphics.drawable.Drawable; setImageResource(), che accetta un riferimento a risorsa drawable, tipicamente dal gruppo R.drawable.

Figura 5 - L'aspetto di un componente ImageView.

Page 32: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 4

Widget e Layout (Java)

Pagina 4

android.widget.ImageButton Un bottone con un’immagine. Estende ImageView, e quindi espone gli stessi metodi di quest’ultima per impostare l’immagine mostrata.

Figura 6 - L'aspetto di un componente ImageButton.

android.widget.CheckBox Questo componente realizza una casella di spunta (check box, appunto). Estende Button e TextView, pertanto il testo a fianco della casella può essere impostato con i metodi setText() già noti.

Figura 7 - L'aspetto di un componente CheckBox.

android.widget.RadioButton Questo componente realizza un bottone radio. Come nel caso di CheckBox, le classi base Button e TextView forniscono i metodi necessari per l’impostazione del testo visualizzato. Un bottone radio, da solo, non ha senso. Due o più bottoni radio, pertanto, possono essere raggruppati all’interno di un android.widget.RadioGroup. L’utente, così, potrà attivare soltanto una delle opzioni del gruppo.

Figura 8 - L'aspetto di tre componenti RadioButton raccolti in un contenitore RadioGroup.

android.widget.ToggleButton Un bottone “ad interruttore”, che può essere cioè “on” o “off”. Può essere usato per far attivare o disattivare delle opzioni.

Figura 9 - L'aspetto di un componente ToggleButton.

Page 33: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 4

Widget e Layout (Java)

Pagina 5

android.widget.DatePicker Un componente che permette di scegliere una data selezionando giorno, mese ed anno. La data impostata dall’utente può essere recuperata servendosi dei metodi getDayOfMonth(), getMonth() e getYear().

Figura 10 - L'aspetto di un componente DatePicker.

android.widget.TimePicker Un componente che permette di scegliere un orario selezionando ora e minuto. L’orario impostato dall’utente può essere recuperato servendosi dei metodi getCurrentHour() e getCurrentMinute().

Figura 11 - L'aspetto di un componente TimePicker.

android.widget.AnalogClock Un componente che mostra all’utente un orologio analogico.

Figura 12 - L'aspetto di un componente AnalogClock.

android.widget.DigitalClock Un componente che mostra all’utente un orologio digitale.

Figura 13 - L'aspetto di un componente DigitalClock.

Tutti gli oggetti discussi finora richiedono, nei loro costruttori, un oggetto che estenda la classe astratta android.content.Context. Si tratta di una struttura che permette l’accesso al sistema e che costituisce il contesto di esecuzione dell’applicazione. Non dovete preoccuparvi di come ottenere

Page 34: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 4

Widget e Layout (Java)

Pagina 6

oggetti di questo tipo: android.app.Activity estende indirettamente Context, per cui dall’interno di un’attività, vi sarà sufficiente usare la parola chiave this. Ad esempio: Button button = new Button(this);

La considerazione vale per le attività, ma anche per tanti altri contesti della programmazione Android: più o meno tutte le classi che sono mattoni fondamentali del sistema estendono direttamente o indirettamente la classe astratta android.content.Context. Layout Con il termine layout (disposizione, impaginazione), in Android, si identificano tutti quei ViewGroup utilizzabili per posizionare i widget sullo schermo. Android fornisce una serie di layout predefiniti. Esaminiamone alcuni. android.widget.FrameLayout Il più semplice e basilare dei layout: accetta un widget, lo allinea in alto a sinistra e lo estende per tutta la dimensione disponibile al layout stesso. Ecco un semplice esempio di utilizzo, che allarga un bottone all’intera area a disposizione di un’attività: Button button = new Button(this); button.setText("Bottone"); FrameLayout layout = new FrameLayout(this); layout.addView(button); setContentView(layout);

Figura 14 - Un bottone, inserito in un FrameLayout, viene espanso fino alla dimensione massima a disposizione del layout che,

nel caso specifico, corrisponde all’intero schermo.

android.widget.RelativeLayout A differenza di FrameLayout, disegna un componente aggiunto al suo interno nelle sue dimensioni ideali, senza allargarlo per ricoprire l’intera area a disposizione. Per default, il componente aggiunto viene allineato in alto a sinistra, ma è possibile controllare l’allineamento servendosi del metodo setGravity(). Questo accetta un argomento di tipo int, che è bene scegliere fra le costanti messe a disposizione nella classe android.view.Gravity. I valori possibili, nel caso di RelativeLayout, sono i seguenti:

Gravity.TOP allinea il widget in alto. Gravity.BOTTOM allinea il widget in basso.

Page 35: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 4

Widget e Layout (Java)

Pagina 7

Gravity.LEFT allinea il widget a sinistra. Gravity.RIGHT allinea il widget a destra. Gravity.CENTER_HORIZONTAL allinea il widget al centro orizzontalmente. Gravity.CENTER_VERTICAL allinea il widget al centro verticalmente. Gravity.CENTER allinea il widget al centro sia orizzontalmente sia verticalmente.

Più costanti Gravity, purché non in contrasto fra di loro, possono essere concatenate in un solo valore servendosi dell’operatore binario OR (che in Java si rende con il simbolo |, detto pipe). Ad esempio per allineare in basso a destra si scrive: Gravity.BOTTOM | Gravity.RIGHT

Ecco un campione di codice che dimostra l’uso di un RelativeLayout all’interno di un’attività: Button button = new Button(this); button.setText("Bottone"); RelativeLayout layout = new RelativeLayout(this); layout.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL); layout.addView(button); setContentView(layout);

Figura 15 - Un RelativeLayout utilizzato per disporre un bottone, nelle sue dimensioni ideali, in alto al centro dello spazio

disponibile.

android.widget.LinearLayout Un layout utile per disporre più componenti uno di seguito all’altro, sia orizzontalmente sia verticalmente. Una volta creato il layout, il suo orientamento può essere stabilito chiamando il metodo setOrientation(), con argomento pari a LinearLayout.HORIZONTAL o LinearLayout. VERTICAL. Con l’orientamento orizzontale i componenti verranno messi tutta sulla stessa riga, uno dopo l’altro. Con l’allineamento verticale, invece, si procede lungo una colonna, e quindi i widget saranno uno sopra l’altro. Esaminiamo il caso dell’allineamento orizzontale. In questo caso i componenti vengono introdotti lungo una sola linea. Il sistema accetta di aggiungere componenti finché c’è spazio. Se si va di poco oltre la dimensione della riga, il sistema tenta un aggiustamento restringendo i componenti al di sotto delle loro dimensioni ideali. Raggiunto un certo limite, comunque, il sistema si rifiuta di andare oltre, ed i componenti di troppo non saranno più visualizzati. Il metodo setGravity(), nell’allineamento orizzontale, può essere usato per decidere dove posizionare e come organizzare la riga dei componenti rispetto allo spazio disponibile. Ecco un esempio: Button button1 = new Button(this); button1.setText("Bottone 1"); Button button2 = new Button(this); button2.setText("Bottone 2"); Button button3 = new Button(this); button3.setText("Bottone 3"); LinearLayout layout = new LinearLayout(this); layout.setOrientation(LinearLayout.HORIZONTAL); layout.setGravity(Gravity.CENTER_HORIZONTAL); layout.addView(button1); layout.addView(button2); layout.addView(button3);

Page 36: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 4

Widget e Layout (Java)

Pagina 8

setContentView(layout);

Figura 16 - Un LinearLayout che allinea tre bottoni in orizzontale, al centro.

Nei LinearLayout verticali i componenti vengono aggiunti uno sopra all’altro, ed espansi in orizzontale fino ad occupare tutto lo spazio a disposizione del layout. In questo caso setGravity() può essere usato per decidere se allineare la colonna in alto, in basso o al centro. Il sistema aggiunge componenti finché c’è spazio nella colonna. Superato il limite, i componenti di troppo non vengono visualizzati. Ecco un esempio: Button button1 = new Button(this); button1.setText("Bottone 1"); Button button2 = new Button(this); button2.setText("Bottone 2"); Button button3 = new Button(this); button3.setText("Bottone 3"); LinearLayout layout = new LinearLayout(this); layout.setOrientation(LinearLayout.VERTICAL); layout.setGravity(Gravity.TOP); layout.addView(button1); layout.addView(button2); layout.addView(button3); setContentView(layout);

Figura 17 - Un LinearLayout che allinea tre bottoni in verticale, in alto.

La classe android.widget.RadioGroup, presentata sopra ed utile per mettere insieme più RadioButton, estende LinearLayout e gode pertanto di tutte le proprietà appena mostrate. android.widget.TableLayout Un layout che permette di sistemare i componenti secondo uno schema a tabella, suddiviso cioè in righe e colonne. I TableLayout vanno costruiti aggiungendo al loro interno degli oggetti TableRow, ciascuno dei quali forma una riga della tabella. Ogni riga è suddivisa in colonne. In ciascuna cella può essere inserito un componente. La gravità, cioè il metodo setGravity(), può essere usato sia su TableLayout che su TableRow, per stabilire gli allineamenti relativi. Ecco un semplice esempio di tre righe e tre colonne: int rows = 3; int columns = 3; TableLayout layout = new TableLayout(this); layout.setGravity(Gravity.CENTER); for (int i = 0; i < rows; i++) { TableRow tableRow = new TableRow(this); tableRow.setGravity(Gravity.CENTER);

Page 37: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 4

Widget e Layout (Java)

Pagina 9

for (int j = 0; j < columns; j++) { Button button = new Button(this); button.setText("Bottone " + ((columns * i) + j + 1)); tableRow.addView(button); } layout.addView(tableRow); } setContentView(layout);

Figura 18 - Un TableLayout con nove bottoni disposti su tre righe e tre colonne.

Si faccia attenzione al fatto che, se la tabella eccede le dimensioni a disposizione, una parte di essa non sarà visibile. Su come Android ripartisca la dimensione da assegnare a ciascuna colonna, si può agire con i seguenti metodi:

public void setColumnCollapsed(int columnIndex, boolean isCollapsed) Stabilisce se una colonna è collapsed. Quando una colonna è collapsed, non viene mostrata sullo schermo. public void setColumnShrinkable(int columnIndex, boolean isShrinkable) Stabilisce se una colonna è shrinkable. Quando una colonna è shrinkable, il sistema cerca di restringerla il più possibile, per fare in modo che occupi poco spazio. public void setColumnStretchable(int columnIndex, boolean isStretchable) Stabilisce se una colonna è stretchable. Quando una colonna è stretchable, il sistema tende ad allargarla fornendogli lo spazio extra di cui dispone.

Si faccia attenzione al fatto che gli indici delle colonne, in Android, partono da 0. TableLayout dispone inoltre di alcuni metodi di comodo che permettono, in un sol colpo, di applicare le medesime impostazioni di shrink o di stretch a tutte le colonne. Questi metodi sono setShrinkAllColumns() e setStretchAllColumns(). Mettere insieme widget e layout I widget ed i layout illustrati sinora, naturalmente, devono essere combinati in maniera coerente. I layout, in maniera particolare, possono e devono essere annidati l’uno dentro l’altro, finché non si ottiene il design desiderato. Proviamo insieme con un esempio semplice ed efficace. Facciamo il caso che dobbiamo realizzare, in un’attività, una maschera di input attraverso la quale l’utente può specificare il proprio nome, il proprio cognome ed il proprio sesso. Al termine dell’operazione, l’utente può salvare i dati con un tasto “Salva” o annullarli con il bottone “Annulla”. Ecco il codice necessario per realizzare quanto teorizzato: TextView label1 = new TextView(this); label1.setText("Nome:"); EditText edit1 = new EditText(this); TextView label2 = new TextView(this); label2.setText("Cognome:");

Page 38: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 4

Widget e Layout (Java)

Pagina 10

EditText edit2 = new EditText(this); TextView label3 = new TextView(this); label3.setText("Sesso:"); RadioButton radio1 = new RadioButton(this); radio1.setText("M"); RadioButton radio2 = new RadioButton(this); radio2.setText("F"); RadioGroup radioGroup1 = new RadioGroup(this); radioGroup1.setOrientation(LinearLayout.HORIZONTAL); radioGroup1.setGravity(Gravity.CENTER); radioGroup1.addView(radio1); radioGroup1.addView(radio2); Button button1 = new Button(this); button1.setText("Salva"); Button button2 = new Button(this); button2.setText("Annulla"); TableRow row1 = new TableRow(this); row1.setGravity(Gravity.CENTER); row1.addView(label1); edit1.setGravity(Gravity.FILL); row1.addView(edit1); TableRow row2 = new TableRow(this); row2.setGravity(Gravity.CENTER); row2.addView(label2); row2.addView(edit2); TableRow row3 = new TableRow(this); row3.setGravity(Gravity.CENTER); row3.addView(label3); row3.addView(radioGroup1); TableLayout tableLayout = new TableLayout(this); tableLayout.setGravity(Gravity.CENTER_HORIZONTAL | Gravity.TOP); tableLayout.addView(row1); tableLayout.addView(row2); tableLayout.addView(row3); tableLayout.setColumnShrinkable(0, true); tableLayout.setColumnStretchable(1, true); LinearLayout linearLayout1 = new LinearLayout(this); linearLayout1.setGravity(Gravity.CENTER); linearLayout1.setOrientation(LinearLayout.HORIZONTAL); linearLayout1.addView(button1); linearLayout1.addView(button2); LinearLayout linearLayout2 = new LinearLayout(this); linearLayout2.setGravity(Gravity.CENTER); linearLayout2.setOrientation(LinearLayout.VERTICAL); linearLayout2.setPadding(5, 5, 5, 5); linearLayout2.addView(tableLayout); linearLayout2.addView(linearLayout1); setContentView(linearLayout2);

Figura 19 - Una maschera di immissione dati realizzata combinando più widget e più layout.

Si sono adoperati dei widget TextView, per le etichette, EditText, per i campi ad immissione libera, RadioButton (con RadioGroup), per la selezione tra un elenco di opzioni, e Button, per i bottoni di azione finali. I componenti sono stati disposti sullo schermo annidando diversi tipi di layout. I campi del modulo sono stati messi insieme servendosi di un TableLayout, che dispone le etichette sulla colonna di sinistra ed i widget manipolabili su quella di destra. La pulsantiera con i bottoni “Salva” e “Annulla” è stata invece realizzata servendosi di un LinearLayout orizzontale, che affianca e centra i due widget. I due layout, infine, sono stati messi insieme servendosi di un terzo

Page 39: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 4

Widget e Layout (Java)

Pagina 11

contenitore, nello specifico un LinearLayout verticale, che ha disposto la tabella in alto e la pulsantiera sotto di questa. Tutto, infine, è stato centrato sullo schermo.

Dimensioni dei widget I widget sono in grado di calcolare automaticamente una propria dimensione ideale, che varia in base allo stile ed al contenuto applicati. La dimensione ideale di un bottone con una lunga scritta al suo interno, ad esempio, sarà calcolata automaticamente più ampia di un altro bottone con un testo breve. La dimensione calcolata dai widget è definita dimensione ideale. In ogni caso, non è detto che il contenitore in cui il widget sarà inserito rispetterà la sua dimensione ideale. Alcuni tipi di layout, ad esempio, estendono i componenti al loro interno per occupare aree più ampie, mentre altri li comprimono. Un certo grado di controllo, poi, è lasciato al programmatore: diversi widget (ma non tutti) dispongono di metodi setWidth() e setHeight() con i quali è possibile variare le loro dimensioni ideali. In certi casi è possibile anche specificare delle dimensioni minime e delle dimensioni massime entro le quali i layout sono pregati di scegliere quelle più adattate al componente. I metodi utili sono: setMinWidth(), setMinHeight(), setMaxWidth() e setMaxHeight().

Padding Quando si gestisce un contenitore è possibile impostare su di esso un padding. Significa che ai componenti che saranno inseriti nel contenitore verrà applicato un margine che li distanzierà dai bordi del contenitore. La classe View, da cui discendono tutti i componenti compresi i contenitori, permette il controllo del padding attraverso il metodo setPadding(). Questo metodo richiede quattro argomenti di tipo int che definiscono la misura del margine in pixel da applicare, rispettivamente, dal bordo sinistro, da quello in alto, da quello a destra e da quello in basso.

Page 40: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

A cura di Carlo Pelliccia

Lezione 5 Widget e Layout (XML)

Con la precedente lezione abbiamo imparato a disporre sullo schermo i principali widget messi a disposizione da Android: bottoni, caselle di testo, check box e via discorrendo. La logica che permea la loro creazione ed il loro utilizzo, come abbiamo potuto osservare, non si discosta di molto da quella adottata da altre librerie Java per le interfacce utente, come AWT e Swing. Questo modo di creare le GUI è potente, ma anche estremamente tedioso. Ogni volta che si deve utilizzare un widget, lo si deve creare, personalizzare ed inserire in un contenitore predisposto in precedenza. Per ogni componente e per ogni contenitore ci vogliono diverse righe di codice, anche nei casi più semplici. La programmazione di interfacce complesse, di conseguenza, è poco intuitiva e piuttosto noiosa. Sin dalle origini delle interfacce basate sui widget, i creatori delle piattaforme di sviluppo hanno cercato di porre rimedio a questo difetto. Nella maggior parte dei casi si è fatto ricorso ad editor visuali: il programmatore, anziché scrivere codice, trascina i componenti sull’editor, dimensionandoli ad occhio ed impostandone le caratteristiche salienti mediante delle procedure guidate. Il lavoro sporco lo fa l’editor in sottofondo, generando ed interpretando il codice di programmazione necessario. Questo approccio è valido, ma da solo non costituisce una vera e propria soluzione del problema. Il codice prolisso e difficile da gestire, infatti, è ancora lì: l’ambiente di sviluppo non ha fatto altro che nascondere la sporcizia sotto il tappeto. Gli editor visuali, poi, sono molto difficili da realizzare, perché devono interpretare e generare del codice complesso, ed infatti ne esistono davvero pochi di buona qualità. Il codice generato automaticamente, infine, è spesso un obbrobrio. L’ambiente, infatti, non ha l’intelligenza sufficiente per scrivere e mantenere un codice leggibile e performante. Apriamo ora una parentesi, per ricollegarci all’argomento più tardi. Un tempo si riteneva che la programmazione Web fosse, in qualche misura, meno pregiata della programmazione di applicazioni native. Molti “puristi” hanno creduto (ed alcuni credono ancora oggi) che fare software per il Web significhi lavorare con piattaforme inferiori e di scarsa qualità, ed hanno guardato con sdegno strumenti quali HTML, XML e JavaScript. I fatti, invece, hanno poi dimostrato il contrario. Oggi lo sviluppo delle applicazioni Web è complesso tanto quanto quello delle applicazioni desktop, se non addirittura di più. Con l’avvento dei browser moderni, di AJAX e degli interpreti di nuova concezione, si sono portate sul Web molte applicazioni che, fino a ieri, erano appannaggio esclusivo degli ambienti desktop e dei linguaggi compilati. I client di posta elettronica, ad esempio, stanno scomparendo a favore delle webmail. Il proliferare delle applicazioni Web sta facendo maturare velocemente gli strumenti di sviluppo propri di questo ambito. All’inizio la programmazione Web sottraeva idee alla programmazione delle applicazioni native, emulandone approcci e strumenti; ora, invece, si assiste ad un’inversione di tendenza. La programmazione Web, ad esempio, ha dimostrato quanto sia più facile gestire un’interfaccia utente descrivendone i componenti con un linguaggio a marcatori, anziché con un linguaggio di programmazione. I linguaggi a marcatori come HTML ed XML ben si prestano a questo genere di operazioni: sono più facili da leggere e da scrivere, sia per l’uomo sia per la macchina (cioè per gli editor visuali). Così oggi le piattaforme moderne applicano alla programmazione di applicazioni native il medesimo principio, fornendo agli sviluppatori framework ed editor basati perlopiù su XML. Uno dei primi tentativi in tal senso ad aver avuto successo è stato XUL, nato in casa Mozilla ed impiegato per le GUI di Firefox e di altre applicazioni della fondazione, poi importato anche in altri ambiti ed ambienti (ad esempio ne esiste un’implementazione per Swing). Torniamo ora ad Android, ed ancora una volta constatiamo come esso sia un sistema operativo di moderna concezione. Come abbiamo imparato nella lezione precedente, le GUI possono essere realizzate con un approccio classico, basato sul codice di programmazione. Spesso e volentieri, però, è più semplice ricorrere all’approccio moderno, basato su XML, che Android rende disponibile nativamente.

Page 41: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 5

Widget e Layout (XML)

Pagina 2

Layout XML Veniamo al dunque. Avrete sicuramente notato che la struttura predefinita di un progetto Android creato in Eclipse contiene sempre la directory res/layout. Abbiamo già conosciuto alcune fra le sottodirectory di res e, in tutti i casi, abbiamo osservato come la piattaforma di sviluppo gestisse in maniera speciale le differenti categorie di risorse possibili. La cartella layout non fa eccezione. Al suo interno possono essere disposti dei file XML che il sistema interpreterà come descrizioni dichiarative dei layout e dei widget che saranno poi usati in una o più attività dell’applicazione. Un esempio, in questo caso, vale più di mille parole. La lezione precedente abbiamo chiuso con questo codice: TextView label1 = new TextView(this); label1.setText("Nome:"); EditText edit1 = new EditText(this); TextView label2 = new TextView(this); label2.setText("Cognome:"); EditText edit2 = new EditText(this); TextView label3 = new TextView(this); label3.setText("Sesso:"); RadioButton radio1 = new RadioButton(this); radio1.setText("M"); RadioButton radio2 = new RadioButton(this); radio2.setText("F"); RadioGroup radioGroup1 = new RadioGroup(this); radioGroup1.setOrientation(LinearLayout.HORIZONTAL); radioGroup1.setGravity(Gravity.CENTER); radioGroup1.addView(radio1); radioGroup1.addView(radio2); Button button1 = new Button(this); button1.setText("Salva"); Button button2 = new Button(this); button2.setText("Annulla"); TableRow row1 = new TableRow(this); row1.setGravity(Gravity.CENTER); row1.addView(label1); edit1.setGravity(Gravity.FILL); row1.addView(edit1); TableRow row2 = new TableRow(this); row2.setGravity(Gravity.CENTER); row2.addView(label2); row2.addView(edit2); TableRow row3 = new TableRow(this); row3.setGravity(Gravity.CENTER); row3.addView(label3); row3.addView(radioGroup1); TableLayout tableLayout = new TableLayout(this); tableLayout.setGravity(Gravity.CENTER_HORIZONTAL | Gravity.TOP); tableLayout.addView(row1); tableLayout.addView(row2); tableLayout.addView(row3); tableLayout.setColumnShrinkable(0, true); tableLayout.setColumnStretchable(1, true); LinearLayout linearLayout1 = new LinearLayout(this); linearLayout1.setGravity(Gravity.CENTER); linearLayout1.setOrientation(LinearLayout.HORIZONTAL); linearLayout1.addView(button1); linearLayout1.addView(button2); LinearLayout linearLayout2 = new LinearLayout(this); linearLayout2.setGravity(Gravity.CENTER); linearLayout2.setOrientation(LinearLayout.VERTICAL); linearLayout2.setPadding(5, 5, 5, 5); linearLayout2.addView(tableLayout); linearLayout2.addView(linearLayout1); setContentView(linearLayout2);

Si tratta del codice necessario per assemblare un modulo per l’inserimento dei dati anagrafici di una persona (nome, cognome, sesso). È stato un buon espediente per mostrare l’utilizzo combinato di

Page 42: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 5

Widget e Layout (XML)

Pagina 3

alcuni layout (LinearLayout, TableLayout) ed alcuni widget (TextView, EditText, Button, RadioButton).

Figura 1 - Un form che permette all’utente l’inserimento dei suoi dati anagrafici di base.

L’esempio ha l’effetto collaterale di dimostrare come sia poco naturale usare Java per assemblare un’interfaccia utente: il codice è poco leggibile, i nomi delle variabili poco significativi, e bisogna concentrarsi molto per capire chi contiene cosa. È il classico caso in cui il formato XML è vantaggioso: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:padding="5px" android:gravity="center" android:orientation="vertical" android:layout_gravity="center"> <TableLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:gravity="center" android:layout_gravity="top|center_horizontal" android:shrinkColumns="0" android:stretchColumns="1"> <TableRow android:layout_width="fill_parent" android:layout_height="wrap_content" android:gravity="fill_horizontal|center_vertical"> <TextView android:layout_width="fill_parent" android:layout_height="fill_parent" android:text="Nome:" /> <EditText android:layout_width="fill_parent" android:layout_height="fill_parent" /> </TableRow> <TableRow android:layout_width="fill_parent" android:layout_height="wrap_content" android:gravity="fill_horizontal|center_vertical"> <TextView android:layout_width="fill_parent" android:layout_height="fill_parent" android:text="Cognome:" /> <EditText android:layout_width="fill_parent" android:layout_height="fill_parent" /> </TableRow> <TableRow android:layout_width="fill_parent" android:layout_height="wrap_content" android:gravity="fill_horizontal|center_vertical"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Sesso:" />

Page 43: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 5

Widget e Layout (XML)

Pagina 4

<RadioGroup android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" android:layout_gravity="center"> <RadioButton android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="M" /> <RadioButton android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="F" /> </RadioGroup> </TableRow> </TableLayout> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Salva" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Annulla" /> </LinearLayout> </LinearLayout>

Questo XML, dopo averci fatto l’occhio, risulta molto più semplice da gestire del suo corrispettivo Java. Anzitutto, per quanto riguarda la composizione dei layout, l’utilizzo dell’indentatura permette di individuare immediatamente chi è il contenitore e chi il contenuto. Gli attributi di XML, poi, sono molto più semplici ed intuitivi, rispetto ai metodi del tipo setProprietà() di Java. Con gli attributi è più semplice impostare le proprietà di ogni singolo componente, come il testo visualizzato, il padding, la gravità e così via. Creare un editor visuale in grado di leggere e scrivere questo XML, inoltre, è estremamente più facile che realizzare un editor in grado di fare lo stesso con del codice Java. In Eclipse, insieme con il plug-in ADT per lo sviluppo dei progetti Android, ne avete già installato uno. Provate a creare un file di layout in un progetto Android, sotto il percorso res/layout. Facendo doppio clic sul file l’ambiente lo aprirà nel suo editor visuale.

Figura 2 - L’editor visuale compreso con ADT per la creazione guidata dei layout XML in ambiente Eclipse.

Page 44: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 5

Widget e Layout (XML)

Pagina 5

Qui è possibile aggiungere layout e widget semplicemente pescandoli dal menù sulla sinistra e trascinandoli sulla schermata al centro, che rappresenta l’interfaccia grafica così come apparirà nello smartphone. Selezionando un componente è possibile accedere all’elenco delle sue proprietà, mostrate nella scheda “Properties” in basso. Da qui è possibile manipolare i parametri del componente. Con un po’ di pratica si riesce a costruire velocemente qualsiasi tipo di interfaccia. È comunque possibile lavorare “a mano” sul codice XML, attivando la linguetta in basso che riporta il nome del file aperto. L’ambiente, anche in questo caso, fornisce alcune utili agevolazioni, come l’auto-completamento dei tag, degli attributi e dei valori, con tanto di aiuto in linea, come mostrato in figura.

Figura 3 - Anche passando all’editing manuale dei layout XML è possibile usufruire di alcune agevolazioni in ambiente

Eclipse ADT, come l’auto-completamento e l’help in linea per tag ed attributi.

Richiamare un layout XML Nella directory res/layout si possono memorizzare quanti file si desidera. L’ambiente li compila e genera automaticamente un riferimento verso ciascuno di essi nella classe R, nel gruppo layout. Ad esempio il file res/layout/mioLayout.xml avrà il suo riferimento in R.layout.mioLayout. Questo riferimento, passando al codice Java, può essere utilizzato per invocare ed adoperare il layout realizzato. La classe Activity, ad esempio, dispone di una versione di setContentView() che accetta come argomento un riferimento ad un oggetto di tipo layout. Continuando con l’esempio XML del paragrafo precedente (che va salvato al percorso res/layout/main.xml), realizziamo un’attività in grado di caricare e mostrare il layout realizzato: package mieapplicazioni.test; import android.app.Activity; import android.os.Bundle; public class TestActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); } }

Regole generali Entriamo un po’ più nel dettaglio del formato XML usato per descrivere un layout. Come è facile intuire, i tag adoperabili al suo interno corrispondono ai nomi dei widget e dei layout manager

Page 45: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 5

Widget e Layout (XML)

Pagina 6

descritti nella lezione precedente. In pratica al widget Java android.widget.TextView corrisponde il tag XML <TextView>, al layout manager android.widget.LinearLayout corrisponde <LinearLayout>, e così via. Potete riprendere la lezione precedente e desumere da voi le corrispondenze fra classi e tag. È invece importante sapere che il namespace da utilizzare è: http://schemas.android.com/apk/res/android Normalmente lo si fa dichiarando il namespace nel primo tag utilizzato (quello di ordine superiore), abbinandogli lo shortname android. In breve, il modello da seguire è il seguente: <tag1 xmlns:android="http://schemas.android.com/apk/res/android" android:attr1="val1" android:attr2="val2"> <tag2 android:attr1="val1" android:attr2="val2" /> <tag2 android:attr1="val1" android:attr2="val2" /> </tag1>

Là dove gli attributi sono utilizzati per immettere un valore libero, ad esempio un testo, una dimensione, un colore, un’immagine e così via, è possibile sfruttare i riferimenti ad altri materiali conservati nella gerarchia della cartella res. Quando è stato trattato lo speciale file AndroidManifest.xml abbiamo imparato ad usare i riferimenti del tipo: @tipo/nome Ad esempio, per richiamare la stringa “titoloApplicazione” definita in un XML sotto res/values, è possibile fare: @string/titoloApplicazione Bene, la stessa identica cosa può essere fatta in un XML di layout. Ad esempio, si può ricorrere a questa funzionalità quando si imposta il testo mostrato in un bottone. Invece di scrivere: <Button android:text="Salva" />

Si può scrivere: <Button android:text="@string/etichettaBottoneSalva" />

A patto, ovviamente, di aver creato in res/values un file XML che definisca la risorsa stringa etichettaBottoneSalva, come ad esempio: <?xml version="1.0" encoding="utf-8"?> <resources> <string name="etichettaBottoneSalva">Salva</string> </resources>

La regola vale non soltanto per le stringhe, ma per tutte le categorie di risorse. Ricapitoliamole:

• @array, per gli array. • @color, per i colori. • @dimen, per le dimensioni. • @drawable, per i valori drawable, ma anche per le immagini messe in res/drawable. • @layout, per richiamare altri layout.

Page 46: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 5

Widget e Layout (XML)

Pagina 7

• @raw, per i file nella cartella res/raw. • @string, per le stringhe. • @style, per gli stili.

Assegnare un ID ai componenti A ciascun componente dichiarato nell’XML, sia esso un contenitore o un widget, è possibile assegnare un identificativo, utile per rintracciare successivamente il componente. Per assegnare un identificativo si deve utilizzare l’attributo id: <Tag android:id="..." /> Per assegnare l’ID è necessario seguire una particolare sintassi, basata sul seguente modello: @+nomeGruppo/nomeId Questa sintassi fa sì che nella classe R venga introdotto, se non esiste già, il gruppo nomeGruppo, e che al suo interno venga memorizzato il riferimento all’ID nomeId. Sarà pertanto possibile ricorrere all’ID, nel codice Java, usando il riferimento: R.nomeGruppo.nomeId

Facciamo il caso di questo bottone: <Button android:id="@+idBottoni/salva" android:text="Salva" />

In Java sarà possibile richiamarlo adoperando il riferimento: R.idBottoni.salva

Le attività dispongono del metodo findViewById(), che come il nome lascia presupporre, ricercano nel layout caricato un componente avente l’ID specificato come argomento. Il bottone può a questo punto essere recuperato e manipolato (ad esempio per aggiungere un gestore di evento, come impareremo prossimamente): Button button = (Button) findViewById(R.idBottoni.salva);

Attributi comuni Gli attributi applicabili ad un tag variano a seconda del componente cui fanno riferimento. Ad esempio il tag <Button> dispone dell’attributo text con il quale è possibile impostare il testo visualizzato all’interno del bottone, mentre il tag <LinearLayout> ha l’attributo orientation che serve per stabilire la direzione in cui disporre i widget. In alcuni casi gli attributi sono obbligatori, mentre in altri sono opzionali. La maggior parte dei componenti, poi, dispone davvero di molti attributi. Per esplorare tutti gli attributi di ogni specifico widget, di conseguenza, è meglio affidarsi alle procedure guidate di un editor visuale, come quello di Eclipse ADT descritto sopra. Gli stessi attributi, naturalmente, sono esaustivamente riportati anche nella documentazione ufficiale. Tutto ciò per dire che, in generale, non ci inalbereremo nello studio di tutti gli attributi possibili. Ci concentreremo invece su quelli comuni o comunque più interessanti. I primi due che esaminiamo sono sempre obbligatori, e si chiamano layout_width e layout_height. Servono per decretare come si relazioni l’oggetto rispetto alle dimensioni del suo contenitore. Due sono i valori possibili:

Page 47: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 5

Widget e Layout (XML)

Pagina 8

wrap_content Rende il componente grande tanto quanto impongono i suoi sotto-componenti. In pratica tutti i sotto-componenti vengono dimensionati rispetto, possibilmente, alla loro dimensione ideale, e poi il contenitore che li contiene viene dimensionato di conseguenza. fill_parent Allarga il componente fino a fargli occupare tutto lo spazio a sua disposizione concessogli dal suo contenitore d’ordine superiore.

Ad esempio: <Button

android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Salva" />

Questo bottone sarà grande tanto quanto basta a mostrare la scritta “Salva”. Quest’altro invece: <Button

android:layout_width="fill_parent" android:layout_height="fill_parent" android:text="Salva" />

Sarà allargato, sia in orizzontale sia in verticale, fino ad occupare tutto lo spazio messo a sua disposizione.

Figura 4 - Le quattro differenti maniere di dimensionare un componente rispetto al suo contenitore. Da sinistra a destra: wrap_content su layout_width e layout_height; wrap_content su layout_width e fill_parent su layout_height; fill_parent su

layout_width e wrap_content su layout_height; fill_parent su layout_width e su layout_height.

Altri attributi condivisi da tutti i componenti sono quelli definiti nella classe View, ereditati poi da tutti i widget e tutti i layout. Fra questi si segnalano:

minWidth e minHeight Permettono di specificare una dimensione minima per il componente. La misura deve essere espressa letteralmente, specificando l’unità di misura, oppure riferendo una dimensione definita sotto res/values. paddingLeft, paddingTop, paddingRight, paddingBottom e padding

Page 48: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 5

Widget e Layout (XML)

Pagina 9

Permettono di specificare il padding (margine interno) del componente. I primi quattro attributi permettono di distinguere la misura di padding assegnata in ogni direzione, mentre il quinto permette di assegnare a tutte e quattro le direzioni il medesimo padding con un’istruzione sola. I valori espressi devono essere dotati di unità di misura, o in alternativa fare riferimento ad una dimensione nota in res/values. visibility Accetta tre possibili valori: 0 (visibile), 1 (invisibile), 2 (scomparso). Nel primo caso, che è quello di default, il componente è visibile. Nel secondo caso il componente non è visibile, ma lo spazio spettante viene riservato e mantenuto vuoto. Nel terzo caso il componente è invisibile e nessuno spazio gli viene assegnato durante il disegno del layout, proprio come non fosse mai esistito. La visibilità di un componente può essere poi manipolata a runtime da codice, con il metodo setVisibility().

Esistono poi dei tag non comuni a tutti i widget, ma molto diffusi. Tra questi:

width e height Permettono di stabilire una dimensione precisa del componente, attraverso una misura scritta letteralmente o riferita da res/values. maxWidth e maxHeight Permettono di stabilire una dimensione massima del componente, attraverso una misura scritta letteralmente o riferita da res/values. gravity Stabilisce la gravità applicata al componente. I valori possibili sono: top, bottom, left, right, center_vertical, center_horizontal, center, fill_horizontal, fill_vertical, fill, clip_vertical e clip_horizontal. Più valori compatibili possono essere concatenati con un carattere di pipe. Ad esempio: gravity="top|center_horizontal".

I tanti componenti che derivano da TextView (tra cui i diversi tipi di bottoni) hanno a loro disposizione:

text Permette di impostare il testo visualizzato, attraverso un valore letterale o un riferimento a stringa memorizzata sotto res/values.

Una particolare estensione di TextView è EditText. Con EditText si realizza una casella di input, che permette all’utente di immettere del testo. In questo caso possono tornare utili i seguenti attributi:

hint Un suggerimento da visualizzare quando non c’è del testo visualizzato. password Un booleano (true o false) che indica se il campo contiene una password. In questo caso il testo viene camuffato in modo da non risultare leggibile ad un occhio indiscreto.

Page 49: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 5

Widget e Layout (XML)

Pagina 10

numeric Rende il campo di tipo numerico. Si deve specificare uno o più valori (separandoli con pipe) fra integer, signed e decimal. Il primo indica un valore intero, il secondo un valore numerico con segno, il terzo di un valore decimale. In questa maniera, in un campo EditText o derivato, è possibile controllare l’input dell’utente, in modo che il valore inserito sia sempre della natura che ci si aspetta. digits Da usare se il campo è numerico. Permette di specificare quali cifre è possibile utilizzare. Ad esempio il valore “123” farà sì che l’utente possa inserire nel campo sono le cifre “1”, “2” e “3”.

Consultando la documentazione ufficiale si può completare in autonomia la panoramica.

XML Se non siete pratici di XML e di linguaggi a marcatori correte il rischio di non condividere quanto ribadito in questa lezione, e di considerare più semplice il design delle interfacce utente a mezzo di un linguaggio di programmazione come Java. Se la pensate così è solo perché ancora non siete riusciti a cogliere l’essenza di XML e del suo utilizzo. Non dovete farvi spaventare: XML è nato per essere semplice, e dunque è anche facile. Ecco alcuni link per studiare cosa sia XML e come funzioni: http://it.wikipedia.org/wiki/XML http://xml.html.it/guide/leggi/58/guida-xml-di-base/ http://www.mrwebmaster.it/xml/guide/guida-xml_9/ http://www.risorse.net/xml/guida.asp

Namespace I namespace di XML, per semplificare, sono l’equivalente dei package in Java. Chi “inventa” un nuovo gruppo di tag, per regola, deve inserirli in un namespace, in modo che possano essere utilizzati senza andare in collisione con eventuali altri tag omonimi di un altro autore. I package di Java hanno la forma aaa.bbb.ccc, mentre i namespace di XML sono un URL del tipo http://aaa/bbb/ccc. Benché questo sia un indirizzo Web a tutti gli effetti, non è detto che debba per forza esserci un file da scaricare a quel percorso, anche se in genere l’autore ci mette la definizione dei tag e degli attributi previsti dal namespace. A ciascuno dei namespace importati in un tag XML è possibile associare uno shortname, cioè un nome breve, utile per richiamare tag ed attributi del namespace senza ambiguità. Per approfondire i namespace e per qualche esempio pratico: http://xml.html.it/articoli/leggi/1648/il-misterioso-mondo-dei-namespaces/

Page 50: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 5

Widget e Layout (XML)

Pagina 11

Attributi XML sulla documentazione Android eredita dalle piattaforme Java la maniera di organizzare la documentazione delle sue API. L’indirizzo http://developer.android.com/reference/ deve essere sempre tra i preferiti di ogni programmatore Android. Proprio come la documentazione javadoc di Java, la reference di Android riporta l’elenco dei package e delle classi disponibili nel sistema. Per ciascuna classe sono riportate proprietà e metodi. In più, per tutte le classi che corrispondono a widget e layout manager richiamabili anche da XML, la documentazione riporta inoltre anche gli attributi previsti. Si veda, ad esempio, android.widget.TextView: http://developer.android.com/reference/android/widget/TextView.html

Page 51: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

A cura di Carlo Pelliccia

Lezione 6

Gestione degli eventi dei widget Nelle due precedenti lezioni abbiamo conosciuto i principali widget di Android e le tecniche utili per richiamarli e disporli nel display dello smartphone. In questa lezione impareremo a raccogliere l’input dell’utente, e lo faremo intercettando gli eventi scatenati dai widget. I metodi di callback Tutti i widget di Android dispongono di una serie di metodi di callback, con nomi del tipo onTipoEvento(). Questi metodi vengono richiamati automaticamente ogni volta che il corrispondente evento è riscontrato sul widget. Pensiamo ad un esempio concreto. La classe android.widget.Button, che abbiamo conosciuto nelle lezioni precedenti, definisce uno speciale metodo chiamato onTouchEvent(). Questo metodo è eseguito automaticamente ogni volta che il bottone viene toccato dall’utente (in gergo, “tap”), attraverso il touch screen del suo dispositivo. Sono molti i metodi di questa categoria, ed ogni widget ha i propri. Ciascun metodo ha le sue regole e la sua firma: parametri e valore di ritorno, insomma, cambiano di caso in caso. La documentazione ufficiale delle API di Android, come sempre, presenta l’elenco esaustivo (in inglese) per ciascun widget. Lo sviluppatore interessato alla gestione di uno specifico evento deve individuare il metodo di suo interesse, quindi estendere il widget e ridefinire il metodo individuato. Proviamo a farlo proprio con il metodo onTouchEvent() di Button, rendendo il bottone reattivo al singolo tap, cioè al “clic da dito”: package mieapplicazioni.test; import android.content.Context; import android.view.MotionEvent; import android.widget.Button; import android.widget.Toast; public class MyButton extends Button { public MyButton(Context context) { super(context); } @Override public boolean onTouchEvent(MotionEvent event) { super.onTouchEvent(event); int action = event.getAction(); if (action == MotionEvent.ACTION_DOWN) { Toast toast = Toast.makeText( getContext(), "Bottone cliccato!", Toast.LENGTH_SHORT); toast.show(); return true; } return false; } }

Il metodo onTouchEvent() riceve in ingresso un argomento di tipo android.view.MotionEvent, che riporta tutte le informazioni relative all’evento di tocco riscontrato sul bottone. La versione superiore del metodo (cioè quella definita in Button) viene richiamata con la riga: super.onTouchEvent(event); Questo, nel caso specifico, è necessario affinché l’evento venga gestito graficamente: la definizione base del metodo contenuta in Button fa sì che il bottone, quando sottoposto a pressione, cambi il suo

Toast “Toast” è la parola usata nel gergo dei dispositivi mobili per identificare le finestrelle pop-up con un avviso rivolto all’utente. Si chiamano così perché nelle loro implementazioni più classiche spuntano fuori dalla parte bassa del display e somigliano ad un toast che salta fuori dal tostapane a cottura ultimata. In Android, come si evince dagli esempi mostrati nell’articolo, gli avvisi toast possono essere realizzati servendosi della classe android.widget.Toast. Ad ogni modo, ne parleremo nuovamente in seguito.

Page 52: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 6

Gestione degli eventi dei widget

Pagina 2

aspetto ed il suo colore, per mostrare reattività al tocco dell’utente (il bottone cambia da grigio ad arancione). Dopo aver eseguito la versione base del metodo, la nostra versione ridefinita controlla i dettagli dell’evento. Se si tratta del primo tocco riscontrato sul bottone (ACTION_DOWN), viene mostrato all’utente un messaggio di notifica (“Bottone cliccato!”). Altri filtri possono essere applicati per intercettare ulteriori varianti dell’evento di tocco, ad esempio ACTION_MOVE per un trascinamento e ACTION_UP per l’azione di rilascio del pulsante (dito sollevato dal display). Il metodo onTouchEvent(), infine, richiede la restituzione di un valore booleano, che serve per indicare se l’evento è stato effettivamente gestito oppure no. L’implementazione restituisce true nel caso di un evento ACTION_DOWN, effettivamente gestito attraverso il messaggio mostrato all’utente, mentre restituisce false in tutti gli altri casi. La classe MyButton può essere ora impiegata all’interno di un’attività, come abbiamo imparato a fare due lezioni fa: package mieapplicazioni.test; import android.app.Activity; import android.os.Bundle; import android.view.Gravity; import android.widget.LinearLayout; public class MyButtonDemoActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); MyButton button = new MyButton(this); button.setText("Toccami!"); LinearLayout layout = new LinearLayout(this); layout.setGravity(Gravity.CENTER); layout.addView(button); setContentView(layout); } }

Figura 1 - In questo esempio l’evento di primo tocco sul bottone viene gestito mostrando un avviso all’utente.

Event listener La ridefinizione dei metodi di callback è una tecnica che funziona, ma non è molto pratica: ogni volta che si usa un widget bisogna estenderlo e generare quindi un’altra classe, in modo da poter

Page 53: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 6

Gestione degli eventi dei widget

Pagina 3

ridefinire il metodo (o i metodi) di callback di proprio interesse. Questo, di per sé, è scomodo e prolisso. Quanti widget possono esserci in un’applicazione di media complessità? Fra bottoni, checkbox, etichette, campi di testo e via discorrendo, il numero è sicuramente elevato. Ecco allora che la velocità di sviluppo viene frenata dal dover definire e catalogare tante classi quanti sono i widget che si vogliono utilizzare. Un vero incubo! La ridefinizione dei metodi di callback, pertanto, è una pratica che serve solo in determinati casi, principalmente durante la creazione di componenti custom. Ad esempio è possibile estendere View per creare un componente personalizzato. È lecito, lo si può fare, e probabilmente prima o poi vi servirà di farlo. Per un intervento di questo genere, dunque, non c’è nulla di male nel ridefinire ed utilizzare i metodi di callback, anzi l’operazione sarebbe sicuramente necessaria. Per tutti gli altri usi quotidiani dei widget pronti all’uso, invece, Android mette a disposizione un meccanismo più semplice e sbrigativo, basato sull’utilizzo dei cosiddetti event listener. Tutti i widget mettono a disposizione una seconda serie di metodi, questa volta del tipo setOnTipoEventoListener(). Il widget Button, ad esempio, dispone del metodo setOnClickListener(). Attraverso i metodi di questa categoria è possibile registrare al widget degli event listener, cioè delle istanze di speciali classi, realizzate appositamente per ricevere notifica ogni volta che lo specifico evento accade. Per ciascun differente tipo di event listener esiste un’interfaccia apposita, che lo sviluppatore deve implementare per creare il suo gestore dell’evento. Ad esempio, l’interfaccia da implementare per gli eventi di clic è android.view.View.OnClickListener (interfaccia innestata nella classe View). Ciascuna interfaccia, ovviamente, richiede l’implementazione di uno o più metodi. Nel caso di OnClickListener, per proseguire con l’esempio, il metodo da ridefinire è: public void onClick(View v) { ... }

Proviamo a ripetere l’esempio del paragrafo precedente, questa volta utilizzando il principio degli event listener e, più nello specifico, l’interfaccia OnClickListener: package mieapplicazioni.test; import android.view.View; import android.view.View.OnClickListener; import android.widget.Toast; public class MyClickListener implements OnClickListener { @Override public void onClick(View view) { Toast toast = Toast.makeText( view.getContext(), "Bottone cliccato!", Toast.LENGTH_SHORT); toast.show(); } }

Questa volta, invece di estendere Button e realizzare così un componente custom, abbiamo semplicemente implementato un’interfaccia. Nel metodo onClick() abbiamo scritto il codice necessario per gestire l’evento di clic. Il parametro ricevuto dal metodo, nel caso specifico, rappresenta l’oggetto View o derivato sul quale l’evento è stato riscontrato. Affinché la classe MyClickListener venga utilizzata come gestore dell’evento di clic su uno specifico widget, è necessario registrarne un’istanza sul widget stesso, servendosi del metodo setOnClickListener() citato in precedenza. Lo si può fare quando si allestisce o si richiama il layout dalla schermata. Ecco una Activity equivalente a quella del paragrafo precedente, ma che a

Page 54: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 6

Gestione degli eventi dei widget

Pagina 4

differenza di quest’ultima utilizza un widget Button standard insieme con l’event listener di poco sopra: package mieapplicazioni.test; import android.app.Activity; import android.os.Bundle; import android.view.Gravity; import android.widget.Button; import android.widget.LinearLayout; public class OnClickListenerDemoActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Button button = new Button(this); button.setText("Toccami!"); button.setOnClickListener(new MyClickListener()); LinearLayout layout = new LinearLayout(this); layout.setGravity(Gravity.CENTER); layout.addView(button); setContentView(layout); } }

In questa maniera non è stato necessario creare un componente custom: è stato sufficiente registrare sul Button l’event listener realizzato pocanzi, con la riga: button.setOnClickListener(new MyClickListener()); La tattica degli event listener, inoltre, si sposa meglio con la possibilità messa in campo da Android di definire risorse e layout attraverso dei file XML. Il layout realizzato nell’attività mostrato poco sopra, ad esempio, potrebbe essere definito in un file XML indipendente. Chiamiamolo main.xml: <?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" android:gravity="center"> <Button android:id="@+id/bottone01" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Toccami!" /> </LinearLayout>

L’attività, di conseguenza, andrebbe riscritta alla seguente maniera: package mieapplicazioni.test; import android.app.Activity; import android.os.Bundle; import android.widget.Button; public class OnClickListenerDemoActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); Button button = (Button) findViewById(R.id.bottone01); button.setOnClickListener(new MyClickListener()); }

Page 55: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 6

Gestione degli eventi dei widget

Pagina 5

}

Dopo aver richiamato il layout definito nel file XML, non si deve far altro che recuperare il bottone al quale si vuole collegare l’evento e registrare su di esso il proprio listener personalizzato. Come scrivere meno codice Qualcuno potrebbe obiettare che, con gli event listener, è comunque necessario creare una classe distinta per ciascun gestore previsto, con il rischio di avere più codice dedicato alla cattura degli eventi che non alla loro gestione. Esistono diversi trucchi applicabili con gli event listener che aiutano ad evitare le situazioni di questo tipo. Ne esamineremo un paio. Per realizzare un event listener bisogna estendere un’interfaccia. Java non supporta l’ereditarietà multipla, e quindi una classe può avere una sola super-classe. Questo limite però non vale nel caso delle interfacce: una classe ne può implementare un numero qualsiasi. Ecco allora che, nel caso di GUI non troppo complesse, si può fare che la Activity che controlla lo schermo sia essa stessa event listener di uno o più eventi, per uno o più widget. Vediamo un esempio pratico che applica tale tecnica. Prendiamo in considerazione il seguente layout, come al solito da battezzare main.xml: <?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" android:gravity="center"> <Button android:id="@+id/bottone01" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Bottone 1" /> <Button android:id="@+id/bottone02" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Bottone 2" /> </LinearLayout>

L’esempio è lievemente più complesso del precedente: qui i bottoni sono diventati due.

Figura 2 - Questa volta bisogna gestire i clic su due bottoni differenti.

Page 56: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 6

Gestione degli eventi dei widget

Pagina 6

Realizziamo un’attività che carichi questo layout e gestisca gli eventi di clic su ambo i bottoni: package mieapplicazioni.test; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.Toast; public class TwoButtonsDemoActivity extends Activity implements OnClickListener { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); Button bottone01 = (Button) findViewById(R.id.bottone01); Button bottone02 = (Button) findViewById(R.id.bottone02); bottone01.setOnClickListener(this); bottone02.setOnClickListener(this); } @Override public void onClick(View v) { String messaggio; if (v.getId() == R.id.bottone01) { messaggio = "Hai cliccato il bottone 1!"; } else if (v.getId() == R.id.bottone02) { messaggio = "Hai cliccato il bottone 2!"; } else { messaggio = "widget non riconosciuto!"; } Toast toast = Toast.makeText(this, messaggio, Toast.LENGTH_SHORT); toast.show(); } }

In questo caso è la stessa attività ad implementare l’interfaccia OnClickListener, definendo di conseguenza il metodo onClick(). Così facendo, non è stato necessario creare (e manutenere) una classe apposita. Inoltre l’event listener realizzato è stato adoperato su due bottoni differenti (bottone01 e bottone02). È stato possibile farlo servendosi degli id assegnati ai due bottoni: all’interno del codice del solo metodo onClick() realizzato, si è andato a mostrare un messaggio differente a seconda della sorgente dell’evento. Un’altra tecnica che permette di risparmiare codice e lavoro consiste nell’adoperare le classi innestate anonime di Java. È possibile fare qualcosa di questo tipo: button.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { // gestione evento } }); Di fatto si crea e si registra allo stesso tempo il gestore dell’evento di clic. Ci pensa il compilatore a separare la classe anonima innestata su un file .class differente. Riscriviamo allora l’esempio precedente secondo quest’altra tecnica: package mieapplicazioni.test; import android.app.Activity; import android.os.Bundle;

Page 57: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 6

Gestione degli eventi dei widget

Pagina 7

import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.Toast; public class TestActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); Button bottone01 = (Button) findViewById(R.id.bottone01); Button bottone02 = (Button) findViewById(R.id.bottone02); bottone01.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { clickSuBottone01(); } }); bottone02.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { clickSuBottone02(); } }); } private void clickSuBottone01() { Toast toast = Toast.makeText(this, "Hai cliccato il bottone 1!", Toast.LENGTH_SHORT); toast.show(); } private void clickSuBottone02() { Toast toast = Toast.makeText(this, "Hai cliccato il bottone 2!", Toast.LENGTH_SHORT); toast.show(); } }

Così facendo non c’è bisogno di far implementare alcuna interfaccia all’attività. La tecnica consente di scrivere davvero poco codice per intercettare gli eventi, lasciando il programmatore libero di concentrarsi sulla logica della loro gestione. Il più delle volte, è proprio quest’ultima la tecnica da preferirsi. Panoramica sugli eventi Ora che abbiamo imparato ad utilizzare gli event listener, la prima cosa da fare è chiedersi: quanti e quali sono gli eventi che è possibile gestire? La risposta non è semplice: ogni widget ha i suoi eventi e, di conseguenza, i suoi event listener. Anche in questo caso, quindi, la documentazione è una risorsa fondamentale, che bisogna assolutamente imparare a leggere. Soprattutto nel caso dei widget più particolari, quindi, dovrete cavarvela da soli. Insieme, però, possiamo prendere in esame i tipi di eventi più universali e diffusi, riconosciuti e gestibili su qualsiasi widget. A definirli, tanto per cambiare, è la madre di tutti i widget: la classe android.view.View. Ecco una panoramica degli eventi più importanti:

Click. Lo abbiamo usato in tutti gli esempi precedenti. Il metodo sul widget è setOnClickListener(), e l’interfaccia per il gestore da implementare è View.OnClickListener. Il metodo richiesto dall’interfaccia è onClick(View view), che in parametro riporta il widget che ha subito l’evento.

Page 58: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 6

Gestione degli eventi dei widget

Pagina 8

Click lungo. Un evento che accade quando si clicca su un widget e si mantiene la pressione per qualche istante. Il metodo per registrare l’event listener è setOnLongClickListener(), e l’interfaccia per il gestore è View.OnLongClickListener. Il metodo da implementare è onLongClick(View view). Il metodo, come nel caso precedente, riceve come parametro un riferimento al widget su cui si è prodotto l’evento. In più, il metodo deve restituire un booleano per segnalare se l’evento è stato completamente gestito (true) oppure no (false). Tocco. Un evento più generico dei due precedenti: serve per rilevare un tocco qualsiasi su un componente. Il metodo per registrare il listener sul widget è setOnTouchListener(), mentre l’interfaccia per implementarlo è View.OnTouchListener. L’interfaccia richiede il metodo onTouch(View view, MotionEvent event). Come nei casi precedenti, view è il widget che ha subito l’evento. Il parametro di tipo MotionEvent riporta invece i dettagli dell’evento (tipo di azione, coordinate, durata ecc.), come nel caso documentato nel primo esempio di oggi. Il metodo deve restituire un booleano per segnalare se l’evento è stato completamente gestito (true) oppure no (false). Digitazione. Un evento usato per segnalare la pressione o il rilascio di un tasto della tastiera hardware. Il metodo per registrare il listener sul widget è setOnKeyListener(), mentre l’interfaccia per implementarlo è View.OnKeyListener. L’interfaccia richiede il metodo onKey(View view, int keyCode, KeyEvent event). Come nei casi precedenti, view è il widget che ha subito l’evento. Il parametro keyCode riporta il codice associato al tasto premuto, mentre quello di tipo KeyEvent riporta ulteriori dettagli (tasto pigiato, tasto rilasciato, eventuali modificatori e così via). Il metodo deve restituire un booleano per segnalare se l’evento è stato completamente gestito (true) oppure no (false). Focus. Quando un widget riceve il focus, significa che è selezionato, e che tutti gli eventi di digitazione saranno lui rivolti. È possibile sapere quando un widget riceve o perde il focus. Il metodo per registrare il listener sul widget è setOnFocusChangeListener(), mentre l’interfaccia per implementarlo è View.OnFocusChangeListener. L’interfaccia richiede il metodo onFocusChange(View view, boolean hasFocus). Il parametro view è il widget che ha subito l’evento, mentre il booleano hasFocus indica se il componente ha ricevuto il focus (true) oppure se lo ha perso (false).

Siccome di esempi dedicati ai clic ne abbiamo già visti diversi, proviamo ora a gestire i due eventi di tocco e digitazione. Prepariamo il seguente layout, da chiamare come al solito main.xml: <?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" android:gravity="center"> <EditText android:id="@+id/eventArea" android:layout_width="wrap_content"

Modificatori Quando si riceve un evento di digitazione, attraverso l’istanza di KeyEvent, è possibile sapere se insieme al tasto principale che riguarda l’evento sono stati attivati anche uno o più tasti modificatori. I tasti modificatori, in Android, sono tre: ALT, SHIFT e SYM. KeyEvent permette di controllare lo stato dei tre modificatori attraverso i metodi booleani isAltPressed(), isShiftPressed() e isSymPressed().

Page 59: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 6

Gestione degli eventi dei widget

Pagina 9

android:layout_height="wrap_content" android:background="#990000" android:textColor="#FFFFFF" android:width="200px" android:height="200px" /> <TextView android:id="@+id/logArea" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Compi un evento sull'area rossa qui sopra" /> </LinearLayout>

Il layout dispone, al centro del display, due componenti. C’è un EditText con sfondo di colore rosso e dimensioni 200 x 200 pixel, che useremo per far compiere all’utente delle operazioni di digitazione e di tocco. C’è poi un TextView, che useremo invece per descrivere ogni evento riscontrato sul componente precedente, dimostrando così di averlo intercettato correttamente.

Figura 3 - L’applicazione intercetta gli eventi di digitazione e tocco riscontrati sull’area rossa, e poi li descrive all’utente che li

ha compiuti.

Andiamo ora a realizzare, mediante una Activity, quanto proposto: package mieapplicazioni.test; import android.app.Activity; import android.os.Bundle; import android.util.Log; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; import android.view.View.OnKeyListener; import android.view.View.OnTouchListener; import android.widget.TextView; public class TestActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); View eventArea = (View) findViewById(R.id.eventArea); // Tocco eventArea.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View view, MotionEvent event) { String action; switch (event.getAction()) {

Page 60: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 6

Gestione degli eventi dei widget

Pagina 10

case MotionEvent.ACTION_DOWN: action = "DOWN"; break; case MotionEvent.ACTION_MOVE: action = "MOVE"; break; case MotionEvent.ACTION_UP: action = "UP"; break; default: action = "OTHER"; break; } float x = event.getX(); float y = event.getY(); log("tocco: " + action + " su (" + x + "," + y + ")"); return true; } }); eventArea.setOnKeyListener(new OnKeyListener() { @Override public boolean onKey(View view, int keyCode, KeyEvent event) { char c = event.getDisplayLabel(); String action; switch (event.getAction()) { case KeyEvent.ACTION_DOWN: action = "DOWN"; break; case KeyEvent.ACTION_UP: action = "UP"; break; default: action = "OTHER"; break; } log("Tasto: codice " + keyCode + " (" + c + "), azione " + action); return true; } }); } private void log(String text) { // Logga Log.i("EVENT", text); // Mostra su schermo. TextView logArea = (TextView) findViewById(R.id.logArea); logArea.setText(text); } }

Sull’EditText vengono registrati due listener, uno per il tocco e l’altro per la digitazione. La tecnica usata per gli event listener è quella della classe anonima innestata. Gli eventi intercettati vengono esaminati (attraverso le istanze di MotionEvent e KeyEvent) e descritte, sia nei log dell’applicazione (che potrete controllare comodamente usando Eclipse) sia nella TextView che ne dà immediato riscontro all’utente.

Figura 4 - Nella scheda “LogCat” della prospettiva “DDMS” di Eclipse è possibile controllare i log emessi dall’applicazione,

che descrivono la sequenza degli eventi riscontrati.

Page 61: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

A cura di Carlo Pelliccia

Lezione 7

Menù I menù sono una parte importante di qualsiasi applicazione moderna. Da anni gli utenti sono abituati ad avere a che fare con il concetto di menù, al quale si rivolgono ogni volta che vogliono cercare i comandi o modificare le opzioni delle loro applicazioni. Ciò risulta vero tanto nell’ambito dei software desktop quanto in quello delle applicazioni mobili per cellulari e smartphone. Con questo articolo impareremo a conoscere i menù previsti da Android, ponendo particolare attenzione alle regole per la costruzione di interfacce grafiche semplici ed efficaci, con menù concisi e facili da raggiungere e navigare. I menù in Android In Android esistono tre differenti tipi di menù, che lo sviluppatore può collegare ad una Activity:

Options menu Sono i menù concepiti per raggruppare le opzioni ed i comandi di un’applicazione. Si dividono in due sotto-gruppi, icon menu ed expanded menu, descritti di seguito.

Icon menu Sono i menù con le opzioni principali di un’applicazione. Vengono visualizzati nella parte bassa dello schermo quando si schiaccia il tasto “menù” del dispositivo. Vengono chiamati icon menu perché gli elementi contenuti al loro interno, in genere, sono delle grosse icone che l’utente può selezionare con il polpastrello. Costituiscono il menù principale di ogni attività e dovrebbero contenere sempre e solo le opzioni principali. Questi menù sono di rapido accesso, ma soffrono per questo di alcune limitazioni: possono contenere al massimo sei elementi, e non è possibile inserire negli icon menu gli elementi avanzati come le caselle di spunta (checkbox) e i bottoni radio.

Figura 1 - L’icon menu utilizzato dal browser di Android. Al suo interno sono presenti i comandi più

fondamentali dell’applicazione. Il tasto “altro” permette poi l’accesso al menù secondario.

Expanded menu Quando il primo tipo di menù non è sufficiente per esporre tutti i comandi e tutte le opzioni di un’applicazione, le attività fanno ricorso agli expanded menu (letteralmente menù espansi). Quando ciò avviene il menù principale, come suo ultimo tasto, presenta il bottone “altro”. Attivandolo si accede ad una lista aperta a tutto schermo, che permette la consultazione delle altre opzioni di menù. Gli

Page 62: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 7

Menù

Pagina 2

expanded menu, quindi, possono essere considerati come dei menù secondari, all’interno dei quali inserire le opzioni di configurazione del software, oppure i comandi che vengono utilizzati meno frequentemente.

Figura 2 - L’expanded menu del browser di Android. Contiene i comandi secondari e l’accesso alle opzioni

di configurazione.

Context menu I menù contestuali sono quelli che appaiono quando si mantiene il tocco per qualche istante su un widget che ne è dotato. Ad esempio nel browser è possibile eseguire un tocco di questo tipo sopra ad un’immagine. Dopo qualche istante verrà aperto un menù contestuale con alcune opzioni relative alla pagina corrente e all’immagine selezionata, come ad esempio i comandi per salvarla in locale e condividerla con gli amici. Come nel caso precedente, questo genere di menù si presenta come una lista a tutto schermo, che può contenere numerose opzioni.

Figura 3 - Il context menu del browser di Android visualizzato quando si tiene il tocco per qualche secondo sopra ad

un’immagine contenuta in una pagina Web.

Submenu Le applicazioni che dispongono di molti comandi possono usufruire anche dei submenu. In pratica, in uno qualsiasi dei menù descritti in precedenza, è possibile inserire un elemento che, invece di compiere un’azione diretta, va ad aprire un sotto-menù, nel quale si possono

Page 63: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 7

Menù

Pagina 3

presentare ulteriori possibilità di scelta. A differenza dei sistemi desktop, dove i menù possono essere annidati a piacimento l’uno dentro l’altro, in Android non è possibile spingersi oltre il sotto-menù di primo livello. In parole più semplici: non è possibile avere il sotto-menù di un sotto-menù. Questa scelta è frutto della precisa volontà dei designer di Android di mantenere semplici le applicazioni. In un display di pochi centimetri, se i menù potessero avere profondità qualsiasi, l’utente avrebbe difficoltà ad orientarsi. La scelta degli ingegneri di Google, quindi, costringe gli sviluppatori a pensare le proprie applicazioni affinché qualsiasi loro funzionalità sia accessibile con al più tre tocchi.

Nei prossimi paragrafi impareremo a programmare tutti e tre i tipi di menù presentati.

Options menu Cominciamo dagli options menu che, come abbiamo detto, costituiscono il menù principale di qualsiasi applicazione. Il menù delle opzioni è un concetto strettamente legato a quello di singola attività. Ogni Activity, infatti, può avere un solo options menu. La classe Activity dispone di un metodo definito al seguente modo: public boolean onCreateOptionsMenu(Menu menu) Questo metodo, nel ciclo di vita dell’attività, viene richiamato automaticamente dal sistema la prima volta che l’utente preme il tasto “menù” del suo dispositivo. L’argomento passato, un oggetto di tipo android.view.Menu, costituisce l’options menu inizialmente vuoto. Ridefinendo il metodo è possibile intercettare queste chiamate e popolare così il menù fornito con le voci utili alla propria applicazione. Il metodo onCreateOptionsMenu(), al termine dei propri compiti, deve restituire un booleano: true per rendere attivo il menù realizzato, false per dichiarare che l’attività non dispone di un menù, e quindi alla pressione del tasto “menù” del dispositivo non si deve mostrare nulla. Programmando nel corpo del metodo, il proprio options menu può essere assemblato servendosi dei metodi messi a disposizione dagli oggetti di tipo android.view.Menu (per l’esattezza, Menu è un’interfaccia). Aggiungere un elemento al menù è molto semplice, basta servirsi dei metodi: public MenuItem add(CharSequence title) public MenuItem add(int titleRes) Ambo i metodi aggiungono un elemento al menù. Il titolo dell’elemento (cioè la scritta che sarà mostrata per qualificarlo) può essere espressa con una stringa, nel primo caso, o con il riferimento ad una risorsa di tipo stringa memorizzata su file XML esterno, nel secondo caso.

La regola dei tre click Una vecchia regola di usabilità dice che l’utente deve sempre poter trovare quel che cerca con al massimo tre click. Se non ci riesce, l’utente inizia a provare frustrazione. Questa regola è stata inizialmente concepita per il Web ma, di fatto, oggi può essere applicata anche alle applicazioni mobili e desktop dedicate all’utente finale e ad un pubblico quanto più ampio possibile. I menù di Android, è evidente, sono stati concepiti tenendo a mente la regola. Per approfondire: http://en.wikipedia.org/wiki/Three-click_rule

Page 64: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 7

Menù

Pagina 4

Un controllo più granulare sugli elementi inseriti è reso possibile da queste altre due varianti del metodo add(): public MenuItem add(int groupId, int itemId, int order, CharSequence title) public MenuItem add(int groupId, int itemId, int order, int titleRes) In questo caso, oltre al titolo, è possibile specificare altre tre proprietà di ciascun elemento del menù:

groupId Con questo valore è possibile assegnare l’elemento ad un gruppo. Si può specificare un qualsiasi valore maggiore di zero (ovviamente deve essere lo stesso per due o più elementi che si intende assegnare al medesimo gruppo), oppure si può usare lo zero o la costante Menu.NONE se non si vuole assegnare l’elemento ad un gruppo. I gruppi sono delle facilitazioni che possono essere utilizzate per compiere delle azioni cumulative su una serie di elementi (ad esempio attivare o disattivare insieme tutti gli elementi di un gruppo). Ne riparleremo più tardi.

itemId Con questo valore si assegna un identificativo all’elemento stesso. Questo identificativo torna utile in seguito, quando si vuole distinguere un elemento da un altro. Come nel caso precedente, bisogna usare un valore maggiore di zero affinché la caratteristica possa essere sfruttata. Se non si è interessati all’assegnare un identificativo all’elemento, è sufficiente usare il valore zero o la costante Menu.NONE.

order Se si vuole assegnare uno specifico ordine all’elemento, è possibile in questo caso specificarlo esplicitamente con un valore da uno in su. Usando lo zero o la costante Menu.NONE si lascia stabilire al sistema l’ordine dell’elemento nel menù di appartenenza.

Sperimentiamo subito quanto descritto finora. Realizziamo un’attività dimostrativa così formulata: package mieapplicazioni.test; import android.app.Activity; import android.view.Menu; public class MenuDemo01Activity extends Activity { @Override public boolean onCreateOptionsMenu(Menu menu) { menu.add(Menu.NONE, 1, 1, "Comando 1"); menu.add(Menu.NONE, 2, 2, "Comando 2"); menu.add(Menu.NONE, 3, 3, "Comando 3"); menu.add(Menu.NONE, 4, 4, "Comando 4"); menu.add(Menu.NONE, 5, 5, "Comando 5"); menu.add(Menu.NONE, 6, 6, "Comando 6"); return true; } }

Page 65: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 7

Menù

Pagina 5

Abbiamo popolato il menù con sei semplici comandi, etichettati rispettivamente “Comando 1”, “Comando 2”, “Comando 3” e così via. Installate l’applicazione su un dispositivo e provare il suo menù. Dovreste ottenere un risultato simile a quello in figura.

Figura 4 - Un semplice icon menu, con sei comandi testuali.

Come è possibile osservare, i sei elementi introdotti sono andati a costituire l’icon menu dell’attività. Provate ora la seguente variante: package mieapplicazioni.test; import android.app.Activity; import android.view.Menu; public class MenuDemo02Activity extends Activity { @Override public boolean onCreateOptionsMenu(Menu menu) { menu.add(Menu.NONE, 1, 1, "Comando 1"); menu.add(Menu.NONE, 2, 2, "Comando 2"); menu.add(Menu.NONE, 3, 3, "Comando 3"); menu.add(Menu.NONE, 4, 4, "Comando 4"); menu.add(Menu.NONE, 5, 5, "Comando 5"); menu.add(Menu.NONE, 6, 6, "Comando 6"); menu.add(Menu.NONE, 7, 7, "Comando 7"); menu.add(Menu.NONE, 8, 8, "Comando 8"); menu.add(Menu.NONE, 9, 9, "Comando 9"); return true; } }

In questo caso i comandi sono nove. Lanciate l’attività e verificate cosa accade.

Page 66: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 7

Menù

Pagina 6

Figura 5 - Sono stati aggiunti nove comandi al menù. I primi cinque sono entrati a far parte dell’icon menù, mentre i restanti

quattro sono stati posizionati nell’expanded menu dell’attività.

Mentre, nel caso precedente, tutti e sei i comandi previsti sono diventati parte dell’icon menu dell’attività, ora le cose sono andate diversamente. Come accennato in apertura, un icon menu può contenere al massimo sei elementi, come nel caso del primo esempio. Ora che gli elementi sono diventati nove, il sistema ha posizionato nell’icon menu solo i primi cinque del lotto. Il sesto spazio disponibile nell’icon menu è stato automaticamente occupato con un elemento di tipo “altro”, che lavora come pulsante d’accesso per l’expanded menu dell’attività. In quest’ultimo sono stati inseriti i restanti quattro comandi (dal sesto al nono) previsti dalla nostra programmazione. Se ne ricava che non c’è una vera e propria differenza tra l’icon menu e l’expanded menu, ed ecco perché i due tipi di menù sono stati presentati inizialmente come due aspetti dell’options menu. Se ne deduce inoltre una regola di usabilità da non dimenticare mai: quando si realizza un’applicazione, i suoi comandi più frequentemente utilizzati vanno aggiunti per primi o comunque con un ordine superiore, in modo che possano entrare a far parte dell’options menu e restare così ad un solo tocco di distanza dall’utente. Gli elementi che confluiscono nell’icon menu possono utilizzare delle icone al posto del testo. Usufruire di questa funzionalità è molto semplice. Per prima cosa dovete fare caso al fatto che i metodi add() di Menu restituiscono un oggetto di tipo android.view.MenuItem. Come è facile intuire, l’oggetto restituito rappresenta l’elemento appena introdotto nel menù. Questo genere di oggetti dispone di metodi che permettono il controllo dell’elemento. Fra questi segnaliamo i seguenti: public MenuItem setIcon(Drawable icon) public MenuItem setIcon(int iconRes) Ambo i metodi servono per aggiungere un’icona all’elemento. Si può usare un oggetto graphics.drawable.Drawable, caricato o realizzato in precedenza, oppure il riferimento ad un’immagine conservata nella directory res/drawable del progetto. Una volta impostata un’icona su un elemento, questa sarà mostrata soltanto se l’elemento è parte dell’icon menu. Ecco un esempio che dimostra la funzionalità: package mieapplicazioni.test; import android.app.Activity; import android.view.Menu; import android.view.MenuItem;

Page 67: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 7

Menù

Pagina 7

public class MenuDemo03Activity extends Activity { @Override public boolean onCreateOptionsMenu(Menu menu) { MenuItem comando1 = menu.add(Menu.NONE, 1, 1, "Comando 1"); comando1.setIcon(R.drawable.play); MenuItem comando2 = menu.add(Menu.NONE, 2, 2, "Comando 2"); comando2.setIcon(R.drawable.pause); MenuItem comando3 = menu.add(Menu.NONE, 3, 3, "Comando 3"); comando3.setIcon(R.drawable.stop); return true; } }

Affinché l’esempio funzioni correttamente, è necessario disporre delle immagini play, pause e stop nella directory res/drawable del progetto. Una volta fatta funzionare l’attività, il suo icon menu sarà tipo quello mostrato in figura.

Figura 6 - Le icone degli elementi vengono mostrate nell’icon menu.

Scorciatoie da tastiera Per velocizzare la selezione di una voce di menù, è possibile associargli una scorciatoia da tastiera. Affinché la scorciatoia possa essere realmente applicata, è necessario che il dispositivo in uso disponga di una tastiera numerica o alfanumerica hardware. A seconda del tipo di tastiera installata, è possibile stabilire una differente associazione. I metodi di MenuItem utili per far ciò sono: public MenuItem setAlphabeticShortcut(char alphaChar) public MenuItem setNumericShortcut(char numericChar) public MenuItem setShortcut(char numericChar, char alphaChar) I tre metodi permettono di impostare, rispettivamente, il carattere di scorciatoia alfanumerico, il carattere di scorciatoia numerico, o entrambi in un colpo solo.

Page 68: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 7

Menù

Pagina 8

Ora che abbiamo imparato a disporre gli elementi nell’options menu, impariamo anche come gestire gli eventi di attivazione di ciascuno degli elementi introdotti. Esistono un paio di maniere per intercettare gli eventi di tocco riscontrati sugli elementi di un options menu. La prima tecnica consiste nel ridefinire un metodo di Activity così definito: public boolean onOptionsItemSelected(MenuItem item) Questo metodo viene richiamato automaticamente ogni volta che uno degli elementi dell’options menu dell’attività viene selezionato dall’utente. Lo specifico elemento selezionato, naturalmente, è quello riportato in argomento, mentre il valore di ritorno serve per indicare al sistema se l’evento è stato gestito (true) oppure no (false). Ridefinendo il metodo ed applicando dei filtri sull’identificativo dell’elemento segnalato (è possibile recuperarlo con il metodo getId() di MenuItem) è possibile riconoscere e gestire la specifica azione eseguita dall’utente. Ecco un esempio che mostra un avviso differente in base alla voce di menù selezionata: package mieapplicazioni.test; import android.app.Activity; import android.view.Menu; import android.view.MenuItem; import android.widget.Toast; public class MenuDemo04Activity extends Activity { private static final int MENUITEM_COMANDO_1 = 1; private static final int MENUITEM_COMANDO_2 = 2; private static final int MENUITEM_COMANDO_3 = 3; @Override public boolean onCreateOptionsMenu(Menu menu) { menu.add(Menu.NONE, MENUITEM_COMANDO_1, 1, "Comando 1"); menu.add(Menu.NONE, MENUITEM_COMANDO_2, 2, "Comando 2"); menu.add(Menu.NONE, MENUITEM_COMANDO_3, 3, "Comando 3"); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { int id = item.getItemId(); switch (id) { case MENUITEM_COMANDO_1: Toast.makeText(this, "Comando 1", Toast.LENGTH_SHORT).show(); return true; case MENUITEM_COMANDO_2: Toast.makeText(this, "Comando 2", Toast.LENGTH_SHORT).show(); return true; case MENUITEM_COMANDO_3: Toast.makeText(this, "Comando 3", Toast.LENGTH_SHORT).show(); return true; } return false; } }

Questo stralcio di codice mette in atto una tecnica consigliata: memorizzare gli identificativi degli elementi del menù attraverso delle costanti. In questo modo gli ID risultano più leggibili e si è meno inclini a commettere errori di battitura o distrazione.

Page 69: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 7

Menù

Pagina 9

Una seconda tecnica per la gestione degli eventi consiste nell’utilizzare il seguente metodo di MenuItem: public MenuItem setOnMenuItemClickListener(

MenuItem.OnMenuItemClickListener menuItemClickListener) La tecnica richiama le logiche di gestione degli eventi che abbiamo conosciuto nella lezione precedente. L’interfaccia MenuItem.OnMenuItemClickListener richiede l’implementazione del metodo: public boolean onMenuItemClick(MenuItem item) Il metodo viene richiamato quando l’elemento è selezionato dall’utente. Ecco un esempio analogo al precedente, ma basato su questa seconda tecnica di gestione degli eventi: package mieapplicazioni.test; import android.app.Activity; import android.view.Menu; import android.view.MenuItem; import android.view.MenuItem.OnMenuItemClickListener; import android.widget.Toast; public class MenuDemo05Activity extends Activity { @Override public boolean onCreateOptionsMenu(Menu menu) { MenuItem comando1 = menu.add("Comando 1"); comando1.setOnMenuItemClickListener(new OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem item) { Toast.makeText(MenuDemo05Activity.this, "Comando 1", Toast.LENGTH_SHORT).show(); return true; } }); MenuItem comando2 = menu.add("Comando 2"); comando2.setOnMenuItemClickListener(new OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem item) { Toast.makeText(MenuDemo05Activity.this, "Comando 2", Toast.LENGTH_SHORT).show(); return true; } }); MenuItem comando3 = menu.add("Comando 3"); comando3.setOnMenuItemClickListener(new OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem item) { Toast.makeText(MenuDemo05Activity.this, "Comando 3", Toast.LENGTH_SHORT).show(); return true; } }); return true; } }

Page 70: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 7

Menù

Pagina 10

La classe Activity, infine, dispone di altri due metodi collegati alla gestione del suo options menu:

public boolean onPrepareOptionsMenu(Menu menu) Viene richiamato quando l’options menu sta per essere visualizzato. Infatti il metodo onCreateOptionsMenu(), usato in tutti gli esempi precedenti, viene richiamato solo la prima volta che il menù deve essere mostrato. Se si intende modificare il menù costruito in precedenza, si può ridefinire questo secondo metodo. Gli oggetti Menu, a tal proposito, dispongono di una serie di metodi che permettono di recuperare, modificare e rimuovere gli elementi introdotti al suo interno in precedenza. Anche se non è un caso molto comune, quindi, Android permette lo sviluppo di options menu dinamici, che cambiano in base allo stato dell’applicazione. public void onOptionsMenuClosed(Menu menu) Viene richiamato quando l’options menu, dopo essere stato visualizzato, viene chiuso. public void openOptionsMenu() Apre il menù automaticamente, senza che sia necessario premere il tasto “menù” del dispositivo.

Ridefinendo questi metodi potrete ottenere un controllo più di dettaglio sugli eventi collegati all’apertura e alla chiusura dell’options menu delle vostre attività. Context menu I menù contestuali permettono di associare particolari opzioni o comandi ai singoli widget di un’attività. La creazione e l’utilizzo dei context menu sono molto simili a quelli dell’options menu. La prima cosa che si deve fare, è dichiarare che uno o più widget dell’attività devono disporre di un menù contestuale. Lo si può fare con il metodo registerForContextMenu(View view) di Activity. Ad esempio, per dotare un bottone di un menù contestuale si deve fare così: Button button = new Button(this); // ... registerForContextMenu(button);

Se il widget è stato dichiarato in un XML di layout, lo si può caricare attraverso il suo identificativo: View widget = findViewById(R.id.mioWidget); registerForContextMenu(widget);

Fatto ciò, è possibile ridefinire uno o più metodi di Activity in modo da generare e gestire il menù contestuale. Questi metodi sono:

public void onCreateContextMenu( ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo)

Questo metodo viene richiamato ogni volta che il menù contestuale per il widget v deve essere mostrato. Il menù deve essere costruito aggiungendo elementi al parametro menu. Il terzo argomento, menuInfo, viene usato solo in alcuni casi, come ad esempio quando si ha a che fare con le liste.

Page 71: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 7

Menù

Pagina 11

public boolean onContextItemSelected(MenuItem item) Richiamato quando un elemento di un context menu viene selezionato. public void onContextMenuClosed(Menu menu) Richiamato quando il menù contestuale viene chiuso.

Gli oggetti android.view.ContextMenu dispongono di tutti i metodi già visti con gli oggetti Menu (ContextMenu, infatti, è un’interfaccia che estende Menu). Fate però attenzione al fatto che gli elementi dei menù contestuali non supportano né le icone né le scorciatoie da tastiera. In compenso gli oggetti ContextMenu si specializzano attraverso i seguenti metodi:

public ContextMenu setHeaderTitle(CharSequence title) public ContextMenu setHeaderTitle(int titleRes) Associa un titolo al menù contestuale, che sarà mostrato nell’intestazione del menù. public ContextMenu setHeaderIcon(Drawable icon) public ContextMenu setHeaderIcon(int iconRes) Associa un’icona al menù contestuale, che sarà mostrata nell’intestazione del menù. public ContextMenu setHeaderView(View view) Imposta l’oggetto view come intestazione del menù, sostituendo l’icona ed il titolo dei metodi precedenti. public void clearHeader() Ripulisce l’intestazione del menù.

Ecco un esempio di codice: package mieapplicazioni.test; import android.app.Activity; import android.os.Bundle; import android.view.ContextMenu; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ContextMenu.ContextMenuInfo; import android.widget.Button; import android.widget.LinearLayout; import android.widget.Toast; public class MenuDemo06Activity extends Activity { private static final int MENUITEM_COMANDO_1 = 1; private static final int MENUITEM_COMANDO_2 = 2; private static final int MENUITEM_COMANDO_3 = 3; private static final int MENUITEM_COMANDO_4 = 4; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Button button1 = new Button(this); button1.setId(1); button1.setText("Bottone 1"); Button button2 = new Button(this); button2.setId(2); button2.setText("Bottone 2"); LinearLayout layout = new LinearLayout(this); layout.addView(button1);

Page 72: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 7

Menù

Pagina 12

layout.addView(button2); setContentView(layout); registerForContextMenu(button1); registerForContextMenu(button2); } @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { if (v.getId() == 1) { menu.setHeaderTitle("Menù bottone 1"); menu.add(Menu.NONE, MENUITEM_COMANDO_1, 1, "Comando 1"); menu.add(Menu.NONE, MENUITEM_COMANDO_2, 2, "Comando 2"); } else if (v.getId() == 2) { menu.setHeaderTitle("Menù bottone 2"); menu.add(Menu.NONE, MENUITEM_COMANDO_3, 1, "Comando 3"); menu.add(Menu.NONE, MENUITEM_COMANDO_4, 2, "Comando 4"); } } @Override public boolean onContextItemSelected(MenuItem item) { int id = item.getItemId(); switch (id) { case MENUITEM_COMANDO_1: Toast.makeText(this, "Comando 1", Toast.LENGTH_SHORT).show(); return true; case MENUITEM_COMANDO_2: Toast.makeText(this, "Comando 2", Toast.LENGTH_SHORT).show(); return true; case MENUITEM_COMANDO_3: Toast.makeText(this, "Comando 3", Toast.LENGTH_SHORT).show(); return true; case MENUITEM_COMANDO_4: Toast.makeText(this, "Comando 4", Toast.LENGTH_SHORT).show(); return true; } return false; } @Override public void onContextMenuClosed(Menu menu) { Toast.makeText(this, "Menù chiuso!", Toast.LENGTH_SHORT).show(); } }

Si tratta di un’attività che dispone di due bottoni, tutti e due collegati ad un menù contestuale. L’esempio mostra come registrare i widget per il menù contestuale, come creare menù distinti per widget differenti e come gestire gli eventi dei menù contestuali.

Figura 7 - Esempio di menù contestuale collegato a dei bottoni.

Page 73: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 7

Menù

Pagina 13

A proposito di eventi: anche nel caso dei context menu è possibile sfruttare la tecnica di gestione alternativa basata sugli oggetti OnMenuItemClickListener. Submenu Aggiungere dei sotto-menù ad un options menu o ad un context menu è possibile grazie ai seguenti metodi, disponibili per tutti gli oggetti Menu e ContextMenu:

public SubMenu addSubMenu(CharSequence title) public SubMenu addSubMenu(int titleRes) public SubMenu addSubMenu(int groupId, int itemId, int order, CharSequence title) public SubMenu addSubMenu(int groupId, int itemId, int order, int titleRes)

Questi metodi assomigliano moltissimo ai metodi add() per l’aggiunta di un comune elemento, ed infatti funzionano alla stessa maniera. L’unica differenza è che l’elemento inserito, una volta selezionato, causerà l’apertura di un sotto-menù. Cosa sarà mostrato nel sotto-menù possiamo stabilirlo manipolando l’oggetto android.view.SubMenu restituito dai metodi addSubMenu(). Anche in questo caso avremo a disposizione tutti i metodi di Menu, ed avremo a disposizione anche dei metodi come quelli di ContextMenu per la definizione di un’intestazione del menù. In effetti i submenu assomigliano moltissimo ai context menu, ed infatti come quest’ultimi non supportano icone e scorciatoie sui loro elementi. Sperimentiamo il seguente esempio: package mieapplicazioni.test; import android.app.Activity; import android.view.Menu; import android.view.SubMenu; public class MenuDemo07Activity extends Activity { @Override public boolean onCreateOptionsMenu(Menu menu) { SubMenu subMenu1 = menu.addSubMenu("Submenu 1"); subMenu1.add("Comando 1"); subMenu1.add("Comando 2"); SubMenu subMenu2 = menu.addSubMenu("Submenu 2"); subMenu2.add("Comando 3"); subMenu2.add("Comando 4"); return true; } }

Gruppi di elementi Gli elementi di un menù, come si è visto inizialmente, possono essere riuniti in dei gruppi. Due o più elementi sono nello stesso gruppo se, quando li si è aggiunti, si è usato lo stesso valore groupId. Ad esempio: menu.add(1, MENUITEM_COMANDO_1, 1, "Comando 1"); menu.add(1, MENUITEM_COMANDO_2, 1, "Comando 2"); menu.add(Menu.NONE, MENUITEM_COMANDO_3, 3, "Comando 3");

Page 74: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 7

Menù

Pagina 14

In questo caso “Comando 1” e “Comando 2” appartengono entrambi al gruppo con groupId 1, mentre “Comando 3” non fa parte di alcun gruppo (il suo groupId è Menu.NONE). I gruppi di elementi permettono di velocizzare alcune operazioni. Ad esempio se si intende abilitare o disabilitare tutti gli elementi di un gruppo, è sufficiente richiamare il metodo di Menu così definito: public void setGroupEnabled(int groupId, boolean enabled) Ad esempio: menu.setGroupEnabled(1, true);

Alla stessa maniera, gli elementi di un gruppo possono essere resi visibili o invisibili chiamando il metodo: public void setGroupVisible(int groupId, boolean visible) Funzionalità più particolare è riservata invece al metodo: public void setGroupCheckable(int group, boolean checkable, boolean exclusive) Questo metodo rende checkable tutti gli elementi di un gruppo. Quando un elemento è checkable, si comporta come un interruttore che può essere acceso (selezionato) o spento (non selezionato). Se il parametro exclusive è false, gli elementi si comporteranno come delle checkbox, cioè sarà possibile selezionarne anche due o più contemporaneamente. Se exclusive è true, invece, gli elementi del gruppo si comporteranno come dei bottoni radio. In pratica, solo un elemento alla volta potrà essere selezionato. Si può selezionare o deselezionare un elemento con il metodo di MenuItem così definito: public MenuItem setChecked(boolean checked) Se un elemento è selezionato oppure no, invece, lo si può sapere chiamando: public boolean isChecked() Ecco un esempio che dimostra questa funzionalità: package mieapplicazioni.test; import android.app.Activity; import android.view.Menu; import android.view.MenuItem; import android.view.SubMenu; public class MenuDemo08Activity extends Activity { @Override public boolean onCreateOptionsMenu(Menu menu) { SubMenu subMenu1 = menu.addSubMenu("Tipo checkbox"); subMenu1.add(1, Menu.NONE, Menu.NONE, "Opzione 1"); subMenu1.add(1, Menu.NONE, Menu.NONE, "Opzione 2"); subMenu1.add(1, Menu.NONE, Menu.NONE, "Opzione 3"); subMenu1.setGroupCheckable(1, true, false); SubMenu subMenu2 = menu.addSubMenu("Tipo radio"); subMenu2.add(2, Menu.NONE, Menu.NONE, "Opzione 1");

Page 75: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 7

Menù

Pagina 15

subMenu2.add(2, Menu.NONE, Menu.NONE, "Opzione 2"); subMenu2.add(2, Menu.NONE, Menu.NONE, "Opzione 3"); subMenu2.setGroupCheckable(2, true, true); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { if (item.isChecked()) { item.setChecked(false); } else { item.setChecked(true); } return true; } }

Figura 8 - Gruppi di elementi checkable. A sinistra un gruppo tipo checkbox, a destra uno di tipo radio.

Menù in salsa XML I menù, proprio come i layout, possono essere definiti via XML anziché via codice Java. Per farlo si usa la speciale cartella res/menu. I file XML al suo interno usano i tag <menu>, <group> e <item> per definire i menù in modo dichiarativo. Eclipse, attraverso il plug-in per lo sviluppo Android, dispone di un plug-in per l’editing visuale di questi file. Ecco un esempio: <?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:title="Sotto menù 1" android:id="@+id/item01"> <menu> <group android:id="@+id/group01" android:checkableBehavior="all"> <item android:id="@+id/item03" android:title="Opzione 1" /> <item android:id="@+id/item04" android:title="Opzione 2" /> <item android:id="@+id/item05" android:title="Opzione 2" /> </group> </menu> </item> <item android:title="Sotto menù 2" android:id="@+id/item01"> <menu> <group android:id="@+id/group01" android:checkableBehavior="single"> <item android:id="@+id/item06" android:title="Opzione 1" /> <item android:id="@+id/item07" android:title="Opzione 2" /> <item android:id="@+id/item08" android:title="Opzione 2" /> </group> </menu> </item> </menu>

Page 76: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 7

Menù

Pagina 16

Questo XML produce un menù tipo quello usato nell’esempio del paragrafo precedente. Si tratta di un menù principale con due comandi, ciascuno dei quali corrisponde ad un submenu. Il primo contiene tre item di tipo checkbox, mentre il secondo ospita tre opzioni radio. I menù XML possono essere caricati attraverso la classe android.view.MenuInflater ed il suo metodo inflate(int menuRes, Menu menu). Supponendo di aver salvato il menù XML sopra mostrato al percorso /res/menu/menu1.xml, la seguente attività è in grado di richiamarlo e mostrarlo come proprio options menu: package mieapplicazioni.test; import android.app.Activity; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; public class MenuDemo09Activity extends Activity { @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = new MenuInflater(this); inflater.inflate(R.menu.menu1, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { if (item.isCheckable()) { item.setChecked(!item.isChecked()); } return true; } }

Abilitare e disabilitare gli elementi Gli elementi di un menù possono essere disabilitati e successivamente riabilitati servendosi del seguente metodo di MenuItem: public MenuItem setEnabled(boolean enabled) Quando un elemento è disabilitato, l’utente non può selezionarlo, come se non ci fosse.

Page 77: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

A cura di Carlo Pelliccia

Lezione 8

Notifiche e finestre di dialogo Andiamo ad aggiungere un ulteriore tassello nel quadro dell’interazione fra un’attività Android e l’utente che ne fa uso. Nelle lezioni precedenti abbiamo già appreso numerose tecniche per dialogare con chi utilizza l’applicazione: tra widget, eventi e menù siamo già in grado di costruire applicazioni interattive. Ci sono comunque altri due strumenti che non possono assolutamente mancare nel nostro armamentario: sono i cosiddetti toast e le finestre di dialogo. I primi servono per segnalare delle notifiche, mentre le seconde possono essere usate sia per emettere un output sia per ricevere un input. Toast Abbiamo già usato i toast in alcuni degli esempi delle lezioni precedenti. Andiamo ora ad ufficializzare la conoscenza di questo strumento. Un toast è un avviso mostrato per qualche istante in sovraimpressione sullo schermo. Si chiama toast perché, in molti sistemi mobili, la sua forma e la sua animazione di comparsa ricorda quella di una fetta di pancarrè che salta fuori dal tostapane.

Figura 1 - Ecco come appare un messaggio toast in Android.

Le notifiche toast vanno usate per brevi messaggi testuali. Insomma, informazioni del tipo “impostazioni salvate”, “operazione eseguita” e simili. I messaggi toast rimangono sullo schermo per qualche istante e poi il sistema li rimuove automaticamente: non c’è alcuna interazione con l’utente. La classe di riferimento per la gestione dei messaggi toast è android.widget.Toast. A disposizione ci sono i seguenti due metodi statici:

public static Toast makeText(Context context, CharSequence text, int duration) public static Toast makeText(Context context, int resId, int duration)

Entrambi i metodi costruiscono un messaggio toast testuale. I parametri da fornire sono, rispettivamente, il contesto applicativo (ad esempio l’attività stessa), il messaggio da mostrare (come stringa, nel primo caso, o come riferimento a risorsa esterna, nel secondo) e la durata del messaggio. Non è possibile specificare quanti secondi, esattamente, il messaggio dovrà restare visibile. Si può soltanto dire se il messaggio deve durare poco o tanto. Per farlo si deve usare come argomento duration una fra le due costanti Toast.LENGTH_SHORT (durata breve) o Toast.LENGTH_LONG (durata lunga). Ecco un esempio: Toast toast = Toast.makeText(this, "Questo è un toast", Toast.LENGTH_LONG);

Una volta creato, il toast può essere mostrato chiamandone il metodo show(): toast.show();

Altri metodi degli oggetti Toast permettono di impostare ulteriori proprietà dell’avviso. Si consideri ad esempio il metodo:

Page 78: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 8

Notifiche e finestre di dialogo

Pagina 2

public void setGravity(int gravity, int xOffset, int yOffset) Con questo metodo si può impostare in quale angolo dello schermo far apparire il messaggio, specificando anche il discostamento dai bordi laterali (xOffset) e da quelli verticali (yOffset). Ad esempio: toast.setGravity(Gravity.TOP | Gravity.LEFT, 10, 10);

Questo avviso sarà mostrato in alto a sinistra, scostato di 10 pixel dai bordi. Si possono anche creare dei messaggi toast che, invece di mostrare del semplice testo, facciano uso di immagini o di altri widget al loro interno. In tal caso, invece di passare per i metodi statici makeToast(), si usa il costruttore della classe, che vuole in argomento il contesto dell’applicazione: Toast toast = new Toast(this);

La durata, in questo caso, la si può stabilire con setDuration(): toast.setDuration(Toast.LENGTH_LONG);

Il contenuto del toast, adesso, può essere del testo, come nel caso precedente: toast.setText("messaggio di testo");

Esiste anche una seconda variante di setText() che permette l’utilizzo delle stringhe esterne: toast.setText(R.string.messaggio_esterno);

Per un toast graficamente più ricco, invece, si può usare il metodo setView(View view), che imposta il widget da visualizzare all’interno della notifica in sostituzione del testo. Ad esempio un’icona: ImageView image = new ImageView(this); image.setImageResource(R.drawable.mia_icona); Toast toast = new Toast(this); toast.setGravity(Gravity.CENTER, 0, 0); toast.setDuration(Toast.LENGTH_LONG); toast.setView(image); toast.show();

Un layout XML esterno può essere caricato, proprio sotto forma di oggetto View, passando attraverso un oggetto android.view.LayoutInflater. Ogni attività ne mette a disposizione un’istanza: LayoutInflater inflater = getLayoutInflater(); View view = inflater.inflate(R.layout.toast_xml, null); Toast toast = new Toast(this); toast.setView(view); toast.show();

Questo significa che toast di maggiore complessità possono essere creati con la più agevole sintassi di XML, per essere poi caricati dinamicamente quando occorre mostrarli.

Page 79: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 8

Notifiche e finestre di dialogo

Pagina 3

Figura 2 - Un toast di maggiore complessità, con icona e testo, costruito a partire da una definizione di layout esterna in

formato XML.

Finestre di dialogo Le finestre di dialogo sono dei riquadri che è possibile aprire sopra l’attività corrente. Quando una finestra di dialogo compare, l’attività da cui dipende viene bloccata, e l’utente deve necessariamente interagire con la finestra di dialogo per farvi ritorno. L’esempio tipico è la finestra di conferma, che domanda all’utente se vuole proseguire con una certa operazione. L’utente, quando tale finestra compare, non può far altro che scegliere tra l’opzione per proseguire e quella per annullare. Finché la scelta non viene espressa, l’attività sottostante rimane bloccata e non può essere ripresa.

Figura 3 - Una richiesta di conferma all’interno di una finestra di dialogo.

A differenza dei toast, quindi, le finestre di dialogo sono sia bloccanti sia interattive. Per questo motivo la loro gestione risulta lievemente più complessa. L’astrazione di base cui far riferimento è la classe android.app.Dialog, che definisce mediante i suoi metodi cosa una finestra di dialogo può fare e come può essere manipolata. Nei prossimi paragrafi la approfondiremo più nello specifico. Adesso, invece, concentriamoci sul ciclo di vita delle finestre di dialogo, e sulla maniera che dovremo adoperare per richiamarle e mostrarle. La classe Activity fornisce un metodo così definito:

public final void showDialog(int id) Possiamo richiamare questo metodo ogni volta che dobbiamo aprire e mostrare una finestra di dialogo. Il parametro id simboleggia quale specifica finestra di dialogo l’attività deve mostrare. Il valore è arbitrario, nel senso che è nostro compito creare le finestre di dialogo ed assegnargli degli identificativi. La prassi consiglia di usare delle costanti interne alla classe dell’attività.

Toast: non solo dalle attività I messaggi toast possono essere mostrati non soltanto dalle attività, ma anche da altri tipi di applicazioni Android, come i servizi.

Page 80: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 8

Notifiche e finestre di dialogo

Pagina 4

Facciamo il caso che la nostra attività faccia uso di due finestre di dialogo, una per emettere un avviso di errore ed una per richiedere una conferma. La miglior cosa da fare, in casi come questo, è aggiungere due costanti all’attività, con nomi e valori arbitrari ma univoci. Ad esempio: private static final int DIALOG_ERROR_ID = 1; private static final int DIALOG_CONFIRM_ID = 2;

Quando dovremo mostrare l’avviso di errore, dunque, chiameremo: showDialog(DIALOG_ERROR_ID);

Analogamente, per la richiesta di conferma, dovremo fare: showDialog(DIALOG_CONFIRM_ID);

Adesso viene da chiedersi: come fa Android a sapere quali finestre corrispondano effettivamente ai due interi indicati? Ed infatti, allo stato attuale delle cose, Android non lo sa: siamo noi a dover svolgere le associazioni. Per farlo dobbiamo ridefinire il metodo di Activity avente firma:

protected Dialog onCreateDialog(int id) Android richiama questo metodo ogni volta che deve creare una finestra di dialogo. La finestra che deve essere creata è identificata dal parametro id. Ridefinendo il metodo dobbiamo riconoscere l’identificativo fornito, costruire la finestra di dialogo corrispondente e restituirla sotto forma di oggetto android.app.Dialog. Lo schema consigliato è il seguente: @Override protected Dialog onCreateDialog(int id) { Dialog dialog; switch (id) { case DIALOG_ERROR_ID: dialog = createErrorDialog(); break; case DIALOG_CONFIRM_ID: dialog = createConfirmDialog(); break; default: dialog = null; break; } return dialog; }

In breve, si utilizza un costrutto switch per associare degli oggetti Dialog ai loro corrispettivi identificativi numerici. Nel codice di esempio si fa riferimento ai due metodi createErrorDialog() e createConfirmDialog(), che sono dei metodi custom che lo sviluppatore crea a proprio piacimento per generare le differenti finestre di dialogo di cui ha bisogno. Dopo che la specifica finestra di dialogo è stata creata, Android prima di mostrarla richiama il seguente metodo di Activity:

protected void onPrepareDialog(int id, Dialog dialog)

Page 81: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 8

Notifiche e finestre di dialogo

Pagina 5

I due parametri corrispondono, rispettivamente, all’identificativo della finestra e all’oggetto Dialog costruito nel passaggio precedente. Lo sviluppatore può opzionalmente ridefinire questo metodo per inizializzare la finestra di dialogo con dei comandi specifici. Il modello, questa volta, è il seguente: @Override protected void onPrepareDialog(int id, Dialog dialog) { switch (id) { case DIALOG_ERROR_ID: prepareErrorDialog(dialog); break; case DIALOG_CONFIRM_ID: prepareConfirmDialog(dialog); break; } }

È importate sapere che il metodo onCreateDialog() per uno specifico id viene richiamato solo la prima volta che la corrispondente finestra di dialogo deve essere mostrata. La seconda volta che la stessa finestra dovrà essere mostrata, il sistema farà riuso dell’istanza già esistente. Se bisogna inizializzare l’istanza in maniera differente dalla volta precedente, quindi, non resta che farlo ridefinendo onPrepareDialog() ed adottando lo schema proposto sopra. Una volta che una finestra di dialogo viene aperta, ci sono due maniere per chiuderla. In primo luogo la può chiudere l’utente servendosi del tasto “back” del suo dispositivo. Non sempre è possibile farlo, dipende dal tipo e dalle impostazioni della specifica finestra di dialogo. Quando è possibile farlo si dice che la finestra è cancelable (cioè cancellabile). Via codice, invece, è sempre possibile chiudere e terminare qualsiasi finestra di dialogo. Lo si può fare invocando il metodo di Activity così definito:

public final void dismissDialog(int id) Ad esempio: dismissDialog(DIALOG_ERROR_ID);

La finestra dismessa viene chiusa e nascosta. Come spiegato prima, però, un riferimento all’oggetto Dialog che la rappresenta viene conservato all’interno dell’attività, in modo che l’istanza possa essere riusata nel caso in cui la stessa finestra debba essere nuovamente mostrata. In realtà è possibile far dimenticare del tutto la finestra di dialogo, servendosi al posto di dismissDialog() del metodo:

public final void removeDialog(int id) In questo caso l’istanza della corrispondente finestra di dialogo viene rimossa ed eliminata. Se la finestra dovrà essere mostrata di nuovo, però, sarà necessario ricrearla passando nuovamente attraverso onCreateDialog(). La prassi, dunque, è invocare dismissDialog() per le finestre di dialogo usate frequentemente, mentre removeDialog() va invocato per chiudere quelle mostrate raramente. Ora che abbiamo le idee chiare sul ciclo di vita delle finestre di dialogo, prepariamoci ad andare più nello specifico, imparando come costruire ed amministrare una finestra di dialogo. Poche volte si usa direttamente la classe Dialog: nella stragrande maggioranza dei casi si fa prima ad usare una delle sue sottoclassi messe a disposizione dalla libreria di Android. Classi come

Page 82: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 8

Notifiche e finestre di dialogo

Pagina 6

android.app.AlertDialog e android.app.ProgressDialog, infatti, coprono il 99% delle esigenze. Andiamo a conoscerle. AlertDialog Il primo tipo di finestra di dialogo che studieremo è android.app.AlertDialog. Si tratta della finestra di dialogo utile per mostrare un avviso o per chiedere qualcosa all’utente, come una conferma o la selezione di un elemento da una lista. Cominciamo dal più semplice dei casi: vogliamo notificare un evento e vogliamo essere sicuri che l’utente ne prenda atto. Un messaggio toast, in questo caso, non andrebbe bene: potrebbe scomparire prima che l’utente lo noti. Useremo allora una finestra di dialogo in grado di bloccare l’applicazione fin quando l’utente non noterà ed accetterà il messaggio. Il codice per farlo è molto semplice: AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle("Avviso"); builder.setMessage("Attenzione! Questo è un avviso!"); builder.setCancelable(true); AlertDialog alert = builder.create();

Come è possibile osservare, la finestra di dialogo viene prodotta servendosi di uno speciale oggetto AlertDialog.Builder. A questo oggetto builder si deve dire quali sono le caratteristiche dell’AlertDialog desiderata, e per farlo sono a disposizione numerosi metodi. In questo caso abbiamo specificato il titolo, con setTitle(), ed il messaggio, con setMessage(). Con il comando setCancelable(true) abbiamo fatto in modo che l’avviso possa essere chiuso con il tasto “back” del telefono. Il metodo create(), a questo punto, è stato invocato per produrre la finestra di dialogo. Una finestra di dialogo così realizzata, adesso, potrebbe essere restituita dal metodo onCreateDialog(), producendo un risultato come quello in figura.

Figura 4 - Un avviso che può essere chiuso con il tasto “back” del telefono.

Facciamo ora il caso di voler produrre un avviso identico al precedente, ma che, invece di costringere l’utente ad usare il tasto “back” del dispositivo, metta a disposizione esso stesso un bottone “chiudi”. Ecco come fare: AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle("Avviso"); builder.setMessage("Attenzione! Questo è un avviso!"); builder.setCancelable(false); builder.setPositiveButton("Chiudi", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dismissDialog(ID_FINESTRA); } }); AlertDialog alert = builder.create();

Con setCancelable(false) abbiamo disabilitato il tasto fisico del dispositivo, mentre con setPositiveButton() abbiamo aggiunto il bottone “chiudi”. Al metodo abbiamo dovuto anche fornire

Page 83: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 8

Notifiche e finestre di dialogo

Pagina 7

un oggetto di tipo android.content.DialogInterface.OnClickListener. Si tratta, come è facile intuire, di un gestore di eventi richiamato alla pressione del tasto “chiudi”. Con una chiamata a dismissDialog() facciamo dunque in modo che la finestra di dialogo venga chiusa quando l’utente tocca il bottone.

Figura 5 - Un avviso che può essere chiuso attraverso il suo stesso bottone “chiudi”.

Altrettanto semplice, a questo punto, è creare una richiesta di conferma: AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle("Conferma"); builder.setMessage("Vuoi proseguire con l'operazione?"); builder.setCancelable(false); builder.setPositiveButton("Prosegui", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // Confermato! dismissDialog(ID_FINESTRA); } }); builder.setNegativeButton("Annulla", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // Annullato! dismissDialog(ID_FINESTRA); } }); AlertDialog alert = builder.create();

Alla risposta positiva programmata con setPositiveButton(), abbiamo aggiunto ora una risposta negativa, con setNegativeButton(). Il metodo è simile al precedente: anche in questo caso dobbiamo fornire un listener che intercetti la pressione sul bottone e gestisca di conseguenza l’evento.

Figura 6 - Una richiesta di conferma con due bottoni.

E se volessimo fornire la facoltà di scegliere fra più di due opzioni? Anche questo è possibile: final String[] options = { "Caffè", "Gelato", "Tè", "Birra", "Ragù" }; AlertDialog.Builder builder = new AlertDialog.Builder(this);

Page 84: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 8

Notifiche e finestre di dialogo

Pagina 8

builder.setTitle("Scelta multipla"); builder.setItems(options, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { String option = options[which]; // gestione dell’opzione option dismissDialog(ID_FINESTRA); } }); builder.setCancelable(false); AlertDialog alert = builder.create();

In questo caso non si sono usati né setMessage() né i metodi setPositiveButton() e setNegativeButton(). Si è invece fatto ricorso al metodo setItems(). Questo metodo vuole come argomento un array di stringhe. Ciascuna delle stringhe fornite sarà un’opzione di scelta. Ancora una volta, il secondo argomento da fornire è il gestore di eventi. Quando una delle opzioni sarà selezionata dall’utente, il metodo onClick() del gestore verrà automaticamente richiamato. L’argomento which, in questo caso, riporterà l’indice dell’opzione selezionata.

Figura 7 - Un AlertDialog con numerose opzioni possibili.

L’elenco di opzioni può essere anche popolato con dei bottoni radio o con delle caselle di tipo checkbox. Nel primo caso si deve utilizzare, al posto di setItems(), il metodo setSingleChoiceItems(). Questo metodo vuole tre parametri: l’array di stringhe con le opzioni, l’indice dell’opzione inizialmente selezionata e, infine, il solito gestore di eventi. Ad esempio: final String[] options = { "Caffè", "Gelato", "Tè", "Birra", "Ragù" }; AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle("Scelta multipla"); builder.setSingleChoiceItems(options, 2, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { String option = options[which]; // gestione della selezione } }); builder.setCancelable(false); builder.setPositiveButton("Ok", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // gestione conferma dismissDialog(ID_FINESTRA); } });

Page 85: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 8

Notifiche e finestre di dialogo

Pagina 9

builder.setNegativeButton("Annulla", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dismissDialog(ID_FINESTRA); } }); AlertDialog alert = builder.create();

In questo caso si è tornati ad aggiungere i tasti di conferma e di cancellazione. Ciò ha senso: l’utente seleziona nell’elenco l’opzione desiderata e quindi conferma la sua scelta con l’apposito bottone. Visto che i listener, in questo caso, crescono di numero, si deve fare attenzione a mettere in atto una corretta politica di gestione degli eventi.

Figura 8 - Un AlertDialog con le opzioni rese come bottoni radio.

La scelta multipla, infine, è possibile usando dei checkbox. Il metodo utile, in questo caso, è setMultiChoiceItems(). Il metodo chiede tre parametri. Il primo è la lista delle opzioni, così come la abbiamo già conosciuta nei due casi precedenti. Il secondo argomento richiesto è un array di booleani, i cui elementi corrispondono a ciascuna delle opzioni possibili, indicando se l’opzione corrispondente è inizialmente selezionata o meno. Il terzo argomento è il gestore degli eventi. Questa volta l’interfaccia da implementare è DialogInterface.OnMultiChoiceClickListener. Il metodo onClick() di questa interfaccia si differenzia da quello di OnClickListener perché prevede un terzo parametro. Si tratta di un booleano chiamato isChecked, che indica se l’opzione toccata dall’utente è stata selezionata o deselezionata. Ecco un esempio: final String[] options = { "Caffè", "Gelato", "Tè", "Birra", "Ragù" }; final boolean[] selections = { true, false, false, false, false }; AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle("Cosa vorresti?"); builder.setMultiChoiceItems(options, selections, new DialogInterface.OnMultiChoiceClickListener() { @Override public void onClick(DialogInterface dialog, int which, boolean isChecked) { String option = options[which]; if (isChecked) { // option selezionato! } else { // option deselezionato! } } });

Page 86: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 8

Notifiche e finestre di dialogo

Pagina 10

builder.setCancelable(false); builder.setPositiveButton("Ok", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // applicare le opzioni selezionate dismissDialog(ID_FINESTRA); } }); builder.setNegativeButton("Annulla", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dismissDialog(ID_FINESTRA); } }); AlertDialog alert = builder.create();

Figura 9 - Un AlertDialog con le opzioni rese come checkbox, consentendo la scelta multipla.

ProgressDialog Può capitare che sia necessario svolgere delle operazioni non istantanee, che possono cioè durare qualche secondo o anche di più. Quando avviene ciò, si deve far capire all’utente che c’è un’operazione in corso, e che bisogna attendere fiduciosi. Se non lo si fa, l’utente potrebbe pensare che l’applicazione non gli sta rispondendo perché è lenta o, peggio ancora, perché si è bloccata. Questo, naturalmente, è male. Per fortuna in casi come questo ci si può servire di una android.app.ProgressDialog. Si tratta di una finestra di dialogo concepita appositamente per mettere in attesa l’utente. Lo scopo è duplice: da una parte blocca l’attività, in modo che non si possa far altro che attendere, mentre allo stesso tempo comunica all’utente che l’applicazione sta lavorando alacremente e che tutto procede come previsto. Opzionalmente si può mostrare anche il progresso dell’operazione. Ad esempio durante un download è possibile far vedere la percentuale di completamento raggiunta. Quando la barra di avanzamento non viene mostrata si dice che la ProgressDialog è indeterminata. La maniera più facile per creare una ProgressDialog indeterminata è attraverso il suo metodo statico:

public static ProgressDialog show( Context context, CharSequence title, CharSequence message)

Page 87: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 8

Notifiche e finestre di dialogo

Pagina 11

Il metodo crea e restituisce una finestra di attesa indeterminata, quindi senza barra di progresso. Richiede come parametri il contesto dell’applicazione (tipicamente l’attività stessa che sta creando la finestra), il titolo da assegnare alla finestra ed il messaggio da mostrare al suo interno.

Figura 10 - Una ProgressDialog indeterminata (cioè senza barra di avanzamento).

Le ProgressDialog con barra di avanzamento sono leggermente più complesse. L’oggetto, in questo caso, va costruito manualmente richiamando il costruttore: ProgressDialog progress = new ProgressDialog(this);

Bisogna poi specificare che la barra che si sta creando non è indeterminata: progress.setIndeterminate(false);

Adesso si deve indicare di usare la barra orizzontale: progress.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);

L’avanzamento raggiunto deve essere espresso mediante un valore che va da 0 a 100. Se, per qualche motivo, questo range non fosse adatto ad una vostra specifica esigenza, potete cambiarne il limite superiore, invocando il metodo setMax(). Ad esempio: progress.setMax(1000);

Così facendo, il valore di progresso raggiunto potrà essere espresso nel range che va da 0 a 1000. A questo punto, la finestra di dialogo può essere restituita e visualizzata. Su un thread parallelo bisogna intanto iniziare a svolgere l’operazione di cui l’utente attende la conclusione. Di tanto in tanto, mentre si svolge tale operazione, è necessario aggiornare la barra di avanzamento della ProgressDialog, per informare l’utente circa il punto raggiunto. Per farlo è disponibile il metodo setProgress(), che accetta come parametro un valore intero che rappresenta il livello di completamento raggiunto. Ad esempio: progress.setProgress(50);

Il valore espresso, naturalmente, deve essere compreso nel range di default (da 0 a 100) o in quello esplicitamente modificato in precedenza chiamando setMax(). Ecco un esempio di attività che, simulando un’operazione di una certa durata, mostra una barra di progresso fatta avanzare gradualmente attraverso un thread secondario:

Page 88: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 8

Notifiche e finestre di dialogo

Pagina 12

package mieapplicazioni.test; import android.app.Activity; import android.app.Dialog; import android.app.ProgressDialog; import android.os.Bundle; import android.view.Gravity; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.LinearLayout; public class ProgressDialogDemoActivity extends Activity { private static final int DIALOG_PROGRESS_ID = 1; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Button button = new Button(this); button.setText("Fammi attendere!"); button.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { showDialog(DIALOG_PROGRESS_ID); } }); LinearLayout layout = new LinearLayout(this); layout.setGravity(Gravity.CENTER); layout.addView(button); setContentView(layout); } @Override protected Dialog onCreateDialog(int id) { Dialog dialog; switch (id) { case DIALOG_PROGRESS_ID: dialog = createProgressDialog(); break; default: dialog = null; break; } return dialog; } @Override protected void onPrepareDialog(int id, Dialog dialog) { switch (id) { case DIALOG_PROGRESS_ID: final ProgressDialog ref = (ProgressDialog) dialog; ref.setProgress(0); Thread thread = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i <= ref.getMax(); i++) { try { Thread.sleep(100); } catch (InterruptedException e) { } ref.setProgress(i); } dismissDialog(DIALOG_PROGRESS_ID); } }); thread.start(); break; } } private ProgressDialog createProgressDialog() { ProgressDialog progress = new ProgressDialog(this); progress.setTitle("Attendere");

Page 89: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 8

Notifiche e finestre di dialogo

Pagina 13

progress.setMessage("Operazione in corso..."); progress.setIndeterminate(false); progress.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); progress.setMax(100); progress.setCancelable(false); return progress; } }

Figura 11 - Una ProgressDialog con avanzamento da 0 a 100.

Per le operazioni più lunghe e complesse si può addirittura usare una barra di progresso secondaria. Facciamo il caso di un’applicazione che scarica dei file da Internet. Ad un certo punto deve scaricare dieci file. In questo caso si può usare la barra di avanzamento principale per far vedere quanti file sono stati già scaricati, e la barra secondaria per mostrare il progresso raggiunto dal singolo file che di volta in volta viene scaricato. Il metodo utile è setSecondaryProgress(), che accetta un intero compreso tra 0 ed il valore massimo previsto. Ad esempio: progress.setSecondaryProgress(30);

Figura 12 - Una ProgressDialog con barra di avanzamento secondaria.

Finestre di dialogo custom Se AlertDialog e ProgressDialog non dovessero andare bene per una vostra specifica esigenza, potete sempre procedere alla costruzione e all’utilizzo di una finestra di dialog custom, cioè i cui contenuti sono stabiliti in tutto e per tutto da voi. Vediamo insieme come procedere. Ancora una volta, la miglior cosa da farsi è realizzare un layout XML. Proviamo con il seguente: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/layout_root" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"

Page 90: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 8

Notifiche e finestre di dialogo

Pagina 14

android:gravity="center" android:padding="10px"> <ImageView android:id="@+id/image" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="10px" /> <TextView android:id="@+id/text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="#ffffff" /> </LinearLayout> Chiamate il file custom_dialog.xml e disponetelo nella cartella res/layout del vostro progetto Android. Questo layout mette insieme un’immagine ed un testo. Né l’immagine né il testo, però, sono specificati a livello di XML: realizzeremo ora una classe estensione di Dialog che permetterà di impostare l’uno e l’altra. Chiamiamola CustomDialog: package mieapplicazioni.test; import android.app.Dialog; import android.content.Context; import android.os.Bundle; import android.widget.ImageView; import android.widget.TextView; public class CustomDialog extends Dialog { public CustomDialog(Context context) { super(context); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.custom_dialog); } public void setImage(int resId) { ImageView image = (ImageView) findViewById(R.id.image); image.setImageResource(resId); } public void setMessage(String message) { TextView text = (TextView) findViewById(R.id.text); text.setText(message); } }

Come potete vedere, estendere Dialog non è poi tanto diverso da estendere Activity. All’atto di creazione della finestra abbiamo provveduto affinché il layout XML realizzato in precedenza venga caricato e mostrato all’interno della finestra. Abbiamo poi predisposto i metodi setImage() e setMessage(), che impostano l’immagine ed il testo visualizzati nel layout. Ora possiamo utilizzare la classe CustomDialog in un’attività. Basterà fare qualcosa del tipo: package mieapplicazioni.test; import android.app.Activity; import android.app.Dialog; public class MyProjectActivity extends Activity { private static final int DIALOG_CUSTOM_ID = 1; @Override protected Dialog onCreateDialog(int id) { Dialog dialog;

Page 91: ...Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android A cura di Carlo Pelliccia Lezione 1 Primi passi con Android Meno …

Laboratorio di Ingegneria del Software A.A 2009/2010 Programmazione su Android

Lezione 8

Notifiche e finestre di dialogo

Pagina 15

switch (id) { case DIALOG_CUSTOM_ID: dialog = new CustomDialog(this); break; default: dialog = null; break; } return dialog; } @Override protected void onPrepareDialog(int id, Dialog dialog) { switch (id) { case DIALOG_CUSTOM_ID: CustomDialog ref = (CustomDialog) dialog; ref.setTitle("Titolo"); ref.setImage(R.drawable.mia_immagine); ref.setMessage("Testo desiderato"); break; } } }

Figura 13 - Una finestra di dialogo completamente custom.

DatePickerDialog e TimePickerDialog Altre due finestre di dialogo previste dalla libreria di Android sono DatePickerDialog e TimePickerDialog, entrambe nel pacchetto android.app. Come il loro nome lascia intuire, servono per far selezionare all’utente una data o un orario.

Notifiche nella status bar La status bar, o barra di stato che dir si voglia, è la barra posizionata nella parte alta dello schermo, quella che contiene l’orologio, per intenderci. La barra di stato di Android viene utilizzata, tra le altre cose, anche per trasmettere delle notifiche all’utente. Ad esempio quando arriva un SMS la barra si attiva e mostra un’icona che segnala l’evento all’utente. L’utente può poi “srotolare” la barra ed esaminare i dettagli della notifica ricevuta. Anche le applicazioni custom possono segnalare delle notifiche all’utente utilizzando questo sistema. Più che le attività, ad ogni modo, in genere sono i servizi a farlo. Per approfondire: http://developer.android.com/guide/topics/ui/notifiers/notifications.html