Tecnologie - Classe Quinta robertomana.it Java per Android Java per Android Rev Digitale 1.1 del 19/05/2017 Android SDK …………………………………………………………………………………..……… Android SDK Manager: Creazione di un device ……………..…………………………..………… Creazione di una applicazione tramite IntelliJ Idea …………………………………..……………. Activity ………………………………………………………..……………………….……………… Layout ………………………………………………………………………………….………………. Widget e View – Gestione dell ID …..……………………………………………………….……… La gestione degli eventi …..………………………………………………………………………… Associazione di uno stesso evento a più controlli ..…………………………………….………… Associazione di una procedura di evento tramite il designer ..…………………………………… L‟evento onTouch ..……………………………………….…………………………….....………… Generazione di Numeri casuali ……………………….………………………………....………… Il file strings.xml ..……………….....……………………………………………………...………… Il file AndroidManifest.xml .…….....……………………………………………...……...………… Concetto di Context ..………………………………………………………….….……...………… 2 5 6 7 8 9 10 11 11 11 12 12 12 13 Le finestre di popup : i Toast …………………………………………………….………………….. AlertDialog …………………………………………………………………….…………………….. InputDialog …………………………………………………………………….…………………….. Il widget EditText ..………………………………………………………………………....………… Il widget ImageView ..………………………………………………………..…….……...………… Rotazione del video …………………………………………………………….………………….. Creazione dinamica dei controlli ………………………………………………………………….. Approfondimenti sulla Gestione dell’ID ……………………………………….…………….. Principali Proprietà comuni ai vari widget ……………………………………….……………….. Check Box e Radio Button ………………………………………………………….……………….. 14 15 16 16 17 17 18 18 19 20 I tipi di Layout …………………………………………………………………….………………….. Gestione delle Activity tramite gli Intent …………………………………………………………….. Passaggio di parametri ad una Activity …………………………………………..……………….. Ciclo di vita di una Activity …………………………………………………………….……………… La gestione dei thread : Il timer …………………………………………………..………………. Android Device Monitor …………………………………………………………………………….. Gestione dei File di Testo …………………………………………………………………………….. Shared Preferences ……………………….…………………………..………………….………….. 21 23 26 28 29 30 31 32 ListView e Adapter ………………………………..…………………………………………………. Spinner ……………………………………………..…………………………………………………. Creazione di un Adapter personalizzato ……………………..……………………………………. OptionsMenù ………………………………………………………………………..………………. ContextMenù …………………………………………………………………………….……………. 33 35 36 38 40 Utilizzo di XML in Java ……………………………….…………………………………...………….. 41 Accesso a SQLite ………………………………………….……………………………...………….. 45 Accesso ad un web service HTTP …………………………..…….……………………………….. 48 Parsing di una stringa Json ……………………..…….……………………………………..…….. 53 Services And Notification ………………………..…….……………………………………..……….. 55 Utilizzo del GPS e Accesso alle Google Maps …………..…….………………………………….. 56 pag 1 Tecnologie - Classe Quinta robertomana.it Java per Android Android SDK Il SO Android usa un kernel Linux dalla versione 2.6 in avanti (dunque con supporto ai multithread). Sopra c‟è Android Run Time (ART) basato sulle Librerie Java Core che sono una versione ridotta delle librerie SE. Lo pseudo codice prodotto dai compilatori android viene tradotto a run time in un eseguibile vero e proprio mediante un compilatore Just In Time che legge lo pseudocodice e crea un eseguibile vero e proprio. Android è stato originariamente sviluppato da una piccola azienda, la Android Inc, ma nel 2005 Google ne acquistò i diritti e decise di rilasciare Android in modalità open source, il che significa che chiunque voglia utilizzare tale sistema può farlo semplicemente scaricando l‟intero codice sorgente. Tuttavia i produttori di hardware possono aggiungere le proprie estensioni al sistema e personalizzare Android per differenziare i propri prodotti dagli altri Le applicazioni per Android sono distribuite mediante files .APK (Android Package) che sono files compressi in un formato simile al JAR. I file .apk possono essere aperti e ispezionati utilizzando applicativi comuni come 7-Zip, Winzip Winrar o X-Plore (in smartphone Android o Symbian). La presentazione ufficiale della prima versione del "robottino verde" avvenne il 5 novembre 2007 da parte della neonata OHA (Open Handset Alliance), un consorzio di aziende del settore Hi Tech che include Google, produttori di smartphone come HTC e Samsung, operatori di telefonia mobile come Sprint Nextel e T-Mobile, e produttori di microprocessori come Qualcomm e Texas Instruments. Oggi copre il 78 % del mercato nella vendita di smartphone / tablet: Uno dei fattori principali che determinano il successo (o meno) di una piattaforma per dispositivi mobili è la varietà di applicazioni che essa mette a disposizione degli utenti. A partire dalla fine del 2008, Google ha messo a disposizione degli utenti l‟Android Market (oggi Google Play), un‟applicazione online attraverso la quale gli utenti possono installare applicazioni aggiuntive sui propri dispositivi. Su Google Play sono disponibili sia applicazioni gratuite che a pagamento. pag 2 Tecnologie - Classe Quinta robertomana.it Java per Android Ogni aggiornamento o release, similmente a quanto accade per molte versioni di Linux, segue un ordine alfabetico e una precisa convenzione per i nomi, che in questo caso sono quelli di dolci: versione 1.5 versione 1.6 versione 2.0 versione 2.2 versione 2.3 versione 3.0 versione 4.0 versione 4.1 versione 4.2 versione 4.3 versione 4.4 versione 5.0 versione 5.1 versione 6.0 Cupcake (30 aprile 2009 – API Level 3) Donut (30 aprile 2009 – API Level 4) Eclair (26 ottobre 2009 – API Level 5) Froyo (20 maggio 2010 – API Level 8) Gingerbread (6 dicembre 2010 – API Level 9) Honeycomb (22 febbraio 2011 – API Level 11) Ice Cream Sandwich (19 ottobre 2011 – API Level 14) Jelly Bean (9 luglio 2012 – API Level 16) Jelly Bean (13 novembre 2012 – API Level 17) Jelly Bean (24 luglio 2013 – API Level 18) Kit Kat n seguito ad un accordo con la Nestlé (31 ottobre 2013 – API Level 19) Lollipop lanciata il 3 novembre 2014 (API Level 21) Lollipop lanciata il 9 marzo 2015 (API Level 22) Marshmallow lanciata il 5 ottobre 2015 (API Level 23 Ambiente di sviluppo Il 12 novembre 2007 l'OHA ha rilasciato il software development kit SDK che include: gli strumenti di sviluppo, le librerie, un emulatore del dispositivo, la documentazione (in inglese), alcuni progetti di esempio, tutorial e altro. E‟ disponibile per: qualsiasi piattaforma Linux, qualsiasi piattaforma x86 compatibile che usi un sistema operativo Windows (da XP in avanti) Mac OS X, dalla versione 10.4.8, L'IDE ufficialmente supportato per lo sviluppo di applicazioni per Android è Eclipse, per il quale è fornito un plug-in ufficiale denominato ADT (Android Development Tools), che contiene tutte le librerie necessarie allo sviluppo. Versioni diverse dell‟SDK possono convivere sulla stessa macchina, ed anzi è necessario installarne più di una, in quanto i device più vecchi ovviamente supportano soltanto fino ad una certa versione di API e non oltre. Nelle ultime versioni sono disponibili API per Google Maps, Facebook, mixPanel (statistiche) Compilzione Java Source Compilatore javac Class files dx legge i class files e le risorse e genera i .dex files (Dalvik EXecutable, dove Dalvik era la vecchia Android Virtual Machine oggi sostituita da ART) jar (compattatore) legge i files dex e genera un file finale .apk che è il file tipico da installare su un sistema Android Application Framework Package Manager Window Manager consente di gestire più applicazioni. Activity Manager consente di gestire la visualizzazione delle applicazioni gestite a stack (quando si torna indietro vedo la finestra precedente) View Manager gestisce la Graphics User Interface Content Provider gestisce lo scambio i dati tra le applicazioni Notification Manager gestisce la barra di notifica in alto (es: è arrivata una nuova mail) pag 3 Tecnologie - Classe Quinta robertomana.it Java per Android L‟APK viene invito direttamente all‟emulatore o al dispositivo vero e proprio. Un file APK (Android Package) è un archivio che contiene le seguenti cartelle: RES (risorse: costanti, form, immagini) META-INF contenente i certificati (a chiave pubblica) che garantiscono riguardo alla bontà della app che si sta scaricando. ed i seguenti files: AndroidManifest.xml ( file xml che descrive l‟applicazione) class.dex che è lo pseudo eseguibile vero e proprio che verrà eseguito dall‟Android Run Time A run time viene generato un processo diverso per ogni applicazione da eseguire. Installazione dell’SDK Occorre innanzitutto installare la Java JDK versione 6 o superiori C:/programmi/java/jdk1.7.0 Occorre poi installare Android SDK scaricabile da : http://developer.android.com/sdk/index.html (86 MBytes) installing the sdk stand alone download the sdk now Al termine del download lanciare l‟installer. L‟installer propone come default il percorso C:/utente/appData/local/Android Viene poi aggiunto un link di lancio nella cartella C:\Users\myUser\AppData\Roaming\Microsoft\Windows\Start Menu\Programs In realtà non si tratta di una vera e propria installazione, ma di una semplice cartella che può essere trascinata ovunque sul disco. L‟installazione base inserisce soltanto alcune librerie base. Android SDK Manager permette però di gestire le varie versioni del SDK installate sul proprio computer. Dopo averlo avviato infatti esso mostra una lista di componenti indicando se ognuno di essi sia installato o meno. In qualsiasi momento è possibile selezionare altre versioni di API e procedere all‟installazione. Selezionare ad es tutte le voci di API 20 e poi clickare sul pulsante Install N packages. Esecuzione di Android SDK Nelle installazioni più vecchie per lanciare l‟SDK manager occorre andare nella cartella TOOLS ed eseguire android.bat Nelle installazioni più recenti nella cartella principale vengono posizionate le due applicazioni : Android SDK Manager Android AVD Manager pag 4 Tecnologie - Classe Quinta robertomana.it Java per Android Installazione scuola Android SDK è installato nella cartella: C:\Anadroid\sdk L‟installazione comprende soltanto la versione 4.3 (latest of Jelly Bean) API Level 18 Android SDK Manager – Creazione di un device Per prima cosa, occorre creare un emulatore di dispositivo, cioè un AVD (Android Virtual Device) Menù Tools / Menage AVDs / Create Come dispositivo di prova va benissimo Nexus S Come target viene proposto Android 4.3 (unico installato). Tutti i valori di default proposti sono OK. Il Nexus S ha una risoluzione di 480 x 800 pixel ARM è un tipo di CPU. Su Nexus 5 sono montate solo CPU ARM. Su altri device possono esistere versioni diverse on CPU diverse. Per provarlo selezionarlo e fare Start La finestra che viene proposta al momento del lancio fa si che l‟emulatore venga aperto all‟interno di una finestra di dimensioni ridotte più facilmente trascinabile e riposizionabile sul video. Il tempo di apertura è abbastanza lungo, per cui è consigliabile avviarlo subito e poi lasciarlo avviato. Dopo aver creato l‟emulatore, SDK Manager può anche essere chiuso. L‟emulatore simula perfettamente il comportamento di un dispositivo reale, quindi se per esempio clicchiamo sul pulsante Home in alto a destra vedremo la schermata iniziale del dispositivo riproduce tutte le caratteristiche hardware e software del dispositivo mobile, tranne la possibilità di effettuare chiamate telefoniche. In esso sono disponibili una grande varietà di pulsanti di navigazione e controllo, utilizzabili tramite il mouse, per generare gli eventi che le applicazioni devono gestire. Riferimento Ufficiale: http://developer.android.com/reference Il menù VIEW mostra la sintassi di tutti i controlli disponibili. Quando una classe è evidenziata in rosso significa che manca l’import corrispondente. Digitando ALT + INVIO intelliJ aggiunge automaticamente in alto l’import necessario. Digitando CTRL+ALT + SPAZIO su un metodo compare la sua firma Se una Activity implementa una certa interfaccia che richiede l‟implementazione di un certo metodo è sufficiente fare ALT + INVIO sul nome dell‟interfaccia e poi scegliere Implementa Metodi pag 5 Tecnologie - Classe Quinta robertomana.it Java per Android Creazione di applicazioni tramite IntelliJ Idea Una applicazione per Android è articolata nelle seguenti sottocartelle: src - sorgenti java res - risorse: layout sono le form, values sono stringhe e costanti, drawable sono le immagini animazioni palette dei colori gen – autogenerati (R.java) bin – compilati (APK e DEX) AndroidManifest è un file xml che descrive l‟applicazione (c‟è scritto ad esempio la versione di API in uso) Una applicazione può utilizzare quattro tipi di componenti : Activity - è il codice che gestisce un layout (form) Service - sono eseguiti in background e non hanno interfaccia grafica Broadcast Receiver Content Provider (fornitore di contenuti) I servizi non sono in grado di inviare nulla a monitor I servizi al massimo possono scrivere sul notification Manager Le activity NON possono accedere al notification Manager Una applicazione più avere più activity e più services Una app non può lanciare un‟altra app, ma può lanciare una activity di un‟altra app Sul nome del package (packege è sinonimo di namespace) ci deve essere almeno un puntino. IntelliJ Idea definisce automaticamente il package come package="com.example.appName" Il primo esercizio New project / Android Application Module Mediante il pulsante new definire il percorso relativo a: JDK Program files / JDK android SDK c: / Android / SDK Selezionare quindi la versione di android SDK da utilizzare nel progetto. Se c‟è una sola versione installata non si pone il problema. Il sistema si memorizza una entry contenente i due percorsi impostati. Ogni volta che si fa new viene creata una nuova entry. Invece di fare new si può scegliere fra una delle entry esistenti. Project Name : Viene proposto il nome della cartella, ma in realtà non c‟è relazione tra le 2 cose. pag 6 Tecnologie - Classe Quinta robertomana.it Java per Android Seconda finestra Propone come Application Name lo stesso nome impostato come Project Name Occorre quindi Impostare il check di creazione di una Activity Base di esempio, assegnandogli un nome (default MyActivity, con iniziale Maiuscola) scegliere l‟emulatore da utilizzare fra quelli precedentemente creati (oppure collegare il telefono e scegliere USB device) Clickando sui 3 puntini si apre Android SDK Manager che, consente, mediante il tasto NEW, di creare un nuovo device. Purtroppo, una volta creato il device, intelliJ non lo vede subito. Occorre chiudere e riaprire IntelliJ Le Activity Avendo impostato il check precedente, dentro la cartella src viene automaticamente creata una activity avente come nome di default MyActivity. Un'activity è costituita da : un file xml relativo alla definizione dell’interfaccia (Layout) una classe che serve a definire il comportamento dell’interfaccia. Facendo doppio click si apre una finestra contenente il codice relativo alla classe dell‟activity: public class MyActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); } MyActivity estende (cioè eredita da) Activity. Definita una classe come Activity, è obbligatorio ridefinire il metodo onCreate in quanto è il metodo che permette l'avvio vero e proprio dell'activity. All'interno di questo metodo occorre inserire tutto il codice riguardante la struttura della activity. savedInstanceState è lo stato dell‟activity salvato da qualche parte su disco. Quando si lancia una activity, per prima cosa andiamo a recuperare lo stato (che potrebbe anche essere vuoto). setContentView(R.layout.main) è un metodo di Activity che consente di associare all‟Activity il Layout contenuto nel file main.xml della cartella res/layout del progetto corrente. Ogni Activity di solito ha assegnato un Layout. pag 7 Tecnologie - Classe Quinta robertomana.it Java per Android La classe R è una classe base autogenerata dal frame work che serve a gestire tutte le risorse memorizzate dentro res (layout, etc). All‟interno della classe R il compilatore memorizza un puntatore esadecimale per ogni elemento contenuto all‟interno del Layout. Pur appartenendo alla stessa App, ogni Activity sostanzialmente vive di vita propria. Volendo è possibile terminare una Activity utilizzando il metodo finish(); Decisamente più complicato da una Activity andare a terminare un‟altra Activity Note Prima dell‟onCreate possono essere dichiarate delle Property, ma non è consentito eseguire delle istruzioni (come ad esempio findViewById() ) Per visualizzare il Project Explorer -> Menù View / Tool Windows / Project Il nome delle risorse (layout, immagini, etc.) deve essere scritto SOLO in minuscolo. Layout res / layout / main.xml main.xml sostanzialmente rappresenta la form principale del progetto. Con doppio click si accede al designer costituito da due finestre : Design contiene il layout grafico della form completabile tramite trascinamento Text contiene il codice xml relativo alla creazione dei controlli posizionati sulla form. Si può indifferentemente scrivere codice oppure trascinare gli oggetto sul designer. All‟interno della form occorre impostare un oggetto layout. I Layouts molto simili a quelli usati in Java all‟interno dei Panels. Per default viene impostato un Linear Layout che consente di posizionare i controlli soltanto uno per riga (uno sotto l‟altro). Il Linear Layout all‟inizio è orientato verticalmente ed ha altezza e larghezza tali da estendere il layout per tutta la grandezza dello schermo del dispositivo sia in orizzontale che in verticale. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World, MyActivity" /> </LinearLayout> All‟interno del contesto Layout si possono posizionare dei Widget che sono i controlli di interazione con l‟utente. Provare ad utilizzare i vari tipi di Label (Plain Text View, Large Text, etc.). Nelle prime due linee viene impostata la larghezza e l'altezza della Casella di Testo. Il valore wrap_content ha l'effetto di estendere l'elemento per una larghezza ed un'altezza tale da contenere la stringa che inseriremo. L‟ultima riga imposta il contenuto. pag 8 Tecnologie - Classe Quinta robertomana.it Java per Android Lancio dell’applicazione Pulsantino play in alto a destra Prima di lanciare l‟applicazione, occorre verificare che l‟emulatore sia avviato. Anche senza aprire SDK Manager, per avviare l‟emulatore è sufficiente utilizzare il comando Run / Edit Configuration (raggiungibile anche dal combo Box posizionato davanti al tasto Play) Nella parte inferiore della finestra è possibile selezionare quale emulatore utilizzare. Clickando sui puntini viene aperto (con notevole ritardo !) SDK Manager. Log Log.i(“title”, “message”); Log.d(“title”, “message”); // messaggio di informazione (solo in modalità BIN) // messaggio di debug (solo in modalità DEBUG) Widget e View Ogni elemento appartenente al package android.widget ha come superclasse la classe View che è un astrazione di ciò che vedrà l'utente sullo schermo. Per creare un componente personalizzato è sufficiente creare una classe che estenda la classe View o una sua sotto-classe. Accesso ai controlli : La proprietà numerica id Ogni widget deve avere come identificativo un nome che necessariamente deve iniziare con ilprefisso @+id/ Se lo si omette le associazioni non vengono eseguite correttamente. Questo prefisso viene impostato in automatico nel momento in cui si trascina un widget sul layout. Es: @+id/button1 Per accedere ai widget dall‟interno dell‟Activity si utilizza il metodo findViewById (View = Controllo) final Button btn =(Button) findViewById(R.id.button1); final TextView lbl = (TextView) findViewById(R.id.lblHello); R.id.button1 è in realtà una costante numerica di tipo int definita dal compilatore all‟interno del file R Due widget anche appartenenti a Layout differenti NON possono avere lo stesso nome. I nomi devono cioè essere univoci a livello dell‟intera applicazione. findViewById Questo metodo è un metodo disponibile all‟interno di : un oggetto di tipo xmlLayout (il cui puntatore può essere recuperato a partire dal nome statico (ID) del layout facedo uso di un oggetto denominato Inflater) una Activity che, mediante il metodo setContentView, ha associato un xmlLayout In entrambi i casi, pur essendo l‟ID dei controlli univoco a livello di applicazione, findViewById consente di acceddere soltanto ai controlli posizionati sul xmlLayout al quale si riferisce. final utilizzato davanti al nome di una variabile significa sostanzialmente che la variabile non può essere riassegnata (nel caso di un riferimento significa che il riferimento deve rimanere costante, cioè non può essere riassegnato). All‟interno del metodo onCreate devono essere dichiarate final tutte quelle variabili per le quali si intende gestire degli eventi o che sono utilizzate all‟interno degli eventi. L‟evento onCreate, oltre alle inizializzazioni, serve a definire delle procedura di evento che verranno eseguite in corrispondenza dell‟evento stesso. Quando la procedura termina le variabili locali vengono rimosse, per cui non sono più accessibili al momento dell‟evento. Dichiarandola final, la variabile viene memorizzata all‟interno di uno string pool che rimane allocato anche al termine della procedura. pag 9 Tecnologie - Classe Quinta robertomana.it Java per Android La Gestione degli eventi Gli eventi sono gestiti tramite oggetti (listener) e non tramite semplici procedure. Per associare un oggetto di evento (listener) ad un controllo occorre accedere al controlo tramite il solido findViewById e poi utilizar il metodo setOnEventNameListener (listenerObject); EventName è il tipo di evento che si intende gestire (es Click) listener deve essere l‟istanza di una opportuna clase di ascolto (es OnClickListener) il metodo .set è sempre seguito dal nome della classe di ascolto (es setOnClickListener() La classe listener di ascolto può esere istanziata: 1. in loco, cioè internamente al metodo set() stesso 2. esternamente, passando al metodo set() il nome della classe. Soluazione 1 btn.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { ((Button) v).setText(“Pressed”); // oppure btn.setText(“pressed”); } }); Soluazione 2 View.OnClickListener myListener = new View.OnClickListener() { @Override public void onClick(View v) { ((Button) v).setText(“Pressed”); // btn.setText(“pressed”); // in questo caso btn non è definito } }); La classe View.OnClickListener è una classe astratta e non può essere direttamente istanziata senza prima ridefinirne i metodi astratti. L'unico metodo dichiarato all‟interno della classe è il metodo onClick che occorre ridefine al momento dell'istanza del listener. Il metodo onClick riceve come unico parametro la View (il controllo) che ha generato l‟evento. Consente di definire il comportamento voluto quando il listener cattura l'evento a cui è associato. Dove posizionare il codice Il metodo set() deve necessariamente essere scritto all‟interno di un qualche metodo, tipicamente all‟interno del metodo di gestione dell‟evento onCreate( ). La classe di ascolto, se definita esternamente rispetto al metodo set(), può essere scritta o all‟interno del metodo onCreate (ma prima dell‟utilizzo del metodo set()), o all‟esterno del metodo onCreate (indifferentemente prima o dopo) pag 10 Tecnologie - Classe Quinta robertomana.it Java per Android Associazione di uno stesso evento a più controlli La soluzione2 precedente ha il vantaggio di poter associare lo stesso listener di ascolto a più controlli. All‟interno del listener si può utilizzare uno switch per individuare il controllo premuto: View.OnClickListener myListener public void onClick(View v) switch(v.getId()){ case R.id.btn1: txt.setText("E' break; case R.id.btn2: txt.setText("E' break; } } }; = new View.OnClickListener() { { stato premuto il pulsante 1"); stato premuto il pulsante 2"); btn1.setOnClickListener(myListener); btn2.setOnClickListener(myListener); Associazione di una procedura di evento tramite il designer Senza istanziare ogni volta il Listener, è sufficiente definire, fuori dal metodo onCreate() una generica procedura con firma adeguata e poi assegnarla all‟evento direttamente tramite Designer cercando nella finestra delle Properties l‟evento onClick ed assegnandogli tramite menù a tendina il metodo desiderato. public void on_click(View v) { } Attenzione che : Il metodo deve essere dichiarato public. Questa tecnica funziona esclusivamente per l‟evento onClick, per il quale è stata definita una property aggiuntiva che consente di memorizzare il puntatore alla classe di gestione dell‟evento L’evento onTouch Dal momento che i device non hanno il mouse, l‟evento click non è l‟evento migliore da utilizzare. Al posto dell‟evento onClick è possibile utilizzare l‟evento onTouch che presenta la seguente sintassi: public boolean onTouch(View v, MotionEvent event) { if( event.getAction() == MotionEvent.ACTION_DOWN) ((Button) v).setText(“Pressed”); else if( event.getAction() == MotionEvent.ACTION_UP) ((Button) v).setText(“Premi”); return true; }); Il primo parametro rappresenta il controllo che ha generato l‟evento Il secondo parametro rappresenta l‟azione specifica legata al Touch (ACTION_DOWN, ACTION_UP, ACTION_MOVE, etc.) Se il metodo onTouch restituisce true significa che lo stesso Listener dovrà occuparsi anche di gestire i successivi eventi ACTION_MOVE e ACTION_UP ammesso che esista un handler Se il metodo onTouch restituisce false significa che l‟evento viene consumato ed il Listener non sentirà più i successivi ACTION_MOVE e ACTION_UP pag 11 Tecnologie - Classe Quinta robertomana.it Java per Android Generazione di numeri casuali in Java Random rnd = new Random(); rnd.nextInt(B-A+1) + A; nextInt usato senza parametri genera unnumero compreso tra 0 e MAX_INT Il file strings.xml Il file strings.xml, posizionato nella cartella res/values, consente di definire delle stringhe costanti che consentono di parametrizzare l‟applicazione nel senso che l‟applicazione può usare queste stringhe in qualsiasi segmento di codice andandole a leggere all‟interno del file di risorsa. Esempio di file strings.xml <resources> <string name="app_name">app_02</string> <string name="titolo">Home Page</string> </resources> La stringa app_name è usata in AndroidManifest.xml e consente di cambiare rapidamente il nome visualizzato sulla barra superiore della app Utilizzo della stringa titolo all’interno del layout <TextView android:text = "@string/titolo" /> Utilizzo della stringa titolo all’interno del codice della Activity Per leggere da codice le stringhe memorizzate all‟interno del file strings.xml occorre utilizzare il metodo this.getString() txt.setText("Hello " + this.getString(R.string.titolo)); Il file AndroidManifest.xml Il file Manifest.xml è un file in cui devono essere registrati tutti i componenti appartenenti al progetto. E‟ l‟equivalente del file di Progetto di C#. Ogni activity deve essere registrata nel file Manifest. <manifest package="com.example.app_02" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="18"/> <application android:label="@string/app_name" android:icon="@drawable/ico"> <activity android:label="Pagina 1" android:name="MyActivity" > <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> </application> </manifest> il file manifest contiene informazioni dettagliate riguardo all‟applicazione. Definisce : il nome del package, il numero ed il nome della versione, espresso in formato x.x.x (es 1.0.1). Questo è ciò che consenti gli aggiornamenti automatici da parte di Google Play. la versione del SDK minima per l‟utilizzo dell‟applicazione (minSdkVersion) pag 12 Tecnologie - Classe Quinta robertomana.it Java per Android <application> icon = icona da utilizzare per rappresentare l‟applicazione sul dispositivo. Questa icona è utilizzata per rappresentare sul desktopo tutte le Activity che compongono la app. label = Label dell‟applicazione. Nome identificativo della app impostato in fase di creazione. Questo nome viene però utilizzato per identificare la app soltanto all‟interno del menù setting / app. Sul desktop ed anche nella barra del titolo della Activity viene utilizzata la Label della Activity. Quando si chiede a IntelliJ all‟avvio di creare una nuova Activity, lui imposta la stessa Label (nome app impostato in fase di creazione) sia per la App che per l‟Activity. <activity> Per ogni Activity sono memorizzati il name e la label, che rappresenta il nome visualizzato come intestazione dell‟Activity e che identifica l‟Activity sul Desktop. L‟icona è quella della app. All‟interno della definizione di ciascuna Activity c‟è un elemento denominato intent-filter che serve a definire le caratteristiche dell‟Activity : - I valori MAIN LAUNCHER indicano che si tratta di una Activity che può essere lanciata direttamente da SO. Tutte le Activity di questo tipo verranno visualizzate sul desktop del dispositivo. - Se invece l‟activity presenta il valore category = DEFAULT significa che si tratta di una Activity secondaria richiamabile soltanto da un‟altra Activity. In questo caso all‟interno di action è possibile specificare un nome che potrà essere utilzzato per avviare l‟activity. Nel caso di Activity di tipo DEFAULT la sezione <intent-filter> può anche essere omessa. Il concetto di Context La classe Context è la classe base da cui, mediante più passaggi, ereditano le classi Activity. Rappresenta una specie di interfaccia che consente all’Activity corrente di accedere a risorse e informazioni specifiche relative all’applicazione. Context context = getApplicationContext(); Mediante il context è possibile accedere ad un ampio insieme di caratteristiche e servizi a disposizione dell‟applicazione. Il Context corrente viene utilizzato per : accedere alle stringhe costanti memorizzate in strings.xml String s = context.getString(R.string.titolo); accedere alle eventuali variabili condivise dell‟applicazione:: SharedPreferences myPref = context.getSharedPreferences(name, mode); quando si creano nuovi oggetti, per fare in modo che il nuovo oggetto possa anche lui accedere a risorse e informazioni relative all‟applicazione, occorre passare all‟oggetto il context corrente: TextView lbl = new TextView(context); Visto che la classe Activity deriva dalla classe Context all‟interno del codice della classe Activity il this rappresenta il Context dell‟applicazione. Se però siamo all‟interno di un Listener di evento, this rappresenta il listener stesso. Per accedere al Context, a seconda dei casi, si può utilizzare una delle seguenti sintassi: this MyActivity.this; // istanza corrente // Va bene quando mi trovo all‟interno di un listener di evento. MyActivity. this è una proprietà che punta all‟istanza corrente v.getContext(); // v è il puntatore ricevuto come parametro dal gestore di evento getApplicationContext(); // disponibile in ogni situazione. Talvolta non funziona pag 13 Tecnologie - Classe Quinta robertomana.it Java per Android Le finestre di pop up : i Toast Le finestre di pop up sono le cosiddette shallow window, cioè le finestre che appaiono / scompaiono con una durata temporanea paragonabile ad una notifica. Scopo di questo widget è quello di notificare all'utente un messaggio del tipo "salvataggio effettuato" oppure "operazione eseguita" in seguito a qualche operazione compiuta dall'utente stesso. Rispetto alle Alert Dialog il Toast è molto più semplice, non ha pulsanti di interazione e, dopo un breve tempo, scompare da solo senza che l‟utente debba eseguire un click. Il costruttore della classe Toast si aspetta come unico parametro il Context di riferimento. Toast t=new Toast(MainActivity.this); t.makeText(getApplicationContext(), "Ok!!", t.show(); Toast.LENGTH_SHORT); Nelle versioni più recenti la precedente sintassi non è più supportata ed è stata sostituita dall‟utilizzo del metodo statico Toast.makeText che presenta 3 parametri che sono : 1. Il Context dell‟applicazione nel quale applicare il toast. Dato che il codice è inserito all'interno del listener non possiamo passare come contesto this in quanto passeremmo come argomento il listener stesso al quale non è applicabile un toast. Occorre pertanto utilizzare getApplicationContext()oppure MyActivity.this 2. Il messaggio da visualizzare 3. La durata della visualizzazione (LONG oppure SHORT) Toast t =Toast.makeText(getApplicationContext(),”ok”, Toast.LENGTH_SHORT) t.show(); E‟ anche possibile aggiungere il testo in un secondo momento, dopo aver istanziato l‟oggetto: Toast t =Toast.makeText(this, ””, Toast.LENGTH_SHORT) t.setText(“myMessage”); t.setGravity(Gravity.CENTER, 0, 0); t.show(); Il metodo setGravity() definisce la posizione dello schermo in cui visualizzare il pop up. Il valore 0,0 rappresenta l‟offset di visualizzazione rispetto al valore impostato da Gravity Utilizzo del toast per la visualizzazione di una immagine Occorre per prima cosa caricare l‟immagine da utilizzare. ImageView img = new ImageView(this); img.setImageResource(R.drawable.ic_launcher); Dopo di che al posto del metodo t.makeText() occorre utilizzare il metodo t.setView() Toast t = new Toast(getApplicationContext()); t.setView(img); t.show(); Un toast non può visualizzare contemporaneamente Testo e Immagine. O si visualizza il testo, oppure si visualizza l‟immagine. pag 14 Tecnologie - Classe Quinta robertomana.it Java per Android AlertDialog Widget utilizzato per mostrare un messaggio informativo all'utente, tipicamente un errore, un comportamento non desiderato oppure una richiesta di conferma da parte dell'utente per una qualche operazione (è dunque in grado di ricevere e gestire degli input da parte dell'utente). Per esempio un'applicazione che richiede una connessione ad Internet per compiere alcune operazioni, potrebbe mostrare un'AlertDialog all'utente nel caso in cui il dispositivo non si riesca a collegarsi ad Internet. AlertDialog.Builder dlg = new AlertDialog.Builder(this); dlg.setTitle("AlertDialog"); dlg.setMessage("E' stato premuto Pulsante 3"); dlg.setIcon(R.drawable. ic_launcher); dlg.show(); // oppure // AlertDialog myAlert = dlg.create(); // myAlert.show(); Il factory method create() istanzia un nuovo oggetto AlertDialog inizializzato con le caratteristiche impostate dal builder. Non essendoci pulsanti di dialogo, per chiudere la AlertDialog occorre clickare nell‟area dell‟Activity Aggiunta dei pulsanti di dialogo Aggiungiamo all‟applicazione un Text View in cui visualizzare il pulsante utilizzato dall‟utente per chiudere la AlertDialog. dlg.setCancelable(false); dlg.setPositiveButton("Si", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { lbl.setText("cliccato il tasto SI"); } }); dlg.setNegativeButton("No", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { lbl.setText("cliccato il tasto NO"); } }); setCancelable(false) ha l'effetto di disabilitare il tasto back del telefono e dunque l'utente dovrà necessariamente cliccare su uno dei due bottoni per chiudere l'AlertDialog. setPositiveButton ha come primo parametro il testo da visualizzare nel pulsante e come secondo parametro un oggetto Listener di tipo DialogInterface.OnClickListener e non di tipo View.OnClickListener. Anche nel caso del DialogInterface.OnClickListener occorre comunque ridefinire il metodo OnClick ed inserire il codice relativo al comportamento che vogliamo dare al bottone. Parametri del metodo onClick dell‟ DialogInterface.OnClickListener : 1. 2. un oggetto di tipo AlertDialog che indica quale dialog ha generato l'attivazione del listener un id che identifica il bottone premuto. Questi due parametri consentono di realizzare un unico Listener per tutte le AlertDialog dell‟applicazione con i relativi pulsanti. Ciò si realizza semplicemente eseguendo un primo switch sul parametro dialog (identificando quale AlertDialog ha attivato il listener) con all‟interno un altro switch per capire quale bottone, relativo alla AlertDialog presa in esame, è stato cliccato. pag 15 Tecnologie - Classe Quinta robertomana.it Java per Android Input Dialog Per trasformare una AlertDialog in InputDialog è sufficiente aggiungere una casella di input all‟interno della Dialog stessa. AlertDialog.Builder dlg = new AlertDialog.Builder(this); dlg.setTitle("InputDialog"); dlg.setMessage("Inserire il nome:"); dlg.setIcon(R.drawable. ic_launcher); final EditText input = new EditText(this); input.setInputType(InputType.TYPE_CLASS_TEXT); dlg.setView(input); dlg.setPositiveButton("OK", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { Toast.show(input.getText().toString()); } }); dlg.show() Attenzione che se il programma necessita di aprire più finestre di dialogo consecutive utilizzando un codice del tipo : apriFinestra3(); apriFinestra2(); apriFinestra1(); le tre finestre verranno aperte consecutivamente una sopra l‟altra, per cui occorrerà richiamarle in ordine inverso rispetto alla sequenza desiderata di apertura. L‟evento click di ogni pulante memorizzarà il valore inserito all‟interno di una aapposita variabile globale. Il click di finestra3 richiamrerà una funzione elabora() che andrà ad elaborare i tre valori inseriti. La casella di testo : il widget EditText La proprietà inputType consente di definire il comportamento della Casella di Testo : inputType inputType inputType inputType = = = = "none" "text" "number" "password" hint = "Inserisci il tuo testo" qualsiasi carattere solo caratteri testuali solo numeri. In corrispondenza del click si apre il tasterino numerico visualizza asterischi suggerimento iniziale Nelle versioni più recenti di IntelliJ sono stati aggiunti molti TextFields specifici con inputType già settato. Quando l'utente clicca su un oggetto di tipo EditText appare automaticamente una tastiera virtuale sullo schermo del dispositivo che permette l'immissione del testo. La chiusura della tastiera quando l'utente termina l'immissione del testo non è invece implementata direttamente nella tastiera, ma occorre aggiungere un bottone particolare che consenta di rimuovere la tastiera dallo schermo. Per arricchire la tastiera con il tasto done occorre inserire la seguente riga: txt.setImeOptions(EditorInfo.IME_ACTION_DONE); Il metodo setImeOptions ha l'effetto di settare un IME nella tastiera. pag 16 Tecnologie - Classe Quinta robertomana.it Java per Android Un IME (input method) implementa un particolare modello di interazione che l'utente può utilizzare. Il tipo di IME da aggiungere alla tastiera deve essere passato come argomento al metodo setImeOptions. I principali tipi IME (mutuamente esclusivi) sono: IME_ACTION_DONE IME_ACTION_GO IME_ACTION_NEXT IME_ACTION_SEND Nel caso di campi URL invia la richiesta HTTP al server Passa al campo successivo Invia il testo ad un servizio specifico La gestione delle immagini : il widget ImageView Consente di visualizzare un'immagine memorizzata all‟interno della cartella res la quale, oltre alla sottocartella Layout, contiene le seguenti cartelle drawable per la memorizzazione delle immagini: drawable-hdpi drawable-mdpi drawable-ldpi // high definition // medium definition // low definition Poiché Android può essere installato su dispositivi diversi aventi risoluzione diversa, per avere una ottimizzazione dell'immagine è opportuno creare versioni delle immagini a grandezze differenti (grandi, medie, piccole) ed inserirle nelle cartelle sopra indicate. A seconda della risoluzione del dispositivo viene automaticamente scelta la cartella appropriata. Se l‟immagine è caricata in una sola cartella, viene comunque caricata quella. Nel caso di immagini di dimensioni superiori alla risoluzione dello schermo, Android effettua un resize automatico in modo da renderla completamente visibile. Il valore layout_width/height="fill_parent" sul tag ImageView ingrandisce il controllo fino a quando l‟immagine non satura completamente una delle 2 dimensioni. Aggiungere una immagine nella cartella hd Così come per i Layout, anche per le res i nomi dei files devono essere scritto in minuscolo. Impostazione dell‟immagine sul Layout Grafico. Trascinare un oggetto ImageView : android:src="@drawable/medusa" // senza estensione !! Impostazione dell‟immagine da codice : final ImageView img =(ImageView) findViewById(R.id.img1); img.setImageResource(R.drawable.desert); // senza estensione !! la sintassi precedente però non consente concatenamenti, In alternative si può scrivere : int id = getResources().getIdentifier(“img”+i, "drawable", "package"); img.setImageResource(id); Rotazione del video if(this.getRequestedOrientation() == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); else this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); Attenzione che la rotazione del video provoca la terminazione dell‟Activity e successivo riavvio con rigenerazione dell‟evento onCreate. Questo di per sé non è un gravissimo problema e non inficia il buon funzionamento della rotazione, ma il riavvio dell‟applicazione può comunque creare fastidi. Ad esempio se il contenitore img del layout all‟avvio caricava staticamente una immagine, questa viene ricaricata ad ogni rotazione. Per evitare che in corrispondenza della rotazione dell‟immagine venga riavviata l‟applicazione, occorre aggiungere la seguente riga:all‟interno del Manifest: <activity android:name="MyActivity" android:label="@string/app_name" android:configChanges="orientation|keyboardHidden|screenSize"> pag 17 Tecnologie - Classe Quinta robertomana.it Java per Android Dall‟API Level 13 in avanti la rotazione del video provoca anche un effetto screenSize che pertanto deve essere inserito dentro configChanges. Nelle versioni precedenti deve essere omesso perché non riconosciuto. Questa riga fa sì che, in caso di rotazione, l‟applicazione non si riavvii ma che generi semplicemente l‟evento onConfigurationChanged che può essere gestito nel modo seguente: @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); Toast t = Toast.makeText(this, "changed", Toast.LENGTH_SHORT); t.show(); } Creazione Dinamica dei controlli Il layout grafico di una Activity può essere creato dinamicamente tramite codice anche senza l‟utilizzo di un file .xml. LinearLayout layout = new LinearLayout(this); layout.setOrientation(LinearLayout.HORIZONTAL); layout.setGravity(Gravity.BOTTOM); final Button btn1 = new Button (this); btn1.setText("Bottone 1"); btn1.setId(1); final Button btn2 = new Button (this); btn2.setText("Bottone 2"); btn2.setId(2); layout.addView(btn1); layout.addView(btn2); setContentView(layout); Essendo il codice scritto direttamente all‟interno del metodo onCreate( ) della classe Activity, in questo caso this rappresenta il Context corrente. Note sulla gestione dell’Id L‟Id è in realtà un numero intero utilizzato per identificare ciascun controllo (View). L‟Id può essere assegnato staticamente all‟interno del file XML oppure dinamicamente da codice. Assegnazione statica dell’ID all’interno del file XML Occorre definire per ogni controllo un attributo del tipo: android:id=”@+id/button1” Il valore dell‟attributo id deve essere univoco sull‟intera applicazione. Il compilatore trasforma questa stringa alfanumerica in un intero positivo e univoco. Riferimento al controllo Per accedere da codice al controllo occorre utilizzare la seguente sintassi: Button btn =(Button) findViewById(R.id.button1); dove R.id.button1 sostanzialmente è la costante numerica identificativa del controllo. pag 18 Tecnologie - Classe Quinta robertomana.it Java per Android Lettura dell’id del controllo Il metodo getId() restituisce la costante numerica R.id.button1 if( v.getId() == R.id.button1) Mentre findViewById restituisce un riferimento al controllo, v.getId() restituisce l‟ID numerico del controllo al quale viene applicato Assegnazione dinamica dell’ID tramite codice L‟assegnazione può essere eseguita semplicemente mediate il metodo .setId(int) dove il parametro deve essere un numero >= 0 non necessariamente univoco e che può anche essere in conflitto con un id statico. L‟univocità dell‟ID è importante soltanto dal punto di vista logico-funzionale. L‟accesso all‟ID di un controllo può essere eseguito al solito tramite v.getId() lbl.setText(Integer.toString(v.getId())); Dalla versione 14 di IntelliJIdea, non è più consentito assegnare direttamente un valore numerico al metodo setId() e nemmeno è consentito confrontare direttamente il valore restituito da v.getId() con un valore numerico, ma in entrambi i casi occorre passare attraverso una variabile: int id = 0; // id DEVE partire da 0 ! btn1.setId(id++); int id = v.getId(); if(id==1) ....... Riferiemnto al controllo creato dinamicamente L‟accesso al controllo sul layout può essere eseguito semplicemente facendo: Button btn =(Button) findViewById(id); Come è possible consentire id non univoci ? Il fatto che l‟ID non debba essere necessariamente essere univoco si spiega con il fatto che, quando si ricerca di un controllo appartenente al Layout tramite findViewById, esso esegue una semplice ricerca sequenziale restituendo il primo controllo avente l‟ID indicato. Poiché nella lista dei controlli quelli creati staticamente a Design Time si trovano sicuramente prima di quelli creati dinamicamente, findViewById, in caso di conflitto, restituirà sempre il controllo statico (che in certi casi potrebbe essere un problema). Se si usa la crazione dinamica è MEGLIO creare tutto dinamicamente ! L‟API 17 ha introdotto il metodo View.generateViewId() che consente di generare ID univoci. Principali Proprietà Comuni ai vari widget http://developer.android.com/reference/android/widget/package-summary.html Elenco principali Property text = “testo del controllo” textSize = "30dp" // dp = „density pixel‟ è sostanzialmente „quasi‟ sinonimo di pixel tag = Campo nascosto di tipo stringa molto comodo per salvare valori di appoggio (id record) textColor = "#ffff00" // colore del testo background = "#0000ff" // colore di sfondo background = "@android:color/holo_blue_dark" pag 19 Tecnologie - Classe Quinta robertomana.it Java per Android I valori fillparent e matchparent sono equivalenti. Il primo è deprecato a favore del secondo. layout_width="185dp” // larghezza del controllo layout_height="wrap_content" // altezza del controllo adattata al contenuto layout_margin=”10dp” // margini esterni. layout_gravity // ancoraggio dell‟oggetto rispetto al Layout. I principali valori di layout_gravity sono: TOP In cima al contenitore CENTER Al centro del contenitore, rispetto sia all‟asse X che all‟asse Y BOTTOM Al fondo del contenitore LEFT A sinistra, RIGHT A destra FILL Riempie l‟intera area del contenitore I precedentii valori di gravity possono essere combinati tra loro tramite una OR bit a bit. Es Gravity.LEFT | Gravity.BOTTOM gravity // ancoraggio del testo rispetto all‟oggetto Esempio: txtNome.setGravity(Gravity.CENTER_VERTICAL); enabled = true / false // Disabilita il controllo editable = true / false // Usato nei TextView visibility = View.VISIBLE / View.INVISIBLE padding = "10dp" // non va a modificare width e height. Da codice : .setPadding(10, 10, 10, 10); // i singoli valori possono anche essere negativi. Viene aggiunto come offset rispetto alla pos corrente ems=”10” // consente di impostare le dimensioni del controllo in sol colpo (width textSize etc) Accesso da codice Per l‟accesso da codice alle Property occorre utilizzare i metodi get e set che spesso utilizzano valori in formato differente rispetto alle Property precedenti. .getText(); restituisce il testo del widget. Spesso occorre fare .toString() .setText(“testo”); setta il testo del widget .bringToFront() porta il widget corrente in primo piano. .setBackgroundColor(Color.holo_blue_dark); // oppure .setBackgroundColor(Color.rgb(214,214,214); // oppure .setBackgroundColor(0xFF00FF00); // verde. Il primo FF è l‟opacity. FF=solido, 00=transparent setPadding(6,6,6,6); I valori di Padding e Margin partono da LEFT / TOP / RIGHT / BOTTOM layout.getChildAt(i); restituisce il puntatore all‟oggetto i-esimo figlio del layout indicato (base 0) Immagine di sfondo di un pulsante btn.setBackgroundResource(R.drawable.myimg); btn.setBackgroundDrawable(getResources().getDrawable(android.R.drawable.btn_ default)); // Depracato ed inoltre imposta il pulsante come dimensioni fisse. Meglio usare 2° img pag 20 Tecnologie - Classe Quinta robertomana.it Java per Android Check Box e Radio Button Non esiste il concetto di value, ma si utilizza sempre la Property Text che è quella visualizzata a fianco del Check Box / Radio Button. Esempi di utilizzo: if (myOpt.isChecked()) { myOpt.setText(“checked”); myOpt.setChecked(false); } int id_elemSelez = optGroup.getRadioButtonCheckedId(); RadioButton opt = (RadioButton) findViewById(id); RadioButton opt = (RadioButton) optGroup.getChildAt(2); // 3° rdb Sono mutuamente esclusivi soltanto i Radio Button inseriti all‟interno di un medesimo RadioGroup. Non sono mutuamente esclusivi i Radio Button inseriti direttamente all‟interno di un Layout o altro. Layout LinearLayout E‟ il layout di default. Dispone gli elementi uno di seguito all'altro. La proprietà orientation (vertical o horizontal=defaut) consente di impostare il layout: verticale - gli elementi vengono disposti uno sotto l'altro (uno per riga) orizzontale - gli elementi vengono disposti uno dopo l'altro da sinistra verso destra (tutti sulla stessa riga). In questo caso i vari elementi dovranno avere width="wrap_content" e NON width="fill_parent" altrimenti ne vedremo solo uno. In entrambi i casi gli elementi vengono aggiunti alla finestra finchè c'è spazio . Se si richiede l'inserimento di un ulteriore elemento, android cerca di ridimensionarlo (peraltro in modo molto poco leggibile) per inserirlo comunque all'interno dello schermo. Questo comportamento è limitato ad una soglia oltre la quale, semplicemente, gli elementi aggiuntivi non vengono più visualizzati. La proprietà layoutDirection consente visualizzare i controlli interni da sinistra verso destra (LAYOUT_DIRECTION_LTR) oppure da destra verso sinistra (LAYOUT_DIRECTION_RTL) La proprietà gravity del LinearLayout consente di ancorare i wdget interni nelle varie posizioni. TableLayout Permette di organizzare i contenuti come se si stesse lavorando con una tabella. L‟organizzazione avviene tramite l‟oggetto TableRow che identifica una riga della tabella All'interno di questo oggetto dovremo inserire tutti gli elementi che vogliamo mostrare in quella specifica riga. Es di matrice 4 x 4 TableLayout tableLayout = (TableLayout) findViewById(R.id.tableLayout); for (int i = 0; i < 4; i++) { TableRow tableRow = new TableRow(this); tableRow.setGravity(Gravity.CENTER); for (int j = 0; j < 4; j++) { Button btn = new Button(this); btn.setId(i * 4 + j); btn.setText(""); btn.setPadding(10, 10, 10, 10); btn.setOnClickListener(myListener); tableRow.addView(btn); } tableLayout.addView(tableRow); } pag 21 Tecnologie - Classe Quinta robertomana.it Java per Android FrameLayout La particolarità del Frame Layout è quella di consentire la sovrapposizione dei controlli, che vengono posizionati su piani crescenti in base alla posizione del controllo all‟interno del Layout. In pratica mentre il Linear Layout consente di disporre consecutivamente i controlli in verticale oppure in orizzontale, il Frame Layout consente di impilarli lungo l‟asse z. Alla base è come se ci fosse un unico controllo. Per ogni controllo si può specificare tramite layout_gravity il tipo di ancoraggio (con i soliti 9 valori: top, bottom, left, right, fill, top|left, top|right, bottom|left, bottom|right). Utilizzato spesso per realizzare le Transitions di una immagine sopra un‟altra immagine. RelativeLayout Consente di posizionare liberamente gli elementi indicando dimensione e posizione. Non è però una buona strategia perché vincola alla risoluzione del monitor android:layout_alignParentLeft="true" android:layout_alignParentTop="true" android:layout_marginLeft="39dp" android:layout_marginTop="94dp"/> Invece che su Top e Left, il controllo può essere allineato, ad esempio, rispetto a ParentRight e ParentBottom, specificando poi i valori di marginRight e marginBottom. Le proprietà di tipo layout .setWidth(50); setHeight(50); // Non sembrano riconosciuti .setGravity(Gravity.LEFT | Gravity.BOTTOM), oltre al valore di gravity, presenta due altri parametri che rappresentano offsetX e offetY rispetto alla posizione definita dal primo parametro. Tutte le proprietà dei singoli controlli che interagiscono con il layout (cioè width, height, margin, gravity del controllo rispetto al layout, etc.) e che, nel designer, iniziano con la parola layout, devono essere definite tramite un oggetto LayoutPrameters ed assegnate al controlo tramite il metodo .setLayoutParams(); L’oggetto LayoutPrameters deve essere del tipo specifico a seconda del layout a cui il controllo appartiene. I valori di width e height possono essere passati direttamente al costruttore. Per alcuni tipi di layout (es TableRow) è anche disponibile un costruttore privo i parametri. L‟oggetto layoutParams può essere istanziato ex-novo oppure può essere ricavato dal layout corrente tramite il metodo . .getLayoutParams(); Esempio : TableRow.LayoutParams params = new TableRow.LayoutParams(300, TableRow.LayoutParams.WRAP_CONTENT); params.setMargins(6,6,6,6); params.gravity= Gravity.CENTER_VERTICAL; params.gravity= Gravity.CENTER_HORIZONTAL; params.width(50); // sovrascrive il 300 passato al costruttore btn.setLayoutParams(params); // oppure, nel caso interessino solo width e height, ancora più semplicemnte si può fare: btn.setLayoutParams(new TableRow.LayoutParams (300, 400); pag 22 Tecnologie - Classe Quinta robertomana.it Java per Android Scroll di un Layout Per rendere un Linear Layout srollable è sufficiente racchiuderlo all‟interno di un oggetto ScrollView <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" > <LinearLayout android:orientation="vertical" </LinearLayout </ScrollView Rimozione dei controlli da un Layout layout.removeAllViews(); Gestione delle Activity tramite gli Intent Per creare una nuova Activity src / package / tasto destro / New Android Component / Activity Creando la nuova Activity in questo modo, questa viene automaticamente dichiarata nel Manifest senza però la sezione di <intent-filter>. Attenzione che la nuova Activity deve essere posizionata nella cartella interna e NON direttamente dentro src. Eventualmente spostarla con trascinamento. Per ciascuna Activity si può creare un apposito layout di riferimento. In realtà però una Activity1 potrebbe utilizzare due diversi Layout senza necessità di dover istanziare una seconda Activity Il fatto di creare due Activity separate, una per ogni layout, consente di suddividere il codice relativo a ciascun layout. Registrazione dell’Activity all’interno del File Manifest Ogni nuova Activity deve necessariamente essere registrata all‟interno del file Manifest. Per registrare l‟Activity all‟interno del file Manifest ci sono due possibilità : pag 23 Tecnologie - Classe Quinta robertomana.it Java per Android caso 1 Utilizzando i valori action=MAIN e category= LAUNCHER <activity android:name="Activity2" android:label="Pagina 2"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> In questo caso l‟Activity potrà essere eseguita direttamente dal sistema operativo e verrà visualizzata sul desktop dello smartphone. Nel caso di più Activity MAIN, l‟Activity da lanciare all‟avvio dovrà essere specificata all‟interno della finestra EDIT CONFIGURATION. caso 2 Utilizzando action= nome simbolico e category=DEFAULT <intent-filter> <action android:name="android.intent.action.secondaActivity"/> <category android:name="android.intent.category.DEFAULT"/> </intent-filter> In questo secondo caso l‟Activity sarà una activity secondariaa che potrà solo essere richiamata da un‟altra activity attraverso l‟utilizzo di un intent. Se si utilizzano gli intent espliciti, la sezione <intentfilter> può anche essere omessa. Il concetto di Intent Un Intent rappresenta l‟intento di eseguire una certa azione, o meglio una richiesta al Sistema Operativo di avviare una certa azione. E‟ una forma di messaggistica gestita dal sistema operativo con cui un Componente (Activity, Service, Broadcast Receiver o ContentProvider) può richiedere in modo asincrono l‟esecuzione di un‟azione da parte di un‟altro componente. Un Intent rappresenta un messaggio che una Activity può inviare al SO per: avviare un‟altra Activity - startActivity(intent) avviare un Service - startService(intent) inviare un broadcast - sendBroadcast(intent) L‟APP o il sistema operativo sono perennemente in ascolto per servire le richieste di tipo Intent. Per avviare l‟Activity esistono due sintassi leggermente diverse entrambe applicabili per avviare sia Activity LAUNCHER sia Activity DEFAULT. Intent Espliciti Un Intent si dice Esplicito quando passo esplicitamente al costruttore il nome del Componente da avviare. Per istanziare ed avviare un Intent esplicito si procede nel modo seguente: si passa al costruttore il nome del file contenente l‟Activity da avviare si esegue l‟Intent mediante il metodo appropriato. Se il Componente da avviare è una Activity occorre utilizzare il metodo startActivity che avvierà l‟Activity in un processo separato. public void onClick(View v) { Intent intent = new Intent(this, Activity2.class); this.startActivity(intent); // metodo del context pag 24 Tecnologie - Classe Quinta robertomana.it Java per Android Intent Impliciti In questo caso non si passa nulla al costruttore creando un Intent anonimo. Successivamente, mediante setAction(), si assegna il nome del Componente da avviare. Intent intent = new Intent(); intent.setAction("android.intent.action.secondaActivity"); startActivity(intent); Nel caso di Activity DEFAULT il nome da utilizzare è quello definito nella action all‟interno del Manifest Nel caso di Activity LAUNCHER, il nome da utilizzare è il name della Activity da avviare (sempre definito all‟interno del Manifest). Se nel manifest non è stata definita la sezione <intent-filter>, gli attributi ACTION e CATEGORY possono anche essere definiti via codice: Intent intent = new Intent(this, Activity2.class); intent.addCategory(Intent.CATEGORY_LAUNCHER); intent.setAction(Intent.ACTION_MAIN); startActivity(intent); Ulteriore sintassi per la definizione dell’intent: Intent intent = new Intent("android.intent.action.secondaActivity "); startActivity(intent); Chiusura di una Activity La stessa cosa la si potrebbe fare sul pulsante di Activity2 per lanciare Activity1. Così facendo però Activity1 viene reistanziate e una seconda istanza viene caricata in memoria in cima allo stack. Per far ricomparire la precedente istanza di Activity1 è sufficiente chiudere la finestra Activity2 e si ripresenta automaticamente l'ultima activity presente sullo stack, cioè Activity1 public void onClick(View v) { Activity2.this.finish(); finish(); this.finish(); // ok // ok // nok per la classe annidata Il metodo finish rimuove fisicamente l‟istanza dalla memoria generando l‟evento onDestroy. Viceversa il pulsante Back dello smartphone non rimuove l‟istanza dalla memoria, ma la contrassegna come “Stopped” e genera il solo evento onStop. Il Sistema Operativo la rimuoverà dalla memoria soltanto in caso di necessità. Come riportare una Activity in primo piano Per riportare una Activity in primo piano è sufficiente settare un apposito flag prima di eseguire startActivity. In questo modo l‟Activity non viene re istanziata ma solo riportata in primo piano. Intent intent = new Intent(Activity2.this, Activity1.class); intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); startActivity(intent); Attenzione che, in questo caso, in Activity2 non viene generato l‟evento onCreate, ma viene generato semplicemente l‟evento onResume() che è un evento privo di parametri e che, allo stesso modo di onCreate(), come prima istruzione DEVE richiamare l‟evento onResume( ) della superclasse pag 25 Tecnologie - Classe Quinta robertomana.it Java per Android Passaggio di parametri ad una Activity Sia nel caso di Explicit Intent, sia nel caso di Intent Anonimo, prima di richiamare il metodo startActiity(intent), si possono aggiungere dentro intent eventuali parametri da passare alla seconda Activity, facendo uso del metodo intent.putExtra("nomeParametro", valore); intent.putExtra("parametro1","Ciao"); intent.putExtra("parametro2", (float) 77.77); // default double intent.putExtra("parametro3", new int[]{23, 12, 54}); intent.putStringArrayListExtra("parametro4", myList); // ArrayList this.startActivity(intent); Lettura dei parametri in Activity2 Activity2 può accedere ai parametri ricevuti nel modo seguente: Intent intent = this.getIntent(); if(intent!=null && intent.hasExtra("parametro1")) parametro1 = intent.getStringExtra("parametro1"); if(intent!=null && intent.hasExtra("parametro2")) parametro2 = intent.getFloatExtra("parametro2", 0); (0=defaultValue) parametro3 = intent.getIntArrayExtra("parametro3"); myList = intent.getStringArrayListExtra ("parametro4"); In alternativa al passaggio dei passaggio dei parametri, questi potrebbero essere dichiarati statici dentro Activity1 diventando accessibili da Activity2 semplicemente come Activity1.parametro1 Restituzione di un risultato Se Activity1 intende ricevere una risposta da Activity2, in fase di chiamata non deve utilizzare il metodo startActivity(), ma startActivityForResult() passandogli come secondo parametro un codice identificativo della richiesta : final int CODICE_RICHIESTA = 13; this.startActivityForResult(intent, CODICE_RICHIESTA); Activity2, una volta letti i parametri ed eseguite le eventuali azioni necessarie, potrà presentare all‟utente due pulsanti OK e CANCEL che provvederanno ad inviare all‟utente due risultati differenti. public void btnOk_Click(View v) { Intent intent = new Intent(); intent.putExtra("risultato", "xx"); this.setResult(RESULT_OK, intent); this.finish(); } public void btnAnnulla_Click(View v) { this.setResult(RESULT_CANCELED); this.finish(); } pag 26 Tecnologie - Classe Quinta robertomana.it Java per Android Activity1 infine potrà andare a leggere il risultato ricevuto gestendo il seguente evento relativo all‟Activity onActivityResult(int requestCode, int resultCode, Intent intent) dove: Il primo parametro rappresenta il codice relativo alla richiesta inviata da Activity1 Il secondo parametro rappresenta il codice restituito da Activity2 Il terzo parametro rappresenta gli eventuali dati inviati da Activity2 ad Activity1 public void onActivityResult(int reqCode, int resultCode, Intent intent){ TextView txt = (TextView) findViewById(R.id.txtRisultato); if(reqCode == CODICE_RICHIESTA){ String pulsantePremuto = ""; if(resultCode == RESULT_OK) pulsantePremuto = "RESULT_OK"; else if(resultCode == RESULT_CANCELED) pulsantePremuto = "RESULT_CANCELED"; String par = null; if(intent!=null && intent.hasExtra("risultato")) par = intent.getStringExtra("risultato"); txt.setText(pulsantePremuto + “ – “ + par); } } Attenzione che, essendo resultCode una define numerica, la seguente riga txt.setText(Integer.toString(resultCode)); produrrebbe a video il valore numerico di resultCode convertito in stringa. Terminazione dell’intera applicazione La chiusura di una app di solito è demandata al SO, e l‟uso di un pulsante Exit è sconsigliato. Una semplice possibilità per chiudere la app è quella di far ritornare alla Activity secondaria un certo risultato prima di fare finish. Su quel risultato anche la Activity primaria farà il suo finish. public void btnOk_Click(View v) { Intent intent = new Intent(); intent.putExtra("termina",true); this.setResult(RESULT_OK,intent); this.finish(); fa sì che venga ritornato il valore Exit alla Activity principale, la quale potrà eseguire il seguente codice: Boolean chiudi=intent.getBooleanExtra("termina",false); if(chiudi) this.finish(); pag 27 Tecnologie - Classe Quinta robertomana.it Java per Android I principali eventi di una activity (ciclo di vita) onCreate : evento richiamato al momento del caricamento in memoria onDestroy : Un'activity viene distrutta quando viene terminata esplicitamente mediante il metodo finish oppure “quando è stata chiusa dal pulsante Back del dispositivo e in seguito il Sistema Operativo l‟ha rimossa dalla memoria per fare spazio al lancio di una nuova applicazione”. onPause : questo evento si verifica quando una nuova activity viene eseguita e l‟activity corrente viene messa in uno stato di pausa (equivalente allo stato di ready del controllore dei processi) onResume : richiamata quando che l‟Actiity ritorna in primo piano Questi eventi sono utilizzati per attivare/terminare servizi che devono eseguiti quando l'activity è in uso onStop : Quando si usa il pulsante Back del dispositivo, l‟Activity è come se venisse chiusa (con cancellazione dei suoi dati), però in realtà non viene deallocata dalla memoria fino a quando c‟è memoria disponibile. Questo per evitare di doverla ogni volta ricaricare. Cioè l‟Activity non compare tra le activity “attive” ma viene comuqnue mantenuta in memeoria. Nel momento in dovesse essere riavviata non viene più generato l‟evento onCreate ma l‟evento onRestart seguito da onStart Quando non c‟è più ram disponbile, il so chiude automaticamente l‟applicazione in fondo allo stack . Viceversa il metodo finish() forza la de-allocazione dalla memoria. pag 28 Tecnologie - Classe Quinta robertomana.it Java per Android Gestione dei thread Per realizzare un Timer si possono utilizzare 2 diverse soluzioni. Prima soluzione: Utilizzo di un thread generico Occorre istanziare due oggetti: un oggetto Runnable dove Runnable è una classe Astratta la cui istanza deve implementare il metodo Run, che è il metodo che consente di eseguire una procedura su un thread separato un oggetto Handler che è un oggetto contenuto nel namespace osHandler che consente di controllare il thread (avvio / arresto). La procedura invocata all‟interno di un thread separato può comunque accedere ai controlli dell‟interfaccia grafica. private Handler threadHandler; final long intervallo = 4000; public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); threadHandler = new Handler(); } public void btnAvvia_Click(View v){ // Avvio del thread che verrà avviato fra 4 secondi // Si aspetta come primo parametro una ISTANZA della classe Runnable threadHandler.postDelayed(aggiornaGrafica, intervallo); } Terminate le istruzioni da eseguire il thread si arresterebbe, ma si può inserire un comando di riavvio. private Runnable aggiornaGrafica = new Runnable() { @Override public void run() { Toast t =Toast.makeText(MainActivity.this, "tick", toast.LENGTH_SHORT) t.show(); threadHandler.postDelayed(this, intervallo); } }; public void btnArresta_Click(View v){ // Arresto del thread threadHandler.removeCallbacks(aggiornaGrafica); } L‟istanza aggiornaGrafica può essere generata anche dentro onCreate, però in tal caso occorre portare dentro onCreate anche i Listener di ascolto Avvia_Click e Arresta_Click (e, a questo punto, anche le 2 variabili esterne). Seconda soluzione: Utilizzo dell’oggetto Timer Molto più complessa della precedente. Anche in questo caso occorre istanziare due oggetti: un oggetto Timer che consente di eseguire una certa procedura a intervalli regolari un oggetto TimerTask che contiene la procedura da eseguire pag 29 Tecnologie - Classe Quinta robertomana.it Java per Android MyTimerTask myTask = new MyTimerTask(); Timer myTimer = new Timer(); // Avvio del timer myTimer.schedule(myTask, 1500, 3000); // Ritardo del 1° avvio, interval // Arresto del timer myTask.cancel(); class MyTimerTask extends TimerTask { public void run() { txtOutput.setText(Integer.toString(nCounter)); } } Android Device Monitor DDMS = Dalvik Device Monitor Service è un software che funge da terminale grafico verso l‟emulatore dello smartphone. E‟ come se ci fosse un computer connesso tramite USB allo smartphone. IntelliJ / Tools / Android / Android Device Monitor Selezionare il device connesso nell’elenco di sinistra. Finestra File Explorer Consente di accedere al file system dello smatphone. Le cartelle dati a cui l‟utente ha accesso in scrittura sono sostanzialmente due: Data (memoria interna) SdCard (memory card esterna) Le app caricate all‟interno dello smartphone sono posizionate nella cartella Data / App Dentro Data si può creare una cartella Music in cui salvare ad esempio files.mp3. Per creare una nuova cartella si usa il pulsante + in alto destra. Per caricare i files è sufficiente il trascinamento oppure il pulsante in alto a destra prima del + Finestra Emulator Control Consente ad esempio di impostare un valore simulato delle attuali coordinate GPS. Gestione dei Text File L’oggetto File Restituisce un oggetto File associato al file avente il path indicato. Occorre sempre specificare il path completo, altrimenti viene assunto come path quello di root generando un errore di protezione. I files vengono salvati nella cartella data / data / path dove String path = this.getFilesDir().getPath().toString() + "/myFile.txt"; pag 30 Tecnologie - Classe Quinta robertomana.it Java per Android Scrittura e Lettura del file public void btnSalvaClick(View v) { try{ // true = append; false=overwrite (default); FileWriter writer = new FileWriter(path, true) writer.write("Lorem Ipsum\r\n"); writer.flush(); writer.close(); toast.setText("File Salvato Correttamente"); toast.show(); } catch (Exception ex) { toast.setText(ex.getMessage()); toast.show(); } public void btnLeggiClick(View v) { try{ FileReader reader = new FileReader(path); BufferedReader br = new BufferedReader(reader); String s, testo=""; // readline elimina il \r\n finale while((s=br.readLine()) != null) testo += s + "\r\n"; br.close(); reader.close(); toast.setText(testo); toast.show(); } catch (Exception ex) { // pokemon exception ! gotta catch „em all toast.setText(ex.getMessage()); toast.show(); } Per vedere se un file esiste ed è effettivamente un file e non una directory: File myFile = new File(path); if(myFile.isFile()) FileReader reader = new FileReader(myFile); else myFile.createNewFile(); Per leggere il contenuto di una cartella : File dir = new File(path); String [] vect = dir.list(); External Storage E‟ anche possibile salvare i dati su una scheda SD esterna. A tal file occorre aggiungere all‟interno del file Manifest (direttamente nel tag principale <manifest>) la seguente riga: <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> Il path da indicare per l‟accesso al file sarà il seguente : String filePath = “/sdcard/filename.txt"; Essendo la SD rimuovibile, se si effettua il salvataggio di dati sulla SD è opportuno controllare che i dati salvati non siano indispensabili per il corretto funzionamento dell'applicazione. pag 31 Tecnologie - Classe Quinta robertomana.it Java per Android L’oggetto FileOutputStream In Java, in alternativa agli oggetti FileWriter e FileReader, raccomandati nel caso di stream testuali, esiste anche un altro oggetto raccomandato nel caso di stream binari che vengono letti byte a byte senza attenzione all‟interpretazione dei dati. Nota: A differenza del caso precedente, questa volta nomeFile non è il path completo, ma il semplice filename. FileOutputStream fs = openFileOutput(nomeFile, Context.MODE_PRIVATE); fs.write(myVar.getBytes()); fs.close(); Qualsiasi variabile viene salvata su file in forma binaria byte per byte. Questa tecnica potrebbe essere utilizzata per creare i vecchi files dati, ma con estrema difficoltà. Molto più semplice convertire in testo ed usare files testuali gestiti tramite i due oggetti precedenti. Lettura File f = getBaseContext().getFileStreamPath(nomeFile); String ris = ""; if (f.exists()) { try { FileInputStream fs = openFileInput(nomeFile); // Legge tanti bytes quanto è grande tmp byte[] tmp = new byte[1]; while (fs.read (tmp) != -1) ris = ris + new String(tmp,"UTF-8"); fs.close(); } Diritti di accesso Di default questo tipo di salvataggio è un salvataggio privato. Il file può essere letto e scritto esclusivamente dall'applicazione che lo ha creato e non da altre applicazioni. Per potere accedere al file anche da altre applicazioni su può utilizzare una delle seguenti costanti: Context.MODE_WORLD_READABLE: viene consentito l'accesso in lettura a tutte le applicazioni Context.MODE_WORLD_WRITEABLE: viene consentito l'accesso in scrittura a tutte le applicazioni Shared Preferences Le SharedPreferences vengono salvate all‟interno di un‟area di memoria comune in cui ogni App può salvare delle variabili in formato chiave/valore, variabili che la App può leggere in qualsiasi momento. Shared nel senso che sono condivise tra tutte le Activity. Ottime per salvare le preferenze dall‟utente. Le SharedPreferences vengono salvate in un file xml in the app data folder, i.e. /data/data/YOUR_PACKAGE_NAME/shared_prefs/YOUR_PREFS_NAME.xml Il metodo getSharedPreferences ritorna la Shared Preference indicata dal primo parametro. Se non esiste la crea. Il secondo parametro è lo stesso di prima ed indica i diritti di accesso. Si usa sempre MODE_PRIVATE , i valori _READABLE e _WRITEABLE sono stati deprecati. pag 32 Tecnologie - Classe Quinta robertomana.it Java per Android public void btnSalva_click(View v){ SharedPreferences myPrefs =getSharedPreferences("Shared1",MODE_PRIVATE) SharedPreferences.Editor editor = myPrefs.edit(); editor.putString("nome", "pippo"); editor.putString("cognome", "pluto"); editor.putInt("eta", 18); editor.remove("cognome"); editor.commit(); // Salva su file public void btnLeggi_click(View v){ SharedPreferences myPrefs=getSharedPreferences("Shared1",MODE_PRIVATE) String nome = myPrefs.getString("nome", "Nessun valore"); String cognome = myPrefs.getString("cognome", "Nessun valore"); int eta = myPrefs.getInt("eta", 0); TextView lbl = (TextView) findViewById(R.id.lblStato); String s = "nome=" +nome + "\n"; s += "cognome=" +cognome + "\r\n"; s += "eta=" + Integer.toString(eta) + "\r\n"; lbl.setText(s); L‟oggetto Editor consente di modificare le singole variabili di un gruppo di SharedPreferences. List<String> e ArrayList<String> List è una interfaccia,e dunque non può essere istanziata direttamente, ma può ad esempio essere restituita da un metodo che esegue il cast da ArrayList. ArrayList viceversa è una classe che implementa l‟interfaccia List e dunque può essere istanziata. Ci sono anche altre classi che implementano l‟interfaccia List in modo diverso da ArrayList, ad es la classe LinkedList. // Creazione di un ArrayList di Stringhe ArrayList<String> list = new ArrayList<String>(); // <String> è il default per cui può anche essere omesso // Popolamento dell’ArrayList list.add("Fossano"); list.add(pos, "Saluzzo"); // int pos = 0; String[] vect = new String[] { "Cuneo", "Alba", "Genola", "Bra" }; list.addAll(Arrays.asList(vect)); list.addAll(list2); // Scansione dell’ArrayList for (int i = 0; i<list.size(); i++) s += list.get(i) + "\r\n"; // Cancellazione elementi list.clear(); list.remove(pos); pag 33 Tecnologie - Classe Quinta robertomana.it Java per Android ArrayAdapter Per il caricamento dei dati di un ArrayList in un Listiew, occorre utilizzare un Adapter che si occupi di associare il dato corretto in relazione al layout grafico definito per la visualizzzione delle singola righe. Si chiama Adapter in quanto funziona proprio come adattatore tra due elementi: i dati e il layout. Esistono numerose varianti di questi "adattatori" a seconda della natura dei dati da mostrare. Uno dei più usati è l'ArrayAdapter, che permette di gestire dei dati memorizzati sotto forma di array. Al costruttore dell'ArrayAdapter occorre passare: 1. Il contesto corrente 2. il nome del layout da utilizzare per la visualizzazione delle singole righe 3. il nome dell'ArrayList che funge da sorgente dati. Invece di un ArrayList, è possibile passare direttamente un vettore di stringhe. L‟ArrayList però, rispetto al vettore statico di stringhe, è estensibile, cioè in qualunque momento è possibile aggiungere nuovi elementi. // Istanza dell’Adapter e associazione con la sorgente dati ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, list); android.R.layout.simple_list_item_1 è un layout predefinito che permette di gestire un solo elemento di testo. E‟ possibile creare layout personalizzati contenenti la rappresentazione di ogni singola riga oppure, nel caso più semplice, un solo TextView eventualmente personalizzato. Può però contenere più TextView combinati in modo da mostrare più campi / img contemporaneamente. lstView lstCitta = (lstView) findViewById(R.id.lstCitta); // Link del ListView all’Adapter lstView.setAdapter(adapter); lstCitta.setOnItemClickListener(listener); adapter.notifyDataSetChanged(); // refresh ListView In Android le liste vengono usate per rappresentare dei dati una riga sotto l'altra con la possibilità di effettuare il classico scrolling per posizionarsi in fondo e in cima. E possibile rendere i singoli elementi della lista sensibili al tocco in modo da accedere al dettaglio di una notizia o di un prodotto. Una volta trascinato sul Layout il controlli ListView si possono definire le seguenti proprietà: android:choiceMode="singleChoice" // Seleziono un solo element alla volta android:listSelector=blue" // Colore dell’elemento selezionato android:fastScrollAlwaysVisible="true" // Barra scorrimento sempre visibile Se non si specifica il listSelector, non viene usato alcun evidenziatore ma semplicemente, al momento del click, l‟elemento selezionato si ricolora per un attimo di blu e poi ritora subito grigio. Accesso ai dati della lista lstCitta.getItemAtPosition(i).toString(); lstCitta.getCount(); // restituisce il numero di elementi contenuti nel ListBox pag 34 Tecnologie - Classe Quinta robertomana.it Java per Android Scrolling del List View lstCitta.setSelection(2); // La lista viene visualizzata a partire dall‟elemento indicato. Per eseguire lo scrolling della ListView è sufficiente clickare su una voce e poi trascinare. Per eseguire lo scrolling da codice : lstCitta.setSelection(index); La lista verrà visualizzata a partire dall‟elemento avente l‟indice index (ovviamente a base 0). La direttiva di layout android:scrollbars= "vertical" è già definita di default. All‟atto del trascinamento compare automaticamente sulla destra una piccola scroll bar, Evento onItemClick Il metodo onItemClick riceve come parametri parent riferimento all‟Adapter che ha scatenato l‟evento v riferimento al controllo associato all‟adapter (ListView) position posizione dell‟elemento su cui si è verificato il clcik id id del controllo associato all‟adapter ListView.OnItemClickListener listener = new ListView.OnItemClickListener() AdapterView.OnItemClickListener lis =new AdapterView.OnItemClickListener() { public void onItemClick(ListView<?> parent, View v, int pos, long id) { String item = (String) parent.getItemAtPosition(pos); show(item); } }; Spinner Implementa il tipico Combo Box. Il funzionamento è praticamente identico rispetto al ListView. Occorre definire una sorgente dati di tipo ArrayList ed un apposito adapter. ArrayList<String> list = new ArrayList<String>(); ArrayAdapter<String> adapter = new ArrayAdapter<String>( this, android.R.layout.simple_spinner_item, list); cmbElenco.setAdapter(adapter); adapter.notifyDataSetChanged(); cmbElenco.setOnItemSelectedListener(new Spinner.OnItemSelectedListener() { public void onItemSelected(Spinner<?> parent, View v, int pos, long id) { String item = (String) parent.getItemAtPosition(pos) show(item); } public void onNothingSelected(AdapterView<?> arg0) { } }); pag 35 Tecnologie - Classe Quinta robertomana.it Java per Android GridView Le GridView operano in modo simile rispetto alle ListView. Per la GridView è possibile ripetere lo stesso identico esercizio precedente. I dati del vettore verranno spalmati attraverso le varie colonne. Dal layout l‟unica cosa da definire è il numero di colonne: android:numColumns="3" E‟ anche possibile definire android:numColumns="auto_fit" specidiando però columnWidth final GridView grid = (GridView) findViewById(R.id.grid); grid.setAdapter(adapter); Anche la procedura di risposta al click è la stessa identica di cui sopra. La posizione restituita è una posizione vettoriale che si incrementa partendo da 0 e scorrendo le righe. I vari campi dei dati da visualizzare devono però essere caricati sequenzialmente all‟interno del vettore. Creazione di un Adapter personalizzato Per la visualizzazione dei dettagli, è possibile utilizzare un TableLayout costruito dinamicamente, oppure è possibile creare un adapter personalizzato basato su un apposito Layout personalizzato in base alla struttura del record da visualizzare. La soluzione basata su TableLayout è più semplice da implementare, mentre la soluzione basata su adapter personalizzato è più strutturata in quanto demanda tutta la gestione dei dati all‟interno della classe di definizione dell‟adapter. L‟Adapter personalizzato può ereditare dalla classe BaseAdapter (Adapter base, occorre però eseguire una implementazione abbastanza complessa) oppure, più semplicemente, può ereditare dalla classe ArrayAdapter<Type> nel qual caso deve implementare soltanto il metodo getView(int position, View layout, ViewGroup parent) dove I tre parametri rappresentano rispettivamente: la posizione (all‟interno della Sorgente Dati) dell‟elemento che si vuole visualizzare sul layout un riferimento al layout personalizzato per la visualizzazione della riga (che il primo giro è null) un riferimento al layout genitore (layout principale) Il metodo ritorna un riferimento al layout in modo che nei giri successivi il riferimento possa essere passato come secondo parametro al metodo stesso, evitando così di re-istanziarlo ogni volta. Questo metodo viene richiamato automaticamente dal Run Time per caricare le righe all‟interno del Layout. Cioè nel momento in cui si istanzia un Adapter associandolo ad una certa sorgente dati, il Run Time provvede automaticamente a scorrere una ad una tutte le righe della sorgente dati e, in corrispondenza di ogni riga, provvede a richiamare il metodo getView() dell‟Adapter per aggiungere la riga all‟interno dell‟Adapter stesso. public class MyAdapter extends ArrayAdapter<Studente> { private final Context context; private final List<Studente> list; public MyAdapter(Context context, List<Studente> list) { super(context, R.layout.rowlayout, list); this.context = context; this.list = list; } pag 36 Tecnologie - Classe Quinta robertomana.it Java per Android public View getView(int position, View layout, ViewGroup parent) { View v = null; if (layout == null) { LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); v = inflater.inflate(R.layout.rowlayout, parent, false); } else v = layout; TextView txtId =(TextView) v.findViewById(R.id.txtId); TextView txtNome = (TextView) v.findViewById(R.id.txtNome); TextView txtCitta = (TextView) v.findViewById(R.id.txtCitta); Studente studente = list.get(position); txtId.setText(Integer.toString(studente.getId())); txtNome.setText(studente.getNome()); txtCitta.setText(studente.getCitta()); return v; } } L'inflating è il meccanismo che permette di istanziare un'oggetto a partire da una risorsa XML. La IF serve ad evitare che l‟oggetto relativo al rowlayout venga re istanziato ad ogni giro Activity Principale protected void onCreate(Bundle savedInstanceState) { setContentView(R.layout.main); ListView lstStudenti = (ListView) findViewById(R.id.listView); // 1) Creazione di un ArrayList ArrayList<Studente> list = new ArrayList<Studente>(); // 2) Popolazione dell’ArrayList list.add (new Studente(1, "pippo", "cuneo")); list.add (new Studente(2, "pluto", "genola")); list.add (new Studente(3, "minnie", "alba")); // 3) Istanza dell’adapter personalizzato MyAdapter adapter = new MyAdapter(this, list); // 4) Link del List View all'Adapter lstStudenti.setAdapter(adapter); Il Listener è lo stesso del ListView base, però questa volta getItemAtPosition(pos) restituisce un Object (lo studente corrente) invece che una semplice String. pos rappresenta il numero di riga. lstStudenti.setOnItemClickListener(new AdapterView.OnItemClickListener() { public void onItemClick(AdapterView<?> parent, View v, int pos, long id) final Object item = lstStudenti.getItemAtPosition(pos); Studente studente = (Studente) item; Toast t = Toast.makeText(MyActivity.this, "", Toast.LENGTH_LONG); t.setText("posiz=" + pos + " nome=" + studente.getName()); t.show(); } pag 37 Tecnologie - Classe Quinta robertomana.it Java per Android Menù Ogni applicazione può possedere un menù attraverso il quale l'utente possa scegliere tra le diverse operazioni da eseguire. Ci sono due tipi di menù: OptionsMenu e ContextMenu. OptionsMenu Questi menù vengono visualizati tramite la pressione del tasto Menù del dispositivo (pulsantino in basso a sinistra), Vengono visualizzati nella parte bassa dello schermo e scompaiono automaticamente dopo il click su una delle voci oppure dopo un altro click sullo stesso pulsante Menù. Sono associati ad una singola Activity ed espongono le operazioni più importanti che un utente può eseguire relativamente a quella Activity. Sono detti anche icon menu in quanto di solito viene inserita in ciascuna voce una icona che sintetizza l‟operazione effettuata in corrispondenza del click. Nelle versioni API più vecchie questi menù avevano un numero massimo di voci visualizzabili pari a sei. In caso di ulteriori voci semplicemente la sesta voce del menù veniva automaticamente mutata in una voce che, se cliccata, apriva un expanded menu contenente le altre voci. API Level 13 In Android 4 le voci vengono visualizzate in verticale una sotto l‟altra (con allineamento a fondo pagina) In Android 5 l‟apertura del Menù viene eseguita mediante un pulsantino “con tre puntini” in alto a destra Evento OnCreateOptionMenu In corrispondenza del click sul pulsante Menù viene automaticamente generato l‟evento OnCreateOptionMenu a cui viene passato come parametro un riferimento menu che punta alle voci di menu da visualizzare. Di default il menu è vuoto e si può utilizzare questo evento per creare un menù personalizzato. Il metodo restituisce un valore booleano: true avrà l'effetto di notificare all'activity di mostrare il menu, mentre false avrà l'effetto contrario (il menù non verrà visualizzato). public boolean onCreateOptionsMenu(Menu menu) { menu.add(Menu.NONE, 1, 1, "Voce 1"); menu.add(Menu.NONE, 2, 2, "Voce 2"); MenuItem menu3 = menu.add(Menu.NONE, 3, 3, "Voce 3"); menu3.setIcon(R.drawable.icon); // Non sembra supportato menu3.setCheckable(true); SubMenu menuOpzioni = menu.addSubMenu(Menu.NONE, 8, 8,"Opzioni"); menuOpzioni.add(GROUP1, OPZIONE1, 1, "Opzione 1"); menuOpzioni.add(GROUP1, OPZIONE2, 2, "Opzione 2"); pag 38 Tecnologie - Classe Quinta robertomana.it Java per Android // setGroupCheckable(int group, boolean checkable, boolean exclusive menu8.setGroupCheckable(GROUP1,true,true); return true; } Parametri del metodo add menu.add(int groupId, int itemId, int order, CharSequence title) groupId è un parametro che indica il gruppo di appartenenza della voce del menu itemId indica l'id da assegnare alla voce del menù order specifica la posizione della voce all'interno del menù (menu.NONE = ordine naturale). title specifica la stringa di testo che verrà mostrata all'utente. Il gruppo in realtà non ha alcuna funzione visuale, ma serve semplicemente a creare un gruppo logico al quale si possono applicare i metodi setGroupCheckable, setGroupEnabled, setGroupVisible Valore Restituito: Il metodo .add restituisce un riferimento all‟item appena aggiunto MenuItem v1 = menu.add(Menu.NONE, 1, 1, "voce1"); v1.setIcon(R.drawable.icon); Proprietà dell’oggetto menuItem getTitle (restitusice il testo dell‟item), setIcon, setEnabled, setVisible, setCheckable, setChecked, Evento onOptionsItemSelected di risposta al click In corrispondenza del click su una voce del Menù viene automaticamente generato l‟evento onOptionsItemSelected che riceve come parametro un riferimento all‟item che ha generato l‟evento. Toast toast = null; switch(item.getItemId()){ case 1: toast = Toast.makeText(this, "premuto item1", Toast.LENGTH_SHORT); break; case 2: toast = Toast.makeText(this, "premuto item2", Toast.LENGTH_SHORT); break; case OPZIONI: break; case 3: case OPZIONE1: case OPZIONE2: if (item.isChecked()) item.setChecked(false); else item.setChecked(true); break; default: toast = Toast.makeText(this, "altro item", Toast.LENGTH_SHORT); break; } if (toast!=null) toast.show(); return true; pag 39 Tecnologie - Classe Quinta robertomana.it Java per Android Creazione statica di un OptionsMenu Invece di aggiungere dinamicamente le voci, un OptionsMenu può anche essere creato staticamente aggiungendo all‟interno della cartella delle risorse un nuovo file menu.xml. Click destro su res / new / Android resource file / tipo=menu Indipendentemente dal nome del file verrà creato un nuovo oggetto XML denominato menù. A differenza del layout, all‟oggetto menu non è associato un designer, per cui occorre lavorare direttamene sul file xml impostando gli stessi attributi dell‟esempio precedente cioè id e title. <group android:id = "@+id/gruppo01"> <item android:id = "@+id/item01" android:title="voce1"> </item> <item android:id = "@+id/item02" android:title="voce2"> </item> </group> <item android:id = "@+id/item04" android:title="voce4"> </item> Evento OnCreateOptionMenu In questo evento, generato quando l‟utente clicka sul pulsante Menu, occorre semplicemente „collegare‟ il parametro menu ricevuto dall‟evento con il menu creato staticamente all‟interno del file xml. A tal fine occorre utilizzare l‟oggetto MenuInflater. public boolean onCreateOptionsMenu (Menu menu){ MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.menu, menu); return true; } Evento onOptionsItemSelected di risposta al click Stesso di prima ContextMenu Il ContextMenu è simile al menù associato al tasto destro del mouse su un controllo Windows. Il ContextMenu associato ad un elemento compare in corrispondenza del click prolungato sull'elemento Per associare due diversi ContextMenu ad un Button btn ed un TextView lbl si può scrivere : public void onCreate(Bundle savedInstanceState) { this.registerForContextMenu(btn); this.registerForContextMenu(lbl); } this.registerForContextMenu fa si che, in corrispondenza del click sul controllo, venga automaticamente generato l‟evento onCreateContextMenu nel quale viene creato il menu. public void onCreateContextMenu(ContextMenu menu, View v, menuInfo) { if(v.getId() == R.id.btn1) { menu.setHeaderTitle("ContextMenu del button"); menu.add(Menu.NONE, 1, 1, "Voce bottone 1"); menu.add(Menu.NONE, 2, 2, "Voce bottone 2"); } else if(v.getId() == 2) { // textView menu.setHeaderTitle("ContextMenu della TextView"); menu.add(Menu.NONE, 3, 1, "Voce TextView 1"); menu.add(Menu.NONE, 4, 2, "Voce TextView 2"); } Per poter associare un comportamento specifico alla pressione di uno dei tasti del menù si opera come per gli OptionMenu, utilizzando il metodo onContextItemSelected(MenuItem item). pag 40 Tecnologie - Classe Quinta robertomana.it Java per Android Utilizzo di XML in Java Parsing di una stringa xml String xml = ………………………; DocumentBuilderFactory builder = DocumentBuilderFactory.newInstance(); DocumentBuilder docBuilder = builder.newDocumentBuilder(); Document xmlDoc = docBuilder.parse(“myFile.xml”); Document xmlDoc = docBuilder.parse(new InputSource(new StringReader(xml))); Il metodo docBuilder.parse() si aspetta come parametro : un File XML oppure un InputStream oppure un oggetto InputSource che è un stream testuale „specializzato‟, cioè in grado di gestire anche i caratteri speciali. Partendo da una Stringa, occorre convertirla in un oggetto StringReader (che è semplicemente uno stream testuale) e poi in InputSource. Restituisce il DOM corrispondente al parametro ricevuto. Sintatticamente accetta come parametro una semplice stringa, però questa stringa deve essere un URI, cioè sostanzialmente il path di un documento Serializzazione di un XML Document XMLSerializer ser = new XMLSerializer(); ser.setCharOutputStream(new FileWriter("myFile.xml")); ser.serialize(xmlDoc); // oppure XMLSerializer ser = new XMLSerializer(); StringWriter sw = new StringWriter(); ser.setCharOutputStream(sw); ser.serialize(xmlDoc); String xml = sw.toString(); // oppure StringWriter writer = new StringWriter(); TransformerFactory factory = TransformerFactory.newInstance(); Transformer transformer = factory.newTransformer(); transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no"); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); transformer.setOutputProperty(OutputKeys.METHOD, "xml"); transformer.transform(new DOMSource(xmlDoc), new StreamResult(writer)); String xml=writer.toString(); I vari tipi di Nodo L‟oggetto Node è la struttura più generale da cui ereditano le seguenti classi: Document: Estende Node per rappresentare (la radice di) un documento. Un Node non può essere creato se non viene creato un Document Element: Estende Node per rappresentare un elemento Attr: Estende Node per rappresentare un attributo Text: Estende Node per rappresentare una sezione #PCDATA (nodo testuale o foglia). pag 41 Tecnologie - Classe Quinta robertomana.it Java per Android Questi vari tipi di Node sono definiti mediante le seguenti costanti: 1 = Node.ELEMENT_NODE = nodo vero e proprio 2 = Node.ATTRIBUTE_NODE = attibuto 3 = Node.TEXT_NODE = nodo testuale, cioè foglia dell‟albero (valore di un nodo ELEMENT) 8 = Node.COMMENT_NODE = commento 9 = Node.DOCUMENT_NODE = l‟intero xmlDoc subito dopo il parsing Accesso alla radice dell’albero Node Node Node Node root root root root = = = = xmlDoc.getDocumentElement(); (Restituisce un Element) xmlDoc.getChildNodes().item(0); xmlDoc.getElementsByTagName("bookstore")[0]; xmlDoc.getElementById(String elementId); Metodi per la navigazione dell’albero Restituiscono come risultato sempre un generic oggetto di tipo Node di cui occorrerà eventualmente eseguire il cast .getChildNodes(); .getFirstChild(); .getLastChild(); .getNextSibling(); .getPreviousSibling(); .getParentNode(); .getOwnerDocument() collezione (NodeList) dei figli del nodo corrente primo figlio del nodo corrente ultimo figlio del nodo corrente prossimo fratello del nodo corrente fratello precedente padre del nodo corrente restituisce l‟oggetto Document contenente il nodo Esempio Node root = xml.getDocumentElement(); root.normalize(); if (xml.hasChildNodes()) { Node node = root.getFirstChild(); while (node != null) { if (node.getNodeType() == Node.ELEMENT_NODE) { list.add(node.getTextContent()); node = node.getNextSibling(); } } // oppure accedo direttamente al vettore dei nomi // NodeList nodi = doc.getElementsByTagName("nome"); } Il metodo .normalize() serve ad eliminare eventuali empty node terminali (whitespaces). Principali Metodi dell’oggetto NodeList int getLength() Node item(int index) (restituita da .getChildNodes()) numero dei nodi accede all‟elemento iesimo (a base 0) pag 42 Tecnologie - Classe Quinta robertomana.it Java per Android Altri Metodi dell’oggetto Node boolean hasChildNodes() verifica la presenza di nodi figli short getNodeType() restituisce il tipo del nodo (Element ,Text, Attr) String getNodeName() restituisce il nome di un TAG di tipo Element setNodeName(String) imposta il nome di un TAG di tipo Element String getNodeValue() restituisce il contenuto di un nodo di tipo Text (foglia) setNodeValue(String) imposta il contenuto di un nodo di tipo Text (foglia) String getTextContent() restituisce il contenuto testuale (foglia) di un Element Node setTextContent (String) imposta il contenuto testuale di un Element Node (in pratica consente di associare direttamente una foglia ad un nodo, come InnerText di C#) Principali Metodi di un nodo di tipo Text void setData(String data) imposta il contenuto del nodo String getData() restituisce il contenuto del nodo Principali Metodi di un nodo di tipo Element String getTagName() restituisce il nome dell’elemento NodeList getElementsByTagName(String name) restituisce i sotto-nodi con un certo tag Principali Metodi per la gestione degli attributi di un oggetto Node boolean hasAttributes() verifica la presenza di attributi boolean hasAttribute(String name) verifica la presenza di un attributo String getAttribute(String name) restituisce il valore di un attributo void setAttribute(String name, String value) imposta il valore di un attributo Attr attr getAttributeNode(String name) restituisce un certo attributo come Object void setAttributeNode(Attr newAttr) aggiunge un nuovo attributo String attr.getName() restituisce il nome dell‟attributo String attr.getValue() restituisce il valore dell‟attributo void attr.setValue(String value) imposta il valore dell‟attributo Element attr.getOwnerElement() restituisce l‟elemento che contiene l‟attributo boolean attr.getSpecified() verifica se l‟attributo è stato specificato esplicitamente, oppure assume il suo valore di default NamedNodeMap attrList getAttributes() restituisce la lista degli attributi del nodo int attrList.getLength(); Numero di attributi Node attrList.item(i) Attributo con indice i (a base 0) Node attrList.getNamedItem(attributeName); Node attrList.removeNamedItem(attributeName); Void attrList.setNamedItem(Node node); aggiunge, o sostituisce se presente, un nodo all‟insieme pag 43 Tecnologie - Classe Quinta robertomana.it Java per Android Metodi dell’oggetto Document per la creazione di nuovi nodi Element createElement(String tagName) crea un elemento con un tag Text createTextNode(String data) crea una sezione #PCDATA (foglia) specificando il contenuto Attr createAttribute(String name) crea un attributo con un dato nome Esempi: Element node = xmlDoc.createElement("nodeName"); root.appendChild(node); node.setTextContent("valore"); // oppure Text foglia = xmlDoc.createTextNode("salve mondo"); node.appendChild(foglia); // Nodo di tipo foglia Metodi per l’aggiunta / rimozione dei nodi parentNode.appendChild(Node) Appende in coda un nuovo nodo parentNode.insertBefore(newNode, childNode) aggiunge newNode davanti a childNode parentNode.removeChild(childNode) Elimina childNode, lui e tutti gli eventuali nipoti, di qualunque ordine e grado Esempio di creazione di un nuovo albero <A id="aa"> <B/> <C>text</C> </A> Document doc = docBuilder.newDocument(); Element elemA = doc.createElement("A"); elemA.setAttribute("id","aa"); doc.appendChild(elemA); Element elemB = doc.createElement("B"); elemA.appendChild(elemB); Element elemC = doc.createElement("C"); Text text = doc.createText("text"); elemC.appendChild(text); elemA.appendChild(elemC); pag 44 Tecnologie - Classe Quinta robertomana.it Java per Android Accesso a SQLite Esistono diversi modi per creare un database, ma quello più utilizzato è quello di estendere la classe SQLiteOpenHelper e poi effettuare un overriding del metodo onCreate per poter creare le tabelle che rappresenteranno la struttura del database. public class SqliteHelper extends SQLiteOpenHelper { private static Context context = null; private static final String[] ALL_TABLES = { "studenti", "insegnanti" }; private static final String CREA_TABELLA_STUDENTI = "CREATE TABLE if not exists studenti (id integer not null primary key, nome TEXT, email TEXT);"; private static final String CREA_TABELLA_INSEGNANTI ="CREATE TABLE if not exists insegnanti (id integer not null primary key, nome TEXT, email TEXT);"; // costruttore SqliteHelper(Context context, String dbName) { // L'ultimo parametro rappresenta la versione iniziale del database super(context, dbName, null, 1); this.context = context; } public void onCreate(SQLiteDatabase db) { try { db.execSQL(CREA_TABELLA_STUDENTI); db.execSQL(CREA_TABELLA_INSEGNANTI); showToast("CREATING...."); } catch (Exception ex) { showToast (ex.getMessage()); } } public void onUpgrade(SQLiteDatabase db, int oldVers, int newVers) { // ricrea tutte le tabelle presenti nel database for (String s : ALL_TABLES) db.execSQL("DROP TABLE IF EXISTS " + s); onCreate(db); } public class Sqlite { public static void init(Context context) { // Se il db non esiste viene creato generando l'eveto onCreate if (sqliteHelper == null) sqliteHelper = new SqliteHelper(context, DATABASE_NAME ); } /* Open database for insert,update,delete in syncronized manner */ private static synchronized SQLiteDatabase open() { return sqliteHelper.getWritableDatabase(); } La parola chiave syncronized, utilizzabile anche all‟interno di una classe davanti ad un blocco di istruzioni, fa si che il blocco di istruzioni racchiuse venga eseguito da un solo thread alla volta. In pratica realizza un specie di semaforo. Nel caso del database fa sì che il db venga aperto in modo sincrono, nel senso che un solo thread alla volta potrà accedere al database. Eventuali Thread successivi verranno accodati al semaforo (un po‟ come il db.serialize() di Node.js). Es: public void increment() { synchronized (this) { count++; } } pag 45 Tecnologie - Classe Quinta robertomana.it Java per Android // 1) aggiungi nuovo studente : il metodo db.insert() public static void aggiungiStudente(Studente studente) { final SQLiteDatabase db = open(); String name = sqlEscape(studente.getNome()); Let's suppose you have a table named foo where all columns either allow NULL String email = sqlEscape(studente.getEmail());values or have defaults. ContentValues record = new ContentValues(); In some SQL implementations, this would record.put(STUDENTE_NOME, name); be valid SQL: INSERT INTO foo; record.put(STUDENTE_EMAIL, email); That's not valid in SQLite. You have to db.insert(TABELLA_STUDENTI, null, record); have at least one column specified. db.close(); Il secondo parametro serve a specificare } il nome della colonna a cui assegnare NULL per impedire che l’operazione vada // 2) Query di ricerca : il metodo db.query() in errore. public static Studente ricercaStudente (int id) { final SQLiteDatabase db = open(); // 1=tableName, 2=arrayOfTableColumns, 3=whereClause, 4=arrayOfWhereArgs, 5=groupBy, 6=having, 7=orderBy, 8=limit; Cursor cursor = db.query( TABELLA_STUDENTI, // 1 new String[] {STUDENTE_ID, STUDENTE_NOME, STUDENTE_EMAIL }, // 2 STUDENTE_ID + "=?", // 3 new String[] { String.valueOf(id) }, // 4 null, null, null, null); // 5, 6, 7, 8 Studente studente = null; if (cursor != null) { cursor.moveToFirst(); studente = new Studente(cursor.getString(0), cursor.getString(1), cursor.getString(2)); } db.close(); return studente; } // 3) Query di ricerca : il metodo db.rawQuery() // Query grezza public static List<Studente> ricercaListaStudenti() { List<Studente> list = new ArrayList<Studente>(); String sql = "SELECT * FROM " + TABELLA_STUDENTI; final SQLiteDatabase db = open(); Cursor cursor = db.rawQuery(sql, null); // 2°= String[] parameters if (cursor !=null) { cursor.moveToFirst(); do { Studente studente = new Studente(); studente.setId(Integer.parseInt(cursor.getString(0))); studente.setNome(cursor.getString(1)); studente.setEmail(cursor.getString(2)); list.add(studente); } while (cursor.moveToNext()); } db.close(); return list; Unsafe: String whereClause = "column1='" + value + "'"; // SQL injection Safe: String whereClause = "column1=?"; pag 46 Tecnologie - Classe Quinta robertomana.it Java per Android // 4) modifica studente avente id indicato : db.update() public static int aggiornaStudente(Studente studente) { final SQLiteDatabase db = open(); ContentValues values = new ContentValues(); values.put(STUDENTE_NOME, studente.getNome()); values.put(STUDENTE_EMAIL, studente.getEmail()); int ris = db.update(TABELLA_STUDENTI, values, STUDENTE_ID + " = ?", new String[]{String.valueOf(studente.getId())}); db.close(); return ris; } // 5) Cancellazione di uno studente avente id indicato : db.delete() public static void cancellaStudente(int id) { final SQLiteDatabase db = open(); db.delete(TABELLA_STUDENTI, STUDENTE_ID + " = ?", new String[] { String.valueOf(id) }); db.close(); pag 47 Tecnologie - Classe Quinta robertomana.it Java per Android Accesso ad un web service HTTP Uno dei metodi più comuni utilizzati dalle applicazioni per comunicare con il mondo esterno è quello di accedere tramite HTTP ai web service dedicati dai quali possono scaricare svariati tipi di contenuti Affinchè la app possa inviare richieste HTTP all‟esterno occorre registrare i permessi all‟interno del file Manifest, creando una nuova sezione direttamente all‟interno del tag<manifest> <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> L’oggetto Runnable L‟oggetto Runnable è l‟oggetto base di java per la realizzazione dei threading. Una procedura eseguita all‟interno di un oggetto Runnable può comunicare con il thread principale solo attraverso uno scambio di Messages. La classe astratta AsynkTask L‟oggetto AsynkTask è un wrapper di Android che utilizza un oggetto Runnable per eseguire una certa procedura in un thread secondario e gestisce la comunicazione fra il thread principale ed il thread secondario senza costringere il programmatore ad entrare nei dettagli di livello più basso (Messages). La classe AsynkTask è stata pensata appositamente come classe di supporto per l‟esecuzione di thread di breve durata che al termine restituisco un risultato al chiamante. Metodi della classe AsyncTask String doInBackground(String... args) unico metodo effettivamente eseguito all’interno di un thread separato. E‟ di tipo protected, quindi non visibile all‟esterno della classe e può essere richiamato tramite il metodo pubblico execute(). E‟ l‟unico metodo di tipo MUST OVERRIDE. Gli altri tre metodi sono metodi di evento richiamati rispettivamente in corrispondenza dell‟avvio del thread, a tempo durante l‟esecuzione del thread, ed in corrispondenza della terminazione del thread. I tre puntini indicano che il parametro args è una ellisse, cioè sequenza di parametri tutti dello stesso tipo che il chiamato tratta come un vettore. Si aspetta un parametro del tipo indicato da TParams e restituisce un risultato di tipo indicato da TResult che viene passato automaticamente all‟evento onPostExecute onPreExeute() richiamato nel momento in cui il thread viene avviato. Per eventuali inizializzazioni onProgressUpdate(View v) utilizzato per riportare sulla UI lo stato di avanzamento del thread. Si aspetta un parametro dei tipo indicato da TProgress che potrebbe essere ad esempio una progress bar di visualizzazione dello stato di avanzamento (sulla base ad es del n. di bytes ricevuti) onPostExecute(String result) richiamato al termine dell‟esecuzione del thread. Il parametro result viene iniettato da AsynkTask al termine del metodo doInBackground (cioè in corrispondenza del ricevimento dei dati dal server). Scopo di questo evento è quello di elaborare il risultato scrivendolo all‟interno di un Object ricevuto dall‟Activity principale. Dichiarazione di una classe che eredita da AsyncTask La classe astratta AsynkTask è una Typed Class definita da tre tipizzazioni: AsyncTask<TParams, TProgress, TResult> pag 48 Tecnologie - Classe Quinta robertomana.it Java per Android TParams rappresenta il tipo delle variabili che il chiamante può passare al metodo execute (che a sua volta richiama doInBackground). Le variabili devono essere tutte dello stesso tipo e vengono passate attraverso una ellissi. TParams seve appunto a tipizzare questi parametri. Se non si vogliono passare parametri TParams sarà Void. TProgress rappresenta un object utilizzabile dal metodo onProgressUpdate per aggiornare il thread principale (UI) sullo stato di avanzamento del thread. Normalmente impostato a null TResult rappresenta il tipo del risultato restituito dal metodo doInBackground che viene automaticamente passato all‟evento onPostExecute() Esempi di Typed Class in C# La classe List <T> che ha una sola tipizzazione Esempio: List <String> myList = new List <String>(); La classe Dictionary <TKey, TValue> che ha due tipizzazioni; la chiave e il valore Dictionary <int, clsLibri> myDic = new Dictionary <int, clsLibri>(); Esempio di classe che eredita da AysncTask: public class InviaRichiestaHttp extends AsyncTask<String,Void,String> { } cioè il metodo doInBackground dovrà ricevere come parametro un ellisse (sequenza) di stringhe e dovrà restituire una stringa che verrà automaticamente passata a onPostExecute. MainActivity: Istanza della classe ed invo della richiesta L‟activity principale dovrà istanziare la classe InviaRichiestaHttp e richiamare il metodo execute che provvede ad attivare il metodo doInBackground della classe AsynkTask. Se l‟unico metodo utilizzato è execute() non serve nemmeno salvare il riferimento all‟interno di una variabile new InviaRichiestaHttp(myParam).execute(“get”, "/cercaStudente", "nomeStudente=pippo"); Passaggio dei parametri al costruttore Poiché nè il metodo execute() né l‟evento onPostExecute() non restituisce alcun risultato, il chiamante dovrà provvedere a passare alla classe InviaRichiestaHttp un oggetto (tipicamente un controllo della UI) all‟interno del quale il metodo onPostExecute possa andare a scrivere il risultato ricevuto dal server. Questo oggetto non può essere passato direttamente Né al metodo execute() in quanto questo si aspetto un vettore di oggetti tutti dello stesso tipo (tipicamente stringhe di impostazione della richiesta). Né all‟evento onPostExecute che ha una firma fissa con un unico parametro di tipo TResult La tecnica allora utilizzata è quella di passare tutti gli eventuali parametri aggiuntivi al costruttore della classe InviaRichiestaHttp che provvederà a salvarli all‟interno di appositi campi privati. In questo modo si può passare anche il context, indispensabile se il thread deve effettuare un Toast. E‟ anche possibile passare tutti i parametri al cotruttore senza più passarli ad execute. In tal caso nella dichiarazione della classe tipizzata InviaHttpRequest come primo parametro si può impostare Void e doInBackground ricevera una ellissi vuota (Void … args). pag 49 Tecnologie - Classe Quinta robertomana.it Java per Android Nota sull’indirizzo IP Se il server è sulla macchina stessa, come indirizzo ip del server occorre utilizzare l‟indirizzo della scheda fisica e NON l‟indirizzo della scheda virtuale 127.0.0.1 e nemmeno localhost che è un alias di 127.0.0.1. Entrambi nel caso dell‟emulatore puntano all‟emulatore stesso che ha un suo indirizzo IP diverso da quello della macchina fisica. Oggetti per l’invio di una richieste HTTP All‟interno della classe AsyncTask occorre poi utilizzare gli oggetti necesari per gestire l‟invio di una richiesta HTTP al server e la ricezione della relativa risposta. Questi oggetti hanno tutti la caratteristica di esporre metodi sincroni, cioè che non restituiscono il controllo fino a quando non ricevono la risposta dal server, per cui devono essere utilizzati all‟interno di AsyncTask oppure in fase di inizializzazione (splash screen). Soluzione 1 : La classe DefaultHttpClient public class InviaRichiestaHttp extends AsyncTask <String,Void,String> { private private private private View v; Context context; String url = "http://10.0.202.123:8080"; String risorsa; // costruttore public InviaRichiestaHttp (View v, Context context) this.v = v; this.context = context; } { protected String doInBackground(String ... args) { String String String String url += ris = ""; metodo = args[0]; risorsa = args[1]; parametro = args[2]; risorsa; DefaultHttpClient client = new DefaultHttpClient(); HttpResponse response = null; try{ if(metodo == "get"){ if(parametro!="") url +="?" + parametro; HttpGet request = new HttpGet(url); response = client.execute(request); // sincrono } else if(metodo == "post") { HttpPost request = new HttpPost(url); if(parametro!="") { ArrayList<BasicNameValuePair> listaParametri = new ArrayList<BasicNameValuePair>(); listaParametri.add( new BasicNameValuePair("nomeStudente", parametro)); request.setEntity(new UrlEncodedFormEntity(listaParametri)); } response = client.execute(request); } pag 50 Tecnologie - Classe Quinta robertomana.it Java per Android // lettura risposta StatusLine statusLine = response.getStatusLine(); if(statusLine.getStatusCode()==200) { InputStream is = response.getEntity().getContent(); BufferedReader reader = new BufferedReader( new InputStreamReader(is, "iso-8859-1"), 8); ris =reader.readLine(); reader.close(); } else ris = “pagina non accessibile”; } catch(Exception ex){ toast(ex.getMessage()); return “”; } catch (Exception ex) { ris = "Exception: " + ex.getMessage(); } return ris; } @Override protected void onPostExecute(String responseText){ TextView txtRisultato = (TextView) v; txtRisultato.setText(responseText); } URI e URL sono quasi sinonimi. URI è più generico in quanto è applicabile anche alla definizione delle risorse tramite namespace. L’oggetto HttpGet non ha un metodo setURL ma ha solo un metodo setURI, per cui occorre passare attraverso setURI. In alternativa si può passare la URL direttamente al costruttore. Nota Il codice precedente suppone che lo stream di risposta sia costituito da una sola riga. Nel caso in cui lo stream di risposta sia costituito da più righe, si può utilizzare un ciclo while: StringBuffer sb = new StringBuffer(""); String line=""; InputStream is = response.getEntity().getContent(); BufferedReader reader = new BufferedReader (new InputStreamReader(is)); while ((line = reader.readLine()) != null) sb.append(line); reader.close(); return sb.toString(); Invece dello StringBuffer si sarebbe potuta usare una semplice String, ma così è più elegante pag 51 Tecnologie - Classe Quinta robertomana.it Java per Android Soluzione 2 : La classe URLConnection (più semplice) Questa seconda soluzione ha il pregio di non distinguere sostanzialmente fra richieste GET e POST, entrambe inviate tramite il comando conn.connet();. In caso di parametri post è sufficiente aggiungerli alla connessione dopo averla aperta. Il meotdo conn.setDoOutput(true) abilita la scrittura di dati sull connessione e trasforma la chiamata da GET a POST protected String doInBackground(String ... args) { String ris = ""; try{ if(metodo == "get") url +="?nomeStudente=" + nome; URL _url = new URL(url); // parsing URLConnection conn = _url.openConnection(); if(metodo == "post") { // Aggiunta Parametri Post conn.setDoOutput(true); String data = URLEncoder.encode("nomeStudente", "UTF-8") + "=" + URLEncoder.encode(nome, "UTF-8"); // apre stream di output e scrive i parametri post OutputStreamWriter wr = new OutputStreamWriter (conn.getOutputStream()); wr.write(data); wr.flush(); wr.close(); } // invio della richiesta conn.connect(); // lettura risposta BufferedReader reader = new BufferedReader( new InputStreamReader(conn.getInputStream())); while((line = reader.readLine()) != null) ris += line + "\n"; reader.close(); return ris; } Notare come le istruzioni di elaborazione della risposta si siano leggermente modificate rispetto alla solizione precedente in quanto ora ricevono un oggetto URLConnection e non più un oggetto HttpResponse. Spostamento del metodo onPostExecute all’interno della classe principale Sfruttando il fatto che in JAVA al momento dell‟istanza di una classe è possiile definire nuovi metodi della classe stessa oppure redifinire metodi esistenti, il codice del metodo onPostExecute può essere scritto all‟interno della MainActivity al momento dell‟istanza della classe InviaRichiestaHttp. In questo modo si evita di dover passare al costruttore i controlli da aggiornare ed anche il context. L‟unico parametro da passare al costruttore sarà la risorsa da richiedere e gli eventuali parametri. Al metodo execute() non si passa nulla. pag 52 Tecnologie - Classe Quinta robertomana.it Java per Android Parsing di uno stream jSon Il metodo onPostExecure riceve solitamente una stringa JSON che dovrà provvedere a parsificare andando poi a visualizzare le varie informazioni sull‟interfaccia grafica. Gli oggetti fondamentali per parsificare una stringa JSON sono JSONArray nel caso in cui la stringa ricevuta faccia riferimento ad un vettore enumerativo. Esempio [ {“nome”:”pippo”, eta:16}, {“nome”:”pluto”, eta:17}, {} ] JSONObject nel caso in cui la stringa ricevuta faccia riferimento ad un oggetto. Esempio {studenti: [ {“nome”:”pippo”, eta:16}, {“nome”:”pluto”, eta:17}, {} ] } JSONArray json = new JSONArray(responseText); JSONObject json = new JSONObject(responseText); Da un JSONObjet, tramite la chiave, si possono poi estrarre i vari campi utilizzando i seguenti metodi : .getString(String key) Restituisce il valore stringa corrispondente alla chiave indicata .getInt(String key) Restituisce il valore intero corrispondente alla chiave indicata .getBoolean(String key) Restituisce il valore boolean corrispondente alla chiave indicata .getJSONArray String key) Restituisce il vettore corrispondente alla chiave indicata .getJSONObject String key) Restituisce l‟oggetto json corrispondente alla chiave indicata Da un JSONArray enumerativo si può estrarre un singolo object nel modo seguente: JSONObject studente = vect.getJSONObject(i); Nel caso di un vettore enumerativo valgono tutti i metodi precedenti, con l‟unica differenza che, anziché passare la chiave, occorre passare l‟indice numerico dell‟elemento all‟interno del vettore enumerativo: String s = vect.getString(i) Integer n = vect.getInt(i) Boolean b = vect.getBoolean(i) JSONObject o = vect.getJSONObject(i) JSONArray v = vect.getJSONArray(i) Esempio 1 Supponendo che il server invii la seguente stringa: {"citta" : ["Cune", "Genola", "Bra"]} Il parsing potrà essere eseguito nel modo seguente: protected void onPostExecute(String result){ try{ JSONObject json = new JSONObject(responseText); JSONArray citta = json.getJSONArray("citta"); for (int i = 0; i< citta.length(); i++) listaCitta.add(citta.getString(i)); adapter.notifyDataSetChanged(); } Notare adapter.notifyDataSetChanged() per il refresh del ListView, pag 53 Tecnologie - Classe Quinta robertomana.it Java per Android Gestione del Layout all‟interno dell‟Activity principale public void onCreate(Bundle savedInstanceState) { final final final final ListView lstCitta= (ListView) findViewById(R.id.lstCitta); TextView txtError = (TextView) findViewById(R.id.txtError); ArrayList<String> listaCitta = new ArrayList<String>(); ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, listaCitta); lstCitta.setAdapter(adapter); InviaRichiestaHttp http = new InviaRichiestaHttp(listCitta, txtErrore, adapter) http.execute("get", "/elencoCitta"); Esempio 2 Supponendo che il server invii la seguente stringa: {"studenti" : [\ {"nome":"pippo", "residenza":"fossano", "eta":16}, \ {"nome":"pluto", "residenza":"fossano", "eta":18}, \ {"nome":"minnie", "residenza":"genola", "eta":17} \ ]} Il parsing potrà essere eseguito nel modo seguente: protected void onPostExecute(String result){ try{ JSONObject jObject = new JSONObject(result); JSONArray studenti = jObject.getJSONArray("studenti"); for (int i = 0; i< studenti.length(); i++){ JSONObject studente = studenti.getJSONObject(i); int id = studente.getInt("id"); String nome = studente.getString("nome"); String residenza = studente.getString("residenza"); listStudenti.add (new Studente(id, nome, residenza)); } adapterStudenti.notifyDataSetChanged(); } catch (JSONException ex) { txtErrore.setText("Exception: " + ex.getMessage()); } } Impostazione del proxy sull’emulatore della scuola Settings More Mobile Networks Access Point Names T-Mobile US Proxy: 10.0.5.1 Port: 3128 A casa occorre invece impostare l‟indirizzo IP del router nella voce Server della finestra precedente. pag 54 Tecnologie - Classe Quinta robertomana.it Java per Android Services and Notification Un Service è simile ad una Activity ma non ha un layout (demone, in windows service). Le notification vengono visualizzate sulla barra n alto ogni volta che arriva una notifica Le notifiche sono pending intents. Quando faccio TAP sulla notifica parte la intent relativa. Solo i Services possono costruire notifiche, le Activity no. Lo scopo è segnalare qualcosa quando non si ha a disposizione il monitor. Il servizio, per segnalare qualcosa all‟utente, l‟unica cosa che può fare è aggiungere qualcosa alla barra delle notifiche. Se si desidera una sola istanza è consigliabile la classe intentService. Altrimenti è consigliata la classe Service. Creazione di una notifica Istanziare un Intent per il richiamo dell‟Activity di risposta alla notifica Istanziare un PendingIntent che congloba al suo interno l‟Intent precedente Istanziare una un oggetto Notification che congloba al suo interno l‟oggetto PendintIntent e che mediante il metodo notify() avvisa il NotificationManager del SO public class MyService extends IntentService { public MyService(){ super ("Mio Servizio"); // La superclasse deve assegnare un nome al servizio // Nome con cui il sistema riconosce il servizio } // Evento richiamato in corrispondenza dell'avvio del servizio protected void onHandleIntent (Intent intent) { // passo 1 : accesso al NotificationManager // s è la stringa che identifica il Notification Manager String s = Context.NOTIFICATION_SERVICE; NotificationManager mng = (NotificationManager)getSystemService(s); // passo 2 : creo un oggetto Notification che contenga gli estremi della notifica // da visualizzare sulla barra delle Notifiche int icon = R.drawable.ic_launcher; CharSequence tickerText = "Breve testo di notifica"; long tempo = System.currentTimeMillis(); Notification notification = new Notification(icon, tickerText, tempo); // L'oggetto Notification è stato deprecato nelle ultime versioni // e sostituido da un Builder decisamente più complicato. // NotificationCompat.Builder myBuilder = new NotificationCompat.Builder(this){} // passo 3 : definisco cosa fare quando l'utente selezionerà (con un tap) la notifica Context context = getApplicationContext(); CharSequence title = "My notification"; CharSequence text = "Testo completo della notifica"; // definisco l'Activity che dovrà essere eseguite sul touch Intent intent2 = new Intent(this, ActivityNotification.class); PendingIntent intent1 = PendingIntent.getActivity(this, 0, intent2, 0); // Associo azione da eseguire in corrispondenza del tap sulla notifica notification.setLatestEventInfo(context, title, text, intent2); pag 55 Tecnologie - Classe Quinta robertomana.it Java per Android // passo 4 : attivo la notifica String id = this.getString(R.string.HELLO_ID); mng.notify(Integer.parseInt(id), notification); } Avvio del servizio Il servizio precedente può essere avviato in corrispondenza dell‟arrivo di dati oppure, semplicemente, posizionando un pulsante su una Activity principale : public void btnClick(View v){ Intent intent = new Intent(MyActivity.this, MyService.class); startService(intent); } Activity di risposta alla notifica Mostra all‟utente i dati relativi alla notifica e presenta le seguenti istruzioni di accettazione della notifica (eseguite ad esempio tramite un semplice pulsante): public void btnClick(View v){ String s = Context.NOTIFICATION_SERVICE; NotificationManager mng = (NotificationManager) getSystemService(s); String id = this.getString(R.string.HELLO_ID); mng.cancel(Integer.parseInt(id)); } Utilizzo del GPS e accesso alle Google Maps Simulazione delle coordinate GPS L‟emulatore non può ovviamente leggere le coordinate GPS, che possono comunque essere impostate (a terminale acceso !) tramite Telnet oppure tramite DDMS / Emulator Control telnet localhost 5555 (porta di ascolto del dispositivo) geo fix 44.552304, 7.762786 Dall‟interno di DDMS occorre usare la virgola e non il puntino. Attenzione che le coordinate GPS vengono azzerate ogni volta che si scarica il programma nell‟emulatore. Android Manifest Nell‟android manifest occorre richiedere i diritti sia per lavorare con il GPS sia per accedere ad Internet (da scriversi direttamente all‟interno del tag Manifest). <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> <uses-permission android:name="android.permission.INTERNET"/> Activity Principale Facciamo in modo che l‟Activity principale, oltre ad ereditare da Activity, implementi anche l‟interfaccia LocationListener la quale richiede l‟implementazione di quattro metodi astratti implementabili al solito con ALT + INVIO. pag 56 Tecnologie - Classe Quinta robertomana.it Java per Android Per avviare il sistema di localizzazione occorre innanzitutto istanziare un oggetto LocationManager LocationManager lm = (LocationManager) getSystemService(LOCATION_SERVICE); Il metodo lm.isProviderEnabled verifica se è abilitata la lettura della Posizione Corrente. Nel caso in cui il GPS non sia attivato, occorre richiamare un‟apposita Activity di sistema che provvede a richiedere all‟utente se acconsente all‟utilizzo del sistema di localizzazione: Intent i = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS); startActivity(i); In caso di sistema di localizzazione attivo si può avviare la lettura delle coordinate correnti utilizzando il metodo lm.requestLocationUpdates(provider, 1000, 10, this); I parametri hanno il seguente significato: 1. Provider Il nome del provider da utilizzare per le registrazioni (indirizzo IP o GPS) 2. minTime intervallo minimo di lettura delle coordinate (in msec) 3. minDistance distanza minima in metri. L‟evento onLocationChanged viene generato ad ogni minTime in cui la distanza percorsa (rispetto all‟evento precedente) risulta > minDIstance 4. LocationListener è il puntatore all‟istanza di LocationListener all‟interno della quale si trova l‟evento onLocationChanged che dovrà essere eseguito in corrispondenza del trigger. (this) if(!(lm.isProviderEnabled(LocationManager.GPS_PROVIDER))) { Intent i = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS); startActivity(i); } else{ Criteria criteria = new Criteria(); String provider = lm.getBestProvider(criteria, false); // String provider = LocationManager.GPS_PROVIDER; // NETWORK_PROVIDER lm.requestLocationUpdates(provider, 1000, 5, this); } L‟oggetto Location corrente viene restituito all‟interno dell‟evento onLocationChanged richiamato nell‟esempio ogni secondo, ma solo se le coodinate sono cambiate per un valore superiore ai 5 m public void onLocationChanged(Location loc){ Double lat = loc.getLatitude(); Double lng = loc.getLongitude(); Toast.makeText(this, "lat, lng", Toast.LENGTH_LONG).show(); } Visualizzazione del percorso sulle Google Maps Per la visualizzazione di un percorso sulle google maps si può utilizzare il seguente codice, passando al servizio google soltanto il punto di partenza ed il punto di arrivo String coord1 = list.get(0); String coord2 = list.get(list.size() - 1); String addr = "http:/maps.google.com/maps?saddr=" + coord1 + "&daddr=" + coord2; // saddr=44.168292,7.289257&daddr=44.568292,7.98925 Uri uri = Uri.parse(addr); Intent mapIntent=new Intent(Intent.ACTION_VIEW, uri); if(mapIntent.resolveActivity(getPackageManager())!=null) startActivity(mapIntent); Per quanto concerne le Mapper Statiche di Google possono essere usate anche senza registrazione. Altrimenti occorre registrarsi al servizio di google e richiedere una key per l‟utilizzo delle Google Maps pag 57