Java per Android -...
Transcript of Java per Android -...
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 1
Java per Android
Rev. 5.3 del 07/03/2020
Android SDK …………………………………………………………………..………………..……… 2 SDK Manager ……………..………………………………………..………………………..………… 4 AVD Manager ……………..………………………………………..………………………..………… 5 Activity e Layout ……………………………………………..……………………….…….………… 7 Il file AndroidManifest.xml .…….....……………………………………………...……....………… 7 Il file strings.xml ..……………….....……………………………………………………...………… 8 La programmazione, Widget e View, Gestione dell ID, il file R …..……………………….……… 8 La gestione degli eventi …..………………………………………………………………………… 10 Concetto di Context ..………………………………………………………….….……….………… 12 Le finestre di popup : i Toast …………………………………………………….………………….. 13 AlertDialog e InputDialog …………………………..………………………….…………………….. 14
Utilizzo dei principali controlli .…………….……………………………………………....………… 16 Il widget EditText ..………………………………………………………………………....………… 16 Check Box, Radio Button ……………………………………….………………….……………….. 17 Il widget ImageView ..………………………………………………………..…….……...………… 17 Creazione dinamica dei controlli ……………………………………………………….………….. 19 Approfondimenti sulla Gestione dell’ID …………………………………………….…………….. 19 List e ArrayList ……………………………………………………………..………….……………….. 20 La scalatura delle immagini : PX vs DP …………….………………..……….………………….. 22 Principali Proprietà Comuni ……………………..………………………………….……………….. 24 I Layout ……………………..………………………….…………………………….……………….. 25 Accesso ai controlli di un layout ………………………….……….………………………..…..….. 30 La gestione degli sfondi tramite la classe ColorDrawable ….……………….………………….. 31 Gestione dello schermo: dimensioni e rotazione …………………………….………………….. 32
Intent ……………………………………………………….………………………………………….. 33 Utilizzo degli Intent impliciti …………..………………….………………………………………….. 34 Richiamo di una activity mediante un intent esplicito …..………………………………………….. 36 Ciclo di vita di una Activity …………………………………………………………….……………… 37 Passaggio di parametri ad una Activity …………………………………………..……………….. 38
Menù ………………………………………………………………………………….………………. 41 Device File Explorer ………………………………………………………………………………….. 46 Accesso al File System …………………………………………………………..………………….. 47 ListView e Adapter ………………………………..…………………………………………………. 49 Spinner e GridView ………………………………..…………………………………………………. 52 Creazione di un Adapter personalizzato ……………………..……………………………………. 53 Shared Preferences ……………………….…………………………..………………….………….. 56 La gestione dei thread : Il timer …………………………………………………..………………. 56
Basic Activity ……………………..……………………………………………………………………. 58 Drawer Activity …………………..……………………………………………………………………. 60
La gestione dei permessi …………………………………….…….……………………………….. 62 Accesso ad un web service HTTP …………………………..…….……………………………….. 63 Gestione di uno stream Json ……………………..…….……………………………………..…….. 68 Richiesta di una immagine ………………….…………….……………………………...………….. 70 Gestione di uno stream XML ……………………….…………………………………...………….. 71 Accesso a SQLite ………………………………………….……………………………...………….. 75 Utilizzo sei Sensori …………..…….………………………………………..……………………….. 78 Services And Notification ………………………..…….…………………………………….……….. 80
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 2
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 on-line attraverso la quale gli utenti possono installare applicazioni aggiuntive sui propri dispositivi. Su Google Play sono disponibili sia applicazioni gratuite che a pagamento.
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 3
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 Cupcake (30 aprile 2009 – API Level 3) versione 1.6 Donut (30 aprile 2009 – API Level 4) versione 2.0 Eclair (26 ottobre 2009 – API Level 5) versione 2.2 Froyo (20 maggio 2010 – API Level 8) versione 2.3 Gingerbread (6 dicembre 2010 – API Level 9) versione 3.0 Honeycomb (22 febbraio 2011 – API Level 11) versione 4.0 Ice Cream Sandwich (19 ottobre 2011 – API Level 14) versione 4.1 Jelly Bean (9 luglio 2012 – API Level 16) versione 4.2 Jelly Bean (13 novembre 2012 – API Level 17) versione 4.3 Jelly Bean (24 luglio 2013 – API Level 18) versione 4.4 Kit Kat n seguito ad un accordo con la Nestlé (31 ottobre 2013 – API Level 19) versione 5.0 Lollipop lanciata il 3 novembre 2014 (API Level 21) versione 5.1 Lollipop lanciata il 9 marzo 2015 (API Level 22) versione 6.0 Marshmallow lanciata il 5 ottobre 2015 (API Level 23) versione 7.0 Nougat lanciata il 22 agosto 2016 (API Level 24) versione 7.1 Nougat lanciata il 20 ottobre 2016 (API Level 25) versione 8.0 Oreo lanciata il 21 agosto 2017 (API Level 26) versione 8.1 Oreo lanciata il 25 ottobre 2017 (API Level 27) versione 9.0 Pie lanciata il 6 agosto 2018 (API Level 28)
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 SDK ufficiale denominato ADT (Android Development Tools), che contiene tutte le librerie necessarie allo sviluppo.
Versioni diverse dell‟SDK possono convivere sulla stessa macchina, 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
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 4
L‟APK viene inviato 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 attualmente alla versione 1.8
Occorre quindi scaricare ed installare Android SDK scaricabile da : http://developer.android.com/sdk/index.html
Nelle ultime versioni di Android Studio l’intera SDK viene installata insieme ad Android Studio all‟interno di un percorso definito in fase di installazione. L‟SDK è costituita da 2 applicazioni principali:
Android SDK Manager (gestore dei pacchetti)
Android AVD Manager (gestore degli emulatori)
SDK Manager e AVD manager sono accessibile dall‟interno di Android Studio dal menù Tools.
Nelle installazioni più vecchie per lanciare i due pacchetti occorreva andare nella cartella TOOLS di Android SDK
ed eseguire il file android.bat. Successivamente all’interno della cartella principale erano stati aggiunti due
comandi batch uno per ciascuna delle 2 utility.
SDK Manager
SDK Manager è un gestore di pacchetti che consente di stabilire quali versioni di sviluppo installare / disinstallare sulla macchina.
Selezionando Show Package Details si vedono i dettagli di ciascuna platform. Per ogni versione che si intende utilizzare occorre installare :
SDK Platform Eventualmente:
Google APIs
SDK Build-tools
Le altre voci sono principalmente emulatori che devono essere installati SOLO SE si intende utilizzarli. AVD Manager – Creazione di un device
Per poter provare una applicazione, occorre creare un emulatore di dispositivo, cioè un AVD (Android Virtual Device). Menù Tools / AVD Manager / pulsantino in basso a sinistra Create Virtual Device
Come dispositivo di prova va benissimo Nexus 4 4,7” che non è troppo pesante come utilizzo di RAM oppure Pixel 2
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 5
Riguardo alla RAM, CPU, etc, confermare i valori proposti, Selezionare quindi il System Image da installare (es API level 29). Per avviarlo 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.
Un ottimo emulatore di terze parti è scaricabile da: www.genymotion.com/fun-zone Scaricabile gratuitamente previa registrazione.
Creazione di applicazioni tramite Android Studio
Una applicazione Android è identificata da un package univoco. packege è sinonimo di namespace. IntelliJ Idea definisce automaticamente il package come package="com.example.appName"
Nel nome del package ci deve essere almeno un puntino. Il primo esercizio
Start a new Android Studio Project
Imposatre il path di lavoro che verrà riproposto per tutti gli esercizi successivi. Assegnare un Application Name univoco. Se non si modifica il path nella parte inferiore, quando si assegna l‟Application Name Android Studio riporta lo stesso nome anche nel text box inferiore
Nella 2° finestra viene richiesto soltanto l‟SDK minimo. Quello proposto (18) va benissimo
Nella 3: finestra scegliere Empty Activity
Nella 4: finestra vengono proposti il nome dell‟Activity e quello del layout Struttura di una applicazione Android
Una applicazione Android è articolata nelle seguenti sottocartelle:
src - sorgenti java
res - risorse (layout, values, drawable, animazioni)
gen – autogenerati (R.java)
bin – compilati (APK e DEX)
Nel combo box della finestra di progetto è possibile scegliere fra diverse voci.
Android visualizza soltanto i files relatvi allo sviluppo
Project visualizza invece tutti i file relativi al progetto, compresa l‟apk finale
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 6
Componenti di una applicazione Android
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 Activity
Un'activity rappesenta una classe che consente di interagire con gli elementi di un layout.
Una Empty Activity è una classe che estende (cioè eredita da) AppCompatActivity, della quale occorre ridefinire il metodo onCreate. Una Activity vuota presenta inizialmente il seguente codice:
public class MyActivity extends AppCompatActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
Il metodo onCreate viene richiamato al momento dell‟avvio dell‟Activity.
savedInstanceState è lo stato dell‟activity salvato su disco in un‟area di sistema. Lo stato viene
automaticamente iniettato a onCreate() al momento del lancio
Per prima cosa onCreate() deve richiamare il metodo onCreate() della superClasse
setContentView(R.layout.main) è un metodo dell‟Activity che consente di associare
all‟Activity un Layout contenuto nel file main.xml della cartella res/layout del progetto corrente. Una Activity può gestire più Layout. In ogni momento ci sarà un unico layout attivo
Layout
I Layout sono memorizzati nella cartella res / layout / main.xml (il nome del layout deve essere interamente scritto in minuscolo)
Il designer del layout è 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.
All‟interno del layout occorre posizonare un on oggetto Layout che per default è un ContraintLayout. Esistono anche altri tipi di layout (LinearLayout, TableLayout, FrameLayout). L’oggetto Layout è molto simile a quelli usati in Java all’interno dei Panels
All‟interno del Layout si possono posizionare i Widget (controlli) di interazione con l‟utente. Si può indifferentemente scrivere il codice xml oppure trascinare gli oggetto sul designer.
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 7
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height=" match_parent" <TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World"
/>
</LinearLayout>
Il widget TextView è equivalente alla Label di C#.
Il valore match_parent significa ricoprire l‟intera area del genitore (cioè a tutto schermo)
Il valore wrap_content adatta le dimensioni del widget alle dimensioni del contenuto.
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#. Le principali informazioni contenute sono:
L’elenco delle Activity utilizzate dall‟applicazione
I permessi necessari per il funzionamento dell‟applicazione
La versione minima di sdk su cui l‟applicazione può essere eseguita
il nome del package
icon = icona da utilizzare per rappresentare l‟applicazione sul dispositivo
label = Nome identificativo dell‟applicazione
theme = tema utilizzato dalla applicazione
eventuali intent impliciti che l‟applicazione è in grado di elaborare
package="com.example.roberto.app01_eventi"
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
Per ogni Activity vengono memorizzati il name e le caratteristiche dell‟Activity :
- I valori MAIN e LAUNCHER indicano che l‟ Activity 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.
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 8
Il file res / values / strings.xml
Questo file consente di definire delle costanti di parametrizzazione dell‟applicazione, nel senso che l‟applicazione può usare queste stringhe in qualsiasi segmento di codice. Esempio di file strings.xml
<resources>
<string name="app_name">app01_eventi</string>
<string name="titolo">Home Page</string>
</resources>
La stringa app_name consente di definire il nome visualizzato sulla barra superiore della app
Utilizzo delle strings all’interno del layout
<TextView android:text = "@string/titolo" />
Utilizzo delle strings all’interno del codice dell’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));
gradle
gradle è un compattatore mirato al packaging dell‟apk finale, simile ad altri strumenti di build automation come Ant e Maven, ma per certi versi più moderno e flessibile. E‟ un progetto multipiattaforma e può essere scaricato dal sito di riferimento www.gradle.org. Trae vantaggio principalmente dall‟utilizzo di configurazioni svolte in un linguaggio dichiarativo: Groovy, un linguaggio di scripting che nasce in seno alla tecnologia Java.
gradle viene scaricato insieme ad Android Studio nella stessa versione di Android Studio (3.4.0 a maggio 2019). E‟ possibile aggiornare gradle a versioni più recenti anche senza aggiornare Android Studio, però la tecnica migliore è quella di aggiornare di tanto in tanto Android Studio alla versione più recente con automatico aggiornamento anche di gradle
La Programmazione
Note
Per visualizzare il Project Explorer -> Menù View / Tool Windows / Project
Prima dell‟onCreate possono essere dichiarate delle Property, ma non è consentito eseguire delle istruzioni (come ad esempio findViewById() )
Il nome delle risorse (layout, immagini, etc.) deve essere INTERAMENTE scritto in minuscolo.
Per terminare una Activity si può utilizzare il metodo finish();
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
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 9
Widget e View
Tutti i widget ereditano dalla superclasse View che è un astrazione di ciò che l'utente vedrà sullo
schermo. Per creare un widget 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 deve iniziare con il prefisso @+id/
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
final Button btn =(Button) findViewById(R.id.button1);
R.id.button1 è in realtà una costante numerica di tipo int
rappresentata solitamente in formato esadecimale
creata dal compilatore al momento della compilazione
salvata all‟interno del cosiddetto file R, che contiene non solo gli ID dei controlli, ma anche altri oggetti, ad esempio i puntatori alle immagini o le costanti definite all‟interno del file strings
R.id.button1 significa vai nella classe R, accedi all‟oggetto id e leggi il campo button1
Il metodo findViewById accede al widget del layout avente l‟ID numerico indicato.
Consente però di accedere soltanto ai controlli posizionati sul xmlLayout attualmente in uso.
Siccome il file R è unico a livello dell‟intera applicazione, nell‟applicazione non possono coesistere due widget aventi lo stesso name, anche appartenenti a Layout differenti
final utilizzato davanti al nome di una variabile significa sostanzialmente che la variabile non può
essere riassegnata. All‟interno del metodo onCreate devono essere dichiarate final tutti quei
riferimenti per i quali si intende gestire degli eventi o che sono utilizzati all‟interno degli eventi.
Infatti, quando la procedura onCreate termina, tutte le variabili locali vengono rimosse dallo stack, per cui non sono più accessibili al momento dell‟evento. Dichiarandole invece final, le variabili vengono
memorizzata all‟interno di uno string pool che rimane allocato anche al termine della procedura.
Visualizzazione della classe R
Il file R può essere aperto tramite il percorso indicato in figura : Project Files / app / app / build_generated / source / r_debug / com.example…. / R.java
Oppure digitando shift shift che apre una finestra di search in cui scrivere R.java e
selezionare l‟ultimo file visualizzato (ci sono più di 20 files R.java !)
E‟ una classe java autogenerata dal framework che serve a gestire tutte le risorse memorizzate dentro res (layout, etc).
All‟interno del file R.java il compilatore memorizza un puntatore numerico esadecimale per ogni elemento contenuto all‟interno del Layout.
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 10
La Gestione degli eventi
Gli eventi sono gestiti tramite appositi oggetti detti listener.
1° soluzione
View.OnClickListener myListener = new View.OnClickListener() {
@Override
public void onClick(View v) { }
});
Nel momento in cui si digita View.OnClickListener myListener = new V
dopo un attimo compare l’intelliSense. Scegliendo la prima voce viene direttamente creato l’intero listener compreso il metodo onClick().
La classe View.OnClickListener è una classe astratta e non può essere direttamente istanziata
senza prima ridefinirne i metodi astratti. L'unico metodo astratto dichiarato all‟interno della classe è il metodo onClick che DEVE essere ridefinito all‟interno del listener.
Il metodo onClick riceve come unico parametro la View (il controllo) che ha generato l‟evento.
public void onClick(View v) {
Button btn =(Button) v;
if (btn.getText()=="Premi") {
btn.setText("Premuto");
btn.setBackgroundColor(Color.RED);
}
else {
btn.setText("Premi");
btn.setBackgroundColor(Color.rgb(214,215,215));
}
}
Per associare un listener di evento ad un controllo occorre accedere al controlo tramite findViewById e poi utilizare il metodo set seguito dal nome della classe di ascolto: setOnClickListener :
Button btn1=(Button) findViewById(R.id.btnPremi);
btn1.setOnClickListener(myListener);
Note 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)
Un listener di evento può anche essere richiamato esplicitamente da codice, passandogli come parametro il puntatore al controllo opportuno (ad es in modo ricorsivo per reiniziare la procedura da capo).
2° soluzione
L‟associazione tra l‟oggetto ed il listener può essere eseguita anche sulla stessa riga nel modo seguente:
Button btn2=(Button) findViewById(R.id.btn2);
btn2.setOnClickListener(new View.OnClickListener() { } );
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 11
3° Soluzione Associazione di una procedura di evento tramite il designer
Nel caso dell‟evento click, è sufficiente, fuori dal metodo onCreate(), definire 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 btn3_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
Associazione di uno stesso evento a più controlli
La prima e la terza soluzione hanno il vantaggio di poter associare lo stesso listener a più controlli. All‟interno del listener si può utilizzare uno switch per individuare il controllo che ha scatenato l‟evento:
View.OnClickListener myListener = new View.OnClickListener() {
public void onClick(View v) {
switch(v.getId()){
case R.id.btn4:
txt.setText("E' stato premuto il pulsante 4");
break;
case R.id.btn5:
txt.setText("E' stato premuto il pulsante 5");
break;
}
}
};
btn1.setOnClickListener(myListener);
btn2.setOnClickListener(myListener);
4° Soluzione Implementazione dell’interfaccia onClickListener
Questa 4° soluzione rappresenta una semplificazione del caso 1 e rappresenta probabilmente la soluzione migliore per la gestione degli eventi in JAVA. Anziché definire all‟interno dell‟Activity corrente
una nuova classe View.OnClickListener, è possible fare in modo che la classe corrente
implementi l‟interfaccia View.OnClickListener. In questo modo è possibile ridefinire l‟evento onClick
direttamente all‟interno della classe corrente:
public class MainActivity extends AppCompatActivity
implements View.OnClickListener {
Gestore unico per tutti gli eventi click
public void onClick(View v){
switch(v.getId()){}
}
Button btn1 = (Button)findViewById(R.id.btn1);
btn1.setOnClickListener(this);
}
In caso di definizione di più listener, il successive maschera il precedente
Una Activity può implementare contemporaneamente PIU’ INTERFACCE separate da una virgola.
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 12
Rilascio di un handler di evento
v.setEnabled(false);
In alternative si potrebbe scrivere :
v.setOnClickListener(null);
Con questa soluzione però l‟evento non viene generato, ma graficamente il click “sembra” attivo.
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
Generazione di numeri casuali in Java
Random rnd = new Random();
int n =rnd.nextInt(B-A+1) + A;
nextInt usato senza parametri genera unnumero compreso tra 0 e MAX_INT
Il tipo di un puntatore: instanceOf
InstanceOf verifica se un puntatore sta puntando ad un certo tipo di oggetto. E‟ l‟equivalente di IS in C#
if(v instanceof Button) { }
In alternativa si potrebbe utilizzare : String type = v.getClass().getName();
if(type.equalsIgnoreCase("android.widget.Button"))
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.
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);
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 13
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 // istanza corrente
MyActivity.this; // 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(); // equivalente a this
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 ok" in seguito a qualche operazione compiuta dall'utente .
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!!", Toast.LENGTH_SHORT);
t.show();
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, ”ciao”, 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
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 14
Utilizzo del toast per la visualizzazione di una immagine
Un toast non può visualizzare contemporaneamente Testo e Immagine. O il testo, o l‟immagine. Nel caso si voglia visualizzare 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();
AlertDialog
Widget utilizzato ticamente per richiedere una richiesta di conferma da parte dell'utente per una qualche operazione. Per esempio un'applicazione che richiede una connessione ad Internet, potrebbe mostrare un'AlertDialog all'utente nel caso in cui il dispositivo non si riesca a collegarsi ad Internet.
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("AlertDialog");
builder.setMessage("E' stato premuto Pulsante 3");
builder.setIcon(R.mipmap. ic_launcher);
Aggiunta dei pulsanti di dialogo
Le dialog vengono automaticamente chiuse in corrispondenza del click su uno dei pulsanti di dialogo. Se non vengono inseriti pulsanti di dialogo, per chiudere la AlertDialog occorre clickare nell‟area dell‟Activity. I pulsanti di dialogo utilizzabili sono 3 (tutti con posizione fissa)
PositiveButton posizionato nella parte destra della finestra
NegativeButton posizionato nella parte centrale della finestra
NeutralButton posizionato nella parte sinistra della finestra
builder.setPositiveButton("Si", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
lbl.setText("cliccato il tasto SI");
dialog.dismiss(); // oppure dialog.cancel();
}
});
builder.setNegativeButton("No", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
lbl.setText("cliccato il tasto NO");
}
});
builder.setNeutralButton("Annulla",new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
lbl.setText("cliccato il tasto Annulla");
}
});
Il primo parametro indica il testo da visualizzare all‟interno del pulsante
Il secondo parametro indica un oggetto Listener di tipo DialogInterface.OnClickListener
Anche nel caso del DialogInterface.OnClickListener occorre comunque ridefinire il metodo OnClick ed inserire all‟interno il codice relativo al comportamento che vogliamo dare al bottone.
Il metodo dialog.dismiss() chiude la finestra corrente. La finestra si chiude automaticamente in
corrispondenza del click su uno dei buttons. dismiss() consente la chiusura forzata da codice.
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 15
builder.setCancelable(true/false); il false ha l'effetto di disabilitare il tasto back del
telefono e dunque l'utente dovrà necessariamente cliccare su uno dei bottoni per chiudere la Dialog che quindi diventa modale.
al termine: AlertDialog dlg = builder.create();
dlg.show();
// oppure builder.show();
Utilizzo di un unico Listener
La classe DialogInterface.OnClickListener utilizzata negli esempi precedenti dispone di un unico evento onClick il quale si aspetta due parametri:
1. un oggetto di tipo AlertDialog che indica sostanzialmente il sender (quale dialog ha generato l'evento. Attenzione AlertDialog, non Builder. Occorre pertanto utilizzare il builder.create
2. un id che identifica il bottone premuto all‟interno della dialog
Questi due parametri consentono di utilizzare un unico listener per tutte le AlertDialog dell‟applicazione, scritto esternamente rispetto alle Dialog medesime. Il codice interno dovrà semplicemente eseguire 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 premuto. DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener(){
public void onClick(DialogInterface dialog, int id) {
String text = "";
if (dialog == dlg2) {
switch (id) {
case DialogInterface.BUTTON_POSITIVE: // -1
text = "OK";
case DialogInterface.BUTTON_NEGATIVE: // -2
text = "No";
case DialogInterface.BUTTON_NEUTRAL: // -3
text = "Cancel";
}
Toast.makeText(this, text, Toast.LENGTH_LONG).show();
}
}
}
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);
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 16
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 pulsante memorizzarà il
valore inserito all‟interno di una apposita variabile globale. Il click di finestra3 richiamrerà una funzione
elabora() che andrà ad elaborare i tre valori inseriti.
Utilizzo dei principali Controlli
La casella di testo : il widget PlainText (EditText)
La proprietà inputType consente di definire il comportamento della Casella di Testo :
inputType = "none" qualsiasi carattere
inputType = "text" solo caratteri testuali, compresa la punteggiatura. No che accentati
inputType = "number" solo numeri. In corrispondenza del click si apre il tasterino numerico
inputType = "password" visualizza asterischi
hint = "placeholder" suggerimento iniziale. Simile al placeholder html
android:backgroundTint="@android:color/black" // Colore della linea di sfondo
Nelle versioni più recenti 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.
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 Nel caso di campi URL invia la richiesta HTTP al server
IME_ACTION_NEXT Passa al campo successivo
IME_ACTION_SEND Invia il testo ad un servizio specifico
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 17
Per cambiare il colore del cursore (how to change cursor color in edittext in android studio) occorre creare all‟interno della cartella drawable un file myCursor.xml contenente il seguente codice:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >
<size android:width="1dp" />
<solid android:color="#000000"/>
</shape>
Dopo di che all‟interno del layout utilizzare la segeunte riga: android:textCursorDrawable="@drawable/black_cursor"
Check Box e Radio Button
A fianco di ogni Check Box / Radio Button viene sempre visualizzato un TextView contenente un testo associato al controllo. Non esiste il concetto di value I Radio Button sono mutuamente esclusivi soltanto se inseriti all‟interno di un medesimo RadioGroup (avente ad esempio id = optGroup). Non sono mutuamente esclusivi i Radio Button inseriti
direttamente all‟interno di un Layout o altro. Esempi di utilizzo:
if (myOpt.isChecked()) {
myOpt.setText(“checked”);
myOpt.setChecked(false); }
int id_elemSelez = optGroup.getCheckedRadioButtonId();
RadioButton opt = (RadioButton) findViewById(id);
RadioButton opt = (RadioButton) optGroup.getChildAt(2); // 3° rdb
for(int i=0; i< chkGroup.getChildCount(); i++) {
CheckBox chk = (CheckBox) chkGroup.getChildAt(i);
if (chk.isChecked()) aus += chk.getText() + ", "; }
In corrispondeza del click si può utilizzare il seguente evento :
optBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
public void onCheckedChanged(CompoundButton v, boolean isChecked) {
if (isChecked) { }
}
La gestione delle immagini : il widget ImageView
Consente di visualizzare un'immagine memorizzata all‟interno della cartella res / mipmap
Nel caso di immagini di dimensioni superiori alla risoluzione del controllo, Android effettua un resize automatico in modo da renderla completamente visibile.
Aggiungere una immagine nella cartella xhdpi
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="@mipmap/desert" // senza estensione !!
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 18
Impostazione dell‟immagine da codice : final ImageView img =(ImageView) findViewById(R.id.img1);
img.setImageResource(R.mipmap.desert); // senza estensione !!
img.setImageBitmap(bitmap); dove bitmap può essere letto da file o ricevuto da un server
il metodo getResources().getIdentifier()
Consente di eseguire un accesso diretto a tutte le risorse identificate tramite il file R, come ad esempio
Le risorse memorizzata all‟interno della cartella res (es R.mipmap.desert)
I controlli identificati tramite un ID statico (es R.id.btn1)
Sia R.drawable.desert che R.id.btn1 sono costanti esadecimali, per cui NON possono essere
utilizzate all‟interno di un ciclo per accedere ad un elenco di oggetti con nomi simili (come ad esempio img1, img2, img,3, btn1, btn2, btn3, etc).
Il metodo this.getResources() consente di accedere a tutte le risorse della app. Partendo
dall‟insieme delle risorse, il metodo getIdentifier() consente di ottenere l‟ID esadecimale di una
singola risorsa a partire dalla stringa identificativa (ad esempio “btn1” oppure “img25”
Esempio 1: accesso diretto all’identificativo di una immagine
Resources res = this.getResources()
int id1 =res.getIdentifier(“desert”, "mipmap", this.getPackageName());
int id2 =res.getIdentifier(“btn”+i, "id", this.getPackageName());
Restituisce l‟ID numerico dell‟oggetto cercato, oppure 0 se non lo trova. Esempio 2: ciclo di caricamento di immagini aventi nomi consecutivi
getIdentifier() può essere utilizzato ad esempio par accedere a tutte le immagini contenute
all‟nterno della cartella mipmap e caricare i relativi ID all‟interno di un ArrayList:
ArrayList<Integer> listaImmagini = new ArrayList<Integer>();
int id;
int i=1;
do {
id = getResources().getIdentifier("img" + i, "mipmap", this.getPackageName());
if(id!=0) listaImmagini.add(id);
i++;
} while(id!=0);
Nota
In realtà le costanti esadecimali di identificazione delle immagini sono numeri positivi crescenti (con incremento di 1) sulla base delle immagini ordinate in ordine alfabetico. Per cui in realtà è possibile accedere in sequenza alle varie immagini nel modo seguente:
img.setImageResource(R.drawable.aaa + i);
dove aaa è il nome della prima immagine, mentre R.drawable.aaa è il suo codice esadecimale.
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 19
Creazione Dinamica dei controlli
Il layout di una Activity può essere creato dinamicamente da codice anche senza l‟ausilio di un file xml.
LinearLayout layout = new LinearLayout(this);
setContentView(layout);
int id=1;
Button btn = new Button (this);
btn.setText("Bottone 1");
btn.setId(id);
layout.addView(btn);
id++;
btn = new Button (this);
btn.setText("Bottone 2");
btn.setId(id);
layout.addView(btn);
Note sulla gestione dell’Id
L‟Id è semplicemente un numero intero esadecimale 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 layout
android:id=”@+id/button1”
Per i controlli definiti staticamente, il valore dell‟id deve essere univoco sull‟intera applicazione. Il compilatore trasforma questa stringa alfanumerica in un intero positivo esadecimale univoco.
Per accedere da codice al controllo si utilizza il metodo findViewById : Button btn =(Button) findViewById(R.id.button1);
Il metodo v.getId() esegue l‟operazione invesa di findViewById(), cioè restituisce la costante
esadecimale del controllo a partire dal riferimento
if( v.getId() == R.id.button1)
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. Lo 0 ha il significato di risorsa non trovata (getIdentifier() restituisce 0 se non trova la risorsa)
setId() si aspetta un oggetto ID, mentre getId() restituisce un oggetto Id che è sostanzialmente un intero unsigned. La conseguenza a livello operativo è che, insieme all‟ID, nel caso di assegnazioni, letture, confronti, non è MAI possibile utilizzare un numero diretto ma occorre SEMPRE passare attraverso una variabile:
int aus = 1;
btn1.setId(aus);
int id = v.getId();
if(id == aus) .......
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 20
In alternativa si può definire all‟interno della cartella res/values un file denominato ids.xml <?xml version="1.0" encoding="utf-8"?>
<resources>
<item type="id" name="btn1" />
<item type="id" name="btn2" />
</resources>
btn.setId(R.id.btn1);
L‟API 17 ha introdotto il metodo View.generateViewId() che consente di generare ID univoci.
Riferimento ad un controllo creato dinamicamente
Dopo aver creato il controllo e aggiunto al layout, l‟accesso può essere eseguito nel solito modo: Button btn =(Button) findViewById(id);
Come è possible consentire id non univoci ?
Il fatto che l‟ID non debba necessariamente essere univoco si spiega con il fatto che, quando si ricerca un controllo tramite findViewById, esso esegue una ricerca sequenziale restituendo il primo controllo
avente l‟ID indicato. Poiché nella lista dei controlli quelli creati staticamente a Design Time si trovano prima di quelli creati dinamicamente, findViewById restituirà sempre il controllo statico.
List<Object> e ArrayList<Object>
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 dell’ArrayList (<String> è il default e può anche essere omesso)
ArrayList<String> list = new ArrayList<String>();
// Popolamento dell’ArrayList
list.add("Fossano");
list.add(pos, "Saluzzo"); // int pos = 0;
list.addAll(list2);
String[] vect = new String[] { "Cuneo", "Alba", "Genola", "Bra" };
list.addAll(Arrays.asList(vect));
// Accesso agli elementi dell’ArrayList
for (int i = 0; i<list.size(); i++)
msg += list.get(i) + "\r\n";
if(list.contains("Fossano"));
// Scansione dell’ArrayList tramite ciclo for each
for(String s : list){
msg += s + "\r\n";
// Modifica di un elemento esistente all’interno dell’ArrayList
list.set(i, "Torino")
Il metodo set è utilizzabile anche nel caso di ArrayList<Object>
list.set(i, studente)
// Cancellazione di elementi
list.remove(pos); list.clear();
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 21
Ordinamento di un arrayList
Collections.sort(arrayList);
Nel caso di ArrayList di Oggetti, ocorre definire all‟interno della classe un metodo che definisca quale campo utilizzare per i confronti e come eseguire i confronti (crescente o decrescente).
1° soluzione : L’interfaccia Comparable
Sulla classe di appartenenza degli oggetti si può implementare l‟interfaccia Comparable con il metodo compareTo() in cui si definisce il campo su cui eseguire il confronto. Tenere presente che in java una classe può implementare più interfacce separate da virgola.
public class Student implements Comparable<Student> {
private String studentName;
private int studentAge;
public Student(String studentName, int studentAge) {
this.studentName = studentName;
this.studentAge = studentAge;
}
@Override
public int compareTo(Student student) {
int compareAge=student.getStudentAge();
/* For Ascending order */
return this.getStudentAge() - compareAge;
/* For Descending order */
//return compareAge-this.getStudentAge();
}
Notare che compareTo restituisce sempre un int che ha il seguente significato:
0 : l‟elemento corrente ha lo stesso valore dell‟elemento confrontato >0 : l‟elemento corrente è maggiore (numericamente o alfabeticamente) dell‟elemento confrontato, che
dunque viene fatto salire <0 : l‟elemento corrente è minore dell‟elemento confrontato, che dunque viene fatto scendere
2° soluzione : La classe Comparator<>
Una seconda soluzione consiste nel dotare la classe di appartenenza degli oggetti della classe Comparator<Studente> la quale utilizza un metodo compare() per eseguire i confronti:
public static Comparator<Studente> confronta = new Comparator<Studente>() {
public int compare(Studente s1, Studente s2) {
String name1 = s1.getNome().toUpperCase();
String name2 = s2.getNome().toUpperCase();
//ascending order
return name1.compareTo(name2);
//descending order
//return name2.compareTo(name1);
}
};
Il chiamante dovrà indicare la classe di confronto all‟interno del secondo parametro. Collections.sort(arrayList, Persona.confronta);
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 22
La scalatura delle immagini PX vs DP
L‟unità px rappresenta I pixel reali dello schermo. Spesso però gli schermi degli smartphone hanno densità differenti, deve per densità si intende il PPI, numero di pixel per pollice lineare.
Due smartphone con uno stesso schermo da 5” possono evere risoluzione diversa, ad esempio il secondo pari al doppio del primo. Questo significa che il secondo smartphone ha una densità di pixel doppia rispetto al primo, cioè i pixel sono molto più vicini tra loro. Cioè il secondo smartphone ha un PPI doppio e quindi può visualizzare il doppio delle cose, ciacuna con dimensioni dimezzate. Un oggetto con larghezza 200px si vedrebbe sempre più piccolo su smartphone aventi PPI crescenti.
Quando si progetta il layout di una app si tende ad assegnare ai vari oggetti una dimensione fissa, indipendente dal PPI del dispositivo. Questo risultato lo si ottiene con l‟introduzione di una nuova unità di misura: il DP Density-independent Pixels. Si supponga di voler visualizzare una immagine con dimensione ½ pollice x ½ pollice.
Su un dipositivo con PPI = 120 l‟immagine dovrà estendersi per 60 x 60 pixel
Su un dipositivo con PPI = 240 l‟immagine dovrà estendersi per 120 x 120 pixel, cioè dovrà essere il doppio rispetto all‟immagine precedente.
mdpi
hdpi
xhdpi
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 23
Density Bucket
Poiché è impensabile memorizzare immagini diverse per ogni possibile PPI del dispositivo, sono stati definiti degli appositi Density Bucket (secchielli di densità):
Density Bucket name DPI from…. to…… density
ldpi low 120 dpi 100 140 0,75
mdpi medium 160 dpi 140 180 1
hdpi height 240 dpi 180 280 1,5
xhdpi extra height 320 dpi 280 360 2
xxhdpi extra extra height 480 dpi 400 3
Per ognuno di questi Density Bucket è stata predisposta una apposita cartella all‟interno delle risorse. Per poter gestire l‟imagine precedente con dimensioni fisse di ½ pollice x ½ pollice, occorre creare cinque immagini con grandezza differente salvandole nelle apposite cartelle. Le varie immagini dovranno avere le dimensioni indicate nel seguente prospetto:
A seconda del PPI del dispositivo la app visualizzerà l‟immagine opportuna. E’ anche possibile memorizzare una sola immagine (in genere quella con risoluzione maggiore). In caso di assenza delle immagini con risoluzione diversa, la app provvederà automaticamente a riscalare l‟immagine espressa in dp in modo da ottere le dimensioni opportune. Funzioni per la conversione da px a dp
Anche impostando tutte le dimensioni in DP, molte funzioni relative al layout restituiscono o si aspettano le dimensioni degli oggetti in px, per cui per poterle riassegnare in dp occorre eseguire una conversione manule da dp a px, utilizzando ad esempio il seguente metodo:
context.getResources().getDisplayMetrics().density;
che restituisce il valore density del dispositivo, come indicato nell‟ultima colonna della tabella precedente
Per semplificarne la gestione si possono predisporre le seguenti funzioni:
public int dpToPx(int dp) {
float density = this.getResources().getDisplayMetrics().density;
return Math.round((float) dp * density);
}
public int pxToDp(int px) {
float density = this.getResources().getDisplayMetrics().density;
return Math.round((float) px / density);
}
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 24
Principali Proprietà Comuni
I valori match_parent (equivalente a fill_parent deprecato) e” wrap_content” sono
sostanzialmente delle contanti predefinite che valgono rispettivamente 0 e -2
text = “testo del controllo”. Utilizza istanze implicite, per cui va bene eseguire il confronto con ==
tag = Campo nascosto di tipo object molto comodo per salvare valori di appoggio (id record)
Utilizza istanze eplicite per cui occorre necessariamente utilizzare .equals()
textSize = "18sp" // Gli sp sono analoghi ai dp ma riferiti al font textStyle = "bold|italic"
typeFace = famiglia del font
width e height = rappresentanto le dimensioni correnti del controllo espresse come numero intero
(in pixel) senza unità di misura. Viceversa layout_width e layout_height accettano come
parametro una stringa che può essere ad esempio "185dp” oppure un valore predefinito come
“match_parent” o “wrap_content”,
Notare che sia width che layout_width non possono essere lette in corrispondenza
dell‟onCreate in quanto all‟onCreate il rendering della pagina non è ancora stato terminato.
La stessa cosa vale per gli eventi successi onStart() e onResume(). Per leggere i valori di width e heigth in corrispondenza dell‟onCreate è comunque disponibile un metodo .measure con le
property measuredWidth e measuredHeight (per i quali si rimanda alla documentazione)
padding = "10dp" // Il padding non viene aggiunto a width e height come in html, ma provoca
semplicemente uno spostamento del testo. Se ad esempio si imposta un allineamento del testo a sinistra, conviene dare un piccolo paddingLeft. In questo caso il paddingRight non viene „sentito‟. In caso di allineamento centrato, si può dare un paddingLeft negativo per spostare il testo verso sinistra ed un paddingTop positivo per abbassare un pochino il testo.
enabled = true / false // Disabilita il controllo
editable = true / false // Usato nei TextView
visibility = visible / unvisible
textColor = "#ffffff00" // colore del testo giallo. // Il primo FF è l‟opacity. FF=solido,
background = "#ff0000ff" // colore di sfondo blu (solido) background = "@android:color/holo_blue_dark" // layout
gravity = ancoraggio del testo rispetto alle dimensioni dell‟oggetto.
Funziona „SOLO „ se sull‟oggettos ono state impostate WIDTH e HEIGHT Ad esempio un TextView può avere larghezza 200dp ma un testo interno molto breve che può essere allineato nel modo seguente:
LEFT / RIGHT a sinistra / a destra TOP / BOTTOM in cima al contenitore / in fondo al contenitore CENTER Al centro del contenitore, rispetto sia all‟asse X che all‟asse Y
Questi valori possono essere combinati tra loro tramite una OR bit a bit.
Es Gravity.LEFT | Gravity.BOTTOM
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 25
Impostazione tramite codice
Per l‟accesso da codice alle Property occorre utilizzare i metodi get e set che come unità di misura utilizzano SOLTANTO i pixel, per cui spesso occorre eseguire manualmente le conversioni necessarie.
.getText(); restituisce il testo del widget. Spesso occorre fare .toString()
.setText(“testo”); setta il testo del widget
.setTextSize(16); imposta la dimensione del testo in sp
.setTypeface(Typeface.SANS_SERIF); // font-style
.setTypeface(Typeface.SANS_SERIF, Typeface.BOLD); // font-style + bold
.setTypeface(null, Typeface.BOLD); // come font-style usa il default
.setTypeface(null, Typeface.ITALIC);
.setTypeface(null, Typeface.NORMAL);
.setTypeface(null, Typeface.BOLD_ITALIC);
.setTypeface(v.getTypeface(), Typeface.BOLD_ITALIC); // get current TypeFace
.setPadding(6,6,6,6); I valori di Padding e Margin partono da LEFT / TOP / RIGHT / BOTTOM
.getPaddingLeft();
.setGravity(Gravity.CENTER_VERTICAL); // Allineamento del testo
Il metodo .setGravity(Gravity.LEFT | Gravity.BOTTOM), oltre al valore di gravity,
presenta due altri parametri che rappresentano offsetX e offetY rispetto alla posizione del 1 param.
.setEnabled(true);
.setEditable(false);
.setVisibility(View.INVISIBLE);
.setTextColor(Color.RED);
.setBackgroundColor(Color.GREEN);
.bringToFront() porta il widget corrente in primo piano.
I Layout
Tutte le proprietà che interagiscono con il layout (cioè width, height, margin, gravity del controllo rispetto al layout, etc.), iniziano con la parola layout. Queste proprietà dipendono dal tipo di layout utilizzato. I principali layout disponibili sono 4:
Linear Layout
Relative Layout (da cui deriva il Constraint Layout)
Table Layout
Frame Layout
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 26
1. LinearLayout
Era inizialmente 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="match_parent" altrimenti si vedrebbe un solo controllo.
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à gravity del LinearLayout consente di definire l‟allineamento dei widget interni.
La proprietà layoutDirection consente visualizzare i controlli interni da sinistra verso destra
(LAYOUT_DIRECTION_LTR) oppure da destra verso sinistra (LAYOUT_DIRECTION_RTL)
layout.setOrientation(LinearLayout.HORIZONTAL);
layout.setGravity(Gravity.TOP);
Allineamento degli oggetti interni
layout_width="185dp” // larghezza del controllo
layout_height="wrap_content" // altezza del controllo adattata al contenuto
layout_margin=”10dp” // margini esterni.
layout_gravity // ancoraggio degli oggetti interni rispetto al Layout.
layout_weight=1; // copertura dell‟area libera in un linear layout orizzonale.
Attenzione che la propriatà gravity, vale SOLO ed ESCLUSIVAMENTE per i testi per cui non può essere utilizzata, ad esempio, per gli imageView. Nel caso di check box / radio button provoca l‟allineamento del testi ma NON l‟allineamento del quadratino / cerchietto rispetto al layout
Il valore layout_gravity =Gravity.Right ha senso ad esempio nel caso di un
Linear Layout verticale in cui l‟oggetto occupa tutta la riga. Se l‟oggetto corrente ha dimensioni ridotte, impostando layout_gravity =Gravity.Right l‟oggetto viene ancorato sul lato destro
del contenitore lasciando libero lo spazio antecedente
Nel caso invece di un Linear Layout orizzontale in cui gli oggetti i susseguono uno dopo l‟altro, l‟imposatzione layout_gravity =Gravity.Right applicata ad esempio all‟ultimo controllo non
sortisce nessun effetto perché si verrebbe a creare davanti all‟oggetto uno spazio vuoto che il designer non sa dove posizionare.
Nel caso di un layout orizzontale, la proprietà layout-weight=1 consente di definire quale peso
avrà il controllo nella ridistribuzione degli spazi liberi all‟interno della riga. Il valore di default è 0. Se si assegna il valore 1, il controllo coprirà l‟intera area libera della riga. A tal fine è però necessario che il contenitore abbia una width pari a MatchParent. Sull‟elemento occorre poi impostare il gravity del testo interno rispetto all‟elemento, in modo da allineare il testo a sinistra rispetto al controllo (con la spaziatura a destra) oppure a destra (con la spaziatura a sinistra).
Riguardo al width del controllo su cui si imposta layout-weight=1 si può utilizzare qualsiasi
valore intanto la larghezza viene poi calcolata automaticamente sulla base dell‟area libera. Normalmente è consigliato il valore 0.
Volendo ridistribuire l‟area libera in modo proporzionale si può impostare sul layout ad esempio weightSum=5, sul primo controllo weight=1, sul secondo weight=3, sul terzo weight=1
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 27
Impostazione da codice
Le propreità di tipo layout devono essere definite tramite un oggetto LayoutParams ed assegnate al
controlo tramite il metodo .setLayoutParams(); L’oggetto LayoutParams deve essere del tipo
specifico a seconda del layout a cui il controllo appartiene.
L‟oggetto layoutParams può essere istanziato ex-novo oppure può essere ricavato da un qualunque
controllo del layout corrente tramite il metodo .getLayoutParams(); riassegnando poi soltanto le
singole proprietà desiderate Per le propery numeriche occorre omettere l’unità di misura che viene automaticamente interpretata in pixel per le dimensioni spaziali e sp per i testi.
LinearLayout.LayoutParams params = (cast)v.getLayoutParams();
params.width=200;
v.setLayoutParams(params);
Nel caso creazione di una istanza ex-novo, occorre passare al costruttore i valori di width e height : (solo in alcuni casi (es TableRow) è ammesso un costruttore privo i parametri).
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(300,
LinearLayout.LayoutParams.WRAP_CONTENT);
params.width=50; // sovrascrive il 300 passato al costruttore
params.setMargins(6,6,6,6);
params.setMarginLeft(6);
params.gravity= Gravity.CENTER_VERTICAL;
params.weight=1.0f; // oppure =1; sembra indifferente
btn.setLayoutParams(params);
// oppure, nel caso si vogliano impostare soltanto width e height, si può semplicemente fare: btn.setLayoutParams(new LinearLayout.LayoutParams (300, 400);
Nel caso in cui interessi soltanto la lettura dei valori si può utilizare la seguente forma abbreviata int w = v.getLayoutParams().width;
oppure semplicemente int w = v.getWidth(); // NON è consentito eseguire il set() ??
2a. RelativeLayout
Layout di default dalla versione 5 in avanti. Consente di posizionare liberamente gli elementi indicando dimensione e posizione. Invece dei valori assoluti è possibile gestire l‟allineamento di un elemento rispetto agli elementi circostanti. La proprietà Layout_Gravity non è riconosciuta. Esempi :
Il primo elementi potrà avere:
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
Il secondo elemento potrà, ad esempio, essere posizionato sotto il primo:
android:layout_below="@+id/idElementoPrecedente "
android:layout_centerHorizontal="true"
Il terzo elemento potrà essere posizionato a fianco del secondo:
android:layout_toRightOf="@+id/idElementoPrecedente"
Il quarto elemento potrà essere centrato nella pagina:
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 28
Impostazione da codice
Le proprietà precedenti, disponibili soltanto nel caso del RelativeLayout, sono dette rules e sono impostabili da codice nel modo seguente:
RelativeLayout.LayoutParams params = (cast) v.getLayoutParams();
Se però l‟oggetto è stato creato dinamicamente v.getLayoutParams()restituice null.
Occorre allora istanziarlo ex-novo passandogli al solito width e height
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.WRAP_CONTENT,
RelativeLayout.LayoutParams.WRAP_CONTENT);
params.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
params.addRule(RelativeLayout.ALIGN_PARENT_TOP);
params.addRule(RelativeLayout.BELOW, R.id.previous);
params.addRule(RelativeLayout.RIGHT_OF, R.id.previous);
params.addRule(RelativeLayout.CENTER_HORIZONTAL);
params.addRule(RelativeLayout.CENTER_VERTICAL);
btn.setLayoutParams(params);
Pr quanto riguarda la rimozione di una regola, esistono due sintassi, la prima antecedente alla versione 17 ma ancora oggi supportata, la seconda disponibile dalla versione 17 in avanti:
params.addRule(RelativeLayout.CENTER_HORIZONTAL, 0);
params.removeRule(RelativeLayout.CENTER_HORIZONTAL);
I metodi addRule() e removeRule() non valgono per il constraintLayout.
2b. Constraint Layout
Evoluzione del Relative Layout con alcune nuove property che rendono ancora più semplice ed immediato il posizionamento degli oggetti.
Ogni oggetto può essere ancorato ai 4 oggetti intorno ed è necessario definire almeno 2 ancore.
Non è possibile cancellare una sola ancora. Per cancellare tutte le ancore dell‟oggetto selezionare l‟oggetto ed utilizzare l‟apposito puslantino di cancellazione delle ancore
Attenzione che gli oggetti ancorati sulla parte inferiore del layout, quando compare la tastiera di inserimento caratteri, vengono traslati in alto al di sopra della tastier, sovrapponendosi ai controlli presenti in quella posizione. Quando si usa la tastiera di insermento evitare la l‟ancora a fine pagina
Principali Proprietà di posizionamento
app:layout_constraintLeft_toLeftOf=”parent”Lato sx ancorato al lato sx del genitore
app:layout_constraintLeft_toRightOf=”ctrl” Lato sx ancorato al lato dx di un altro controllo
app:layout_constraintTop_toTopOf=”parent” Lato sup ancorato al lato sup del genitore
app:layout_constraintTop_toBottomOf=”ctrl” Lato sup ancorato al lato inf di un altro control
In caso di flusso da sinistra verso destra Start coincide con Left, End coincide con Right
In caso di flusso da destra verso sinistra Start coincide con Right, End coincide con Left
Per il Constraint Layout, riguardo alle property layout_width e layout_height, non è ammesso il
valore match_parent. Al suo posto si utilizza match_constraint chef a sì che il controllo si estenda
fino ai limiti del constraint. Per riempire l‟intera area client si può impostare match_constraint sia su
width sia su height e settare il constraint sinistro ed il constraint destro al valore parent (come nel codice che segue):
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 29
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"/>
La proprietà constraintHorizontal_bias
Questa propreità contiene un numero float compreso tra 0 e 1 che, se impostata a 0.5, esegue una centratura orizzontale dell‟oggetto. Il valore 0.5 rappresenta il valore di default, per cui quando si imposta questo valore la property viene rimossa dal file xml. Il valore 0f visualizzza l‟oggetto tutto a sinistra (a meno dell‟impostazione di un marginLeft) Il valore 1f visualizzza l‟oggetto tutto a destra (a meno dell‟impostazione di un marginRight)
Se si imposta un valore di marginLeft e/o di MarginRight, il valore HorizontalBias=0.5 provvede a centrare l‟oggetto nello spazio rimanente all‟interno dei due margini.
Impostazione da codice
La proprietà HorizontalBias è abbastanza complicata da gestire da codice. Nel caso in cui si preveda di dover riposizionare un oggetto da codice, la soluzione più semplice è quella di impostare sul designer HorizontalBias=0 e gestire la proprietà marginLeft.
ConstraintLayout.LayoutParams params = widget.getLayoutParams();
params.setMarginStart(marginStart); //px
widget.setLayoutParams(params);
params.width = larghezza del widget
params.height = altezza del widget
3. 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 layout = (TableLayout) findViewById(R.id.tableLayout);
for (int i = 0; i < 4; i++) {
TableRow riga = new TableRow(this);
riga.setGravity(Gravity.CENTER);
for (int j = 0; j < 4; j++) {
Button btn = new Button(this);
btn.setId(i * 4 + j);
btn.setOnClickListener(myListener);
riga.addView(btn);
}
layout.addView(riga);
}
Attenzione che eventuali parametri devono essere d tipo TableRow e non di tipo TableLayout.
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 30
4. FrameLayout
Il Frame Layout consente 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 o in orizzontale, il Frame Layout consente di impilarli lungo l‟asse z uno per piano. Il metodo bringToFront() consente di spostare in cima l‟elemento corrente Per ogni controllo si può inoltre 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).
Accesso ai controlli di un Layout
Associazione di un layout ad una Activity
setContentView(R.layout.activity_main);
Quando si cambia layout associato all‟activity, il layout viene „ricreato‟, per cui non mantiene i valori precedenti. Non solo, ma di conseguenza occorre anche reinizializzare i vari puntatori ai controlli dei
layout ripetendo tutti i vari findViewById.
Navigazione all’interno di un layout
layout.getChildCount(); restituisce il numero di controlli presenti all‟interno del layout
layout.getChildAt(i); restituisce il puntatore all‟oggetto i-esimo figlio del layout (a base 0)
this.getParent(); restituisce il puntatore all‟elemento genitore.
L‟oggetto restituito da .getParent() è un oggetto di tipo ViewParent, per cui occorre eseguire un cast. Per salire ancora di un livello occorre eseguire un cast intermedio al tipo ViewGroup RelativeLayout r = (RelativeLayout) ((ViewGroup) this.getParent()).getParent()
Cilo di scansione dei controlli interni ad un layout
Non sembra possible eseguire direttamente un ciclo for each sui controlli contenuti all‟interno di un layout. L‟indicazione è quella di scorrere l‟elenco dei controlli tramite un ciclo trazionale ed eventualmente aggiunge tutti i controlli all‟interno di un ArrayList scandibile poi tramite for each.
ArrayList<View> myList = new ArrayList<View>();
for (int i = 0; i < wrapper.getChildCount(); i++) {
View child = wrapper.getChildAt(i);
myList.add(v); }
Cancellazione di elementi
layout.removeAllViews(); elimina tutti i controlli contenuti nel layout (statici e dinamici).
layout.removeView(view); elimina uno specifico controllo all‟interno del layout
layout.removeView(layoutInterno); elimina un intero layout con tutti i controlli contenuti
Scroll di un Layout
Per rendere un Linear Layout scrollable è sufficiente racchiuderlo all‟interno di un oggetto ScrollView
<ScrollView android:layout_width="match_parent"
android:layout_height="match_parent" >
<LinearLayout> </LinearLayout>
</ScrollView>
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 31
La gestione degli sfondi tramite la classe ColorDrawable
Fino alla versione 21 gli sfondi erano gestiti tramite la classe ColorDrowable che consentiva di leggere /
scrivere i valori tramite I seguenti metodi:
.setBackgroundColor(Color.GREEN); // oppure
.setBackgroundColor(Color.rgb(0, 255, 0)); // oppure
.setBackgroundColor(0xFF00FF00); // Il primo FF è l‟opacity. FF=solido, 00=transparent
.setBackgroundColor(Color.parseColor("#FF00FF00"));
ColorDrawable buttonColor = (ColorDrawable) btn.getBackground();
int colorId = buttonColor.getColor();
dove colorId è una costante che può essere confrontata con Color.RED o Color.rgb(200, 200, 200)
La classe RippleDrawable
Dalla versione 21 in avanti, pur rimanendo la classe precedente, lo sfondo dei pulsanti per default viene impostato di tipo RippleDrawable, una classe particolare che aggiunge intorno al pulsante un bordino trasparente di spessore 5dp, riducendo di fatto le dimensioni per pulsante di 10dp.
In realtà lo spessore del bordo è una percentuale delle dimensioni del pulsante
Se ad un certo punto da codice si va a modificare il colore di sfondo del pulsante mediante il metodo precedente .setBackgroundColor, questo sovrascrive il bordo ingrandendo di conseguenza il
pulsante. Il RippleDrawable è stato pensato per essere applicato staticamente. La sua gestione da codice è piuttosto complicata per cui, se si intende gestire dinamente da codice lo sfondo del pulsante, si può impostare fin dall‟inizio (onCreate) lo sfondo tramite il metodo .setBackgroundColor() che
sostanzialmente sovrascrive la classe RippleDrawable applicando la classe ColorDrawable. In questo modo il bordino esterno di 5dp viene eliminanto fin da subito. La classe GradientDrawable
La classe GradientDrawable consente di applicare come sfondo in gradiente di colore, con la possibilità di impostare i bordi arrotondati ed un bordino esterno che, impostato al valore TRANSPARENT, consente di mantenere un funzionamento simile al RippleDrawable
int[] colors = new int[]{
Color.rgb(214, 215, 215), // grigio originale dei pulsanti Color.WHITE,
Color.rgb(214, 215, 215)
};
GradientDrawable bg = new GradientDrawable();
bg.setGradientType(GradientDrawable.LINEAR_GRADIENT);
bg.setOrientation(GradientDrawable.Orientation.TOP_BOTTOM);
bg.setColor(Color.rgb(214, 215, 215)); // colore singolo
bg.setColors(colors); // vettore di colori
bg.setStroke(10, Color.TRANSPARENT); // spessore (px) e colore del bordo
bg.setCornerRadius(10f); // 10 espresso come float
btn.setBackground(bg);
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 32
Immagine di sfondo di un pulsante
btn.setBackgroundResource(R.drawable.myimg);
btn.setBackgroundDrawable(this.getResources().getDrawable(android.R.drawable.
btn_default));
In alternative esiste un comodo pulsante imageButton.
La Gestione dello schermo
Dimensioni dello schermo
Display display = this.getWindowManager().getDefaultDisplay();
DisplayMetrics metrics = new DisplayMetrics();
display.getMetrics(metrics);
screenWidth = metrics.widthPixels;
screenHeight = metrics.heightPixels;
// oppure Point size = new Point();
display.getSize(size);
screenWidth = size.x;
screenHeight = size.y;
Dimensioni dell’Area client di un layout
Window window =this.getWindow();
View clientArea =. Window.findViewById(Window.ID_ANDROID_CONTENT);
int width = clientArea.getWidth();
int height = clientArea.getHeight();
La width dell‟area client coincide con la width del dispositivo. La height non perchè in alto cè la barra del titolo che sottrae spazio
Rotazione dello schermo
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 imageView all‟avvio caricava staticamente una immagine, questa viene ricaricata ad ogni rotazione.
Per evitare che in corrispondenza della rotazione dell‟immagine venga riavviata l‟Activity, occorre aggiungere la seguente riga:all‟interno del Manifest:
<activity android:name = "MainActivity"
android:configChanges = "orientation|keyboardHidden|screenSize">
Questa riga fa sì che, in caso di variazione della orientation del telefono, oppure in caso di nascondimento della tastiera, oppure in caso di variazione dello sceenSize, l‟applicazione non si riavvii, ma generi semplicemente un evento onConfigurationChanged che può essere semplicemente
gestito nel modo seguente oppure anche non gestito.
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig); }
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 33
INTENT
La classe Intent fornita col framework di Android fornisce i meccanismi necessari per realizzare una
sorta di messaggistica gestita dal sistema operativo con cui un componente può richiedere l’esecuzione di operazioni ad altre componenti.
Tramite gli intent una APP può richiedere al Sistema Operativo una certa azione, come ad esempio il lancio di un‟altra activity o di un Service, oppure per inviare un messaggio di broadcast che può essere ricevuto da qualsiasi app.
Un intent può contenere al suo interno tre informazioni:
action, cioè l‟azione da eseguire. L‟azione da eseguire può essere l‟avvio di una activity, la
visualizzazione di un documento, l‟editazione di un documento, la riproduzione di un filmato o altro ancora. Possibili valori di action sono i seguenti:
ACTION_MAIN Richiesta di Avvio di un componente. Non ha dati in input ACTION_VIEW Richiesta di visualizazione di un insieme di dati indicati tramite URI ACTION_EDIT Richiesta di accesso in editazione a un insieme di dati indicati tramite URI ACTION_DIAL Display the phone dialer with the given number filled in ACTION_ECHO Riproduzione di un file video / audio indicato tramite URI ACTION_REQUEST_ENABLE Richiesta di abilitazione all‟utilizz di uno specifico sensore (GPS)
destinatario del messaggio (component). Può essere un‟Activity, un Servizio o un Broadcast
Receiver. E‟ l‟elemento a cui il SO dovrà inoltrare il messaggio ricevuto.
uri, indica la risorsa da eleborare. Ad esempio una pagina web da visualizzare, un documento
PDF da visualizzare, un numero di telefono da contattare. URI (Uniform Resource Identifier) è una sequenza di caratteri che identifica univocamente una risorsa generica. Sono esempi di URI: un indirizzo web (URL), un documento, un'immagine, un file, un servizio, un indirizzo di posta elettronica simile. URL è strettamente legato alle risorse web, mentre URI è più generico. Si può dire “tutti gli URL sono URI, ma esistono URI che non sono URL. Esempi di URI:
String uri = "http://www.google.com"; (URL)
String uri = "tel:3337684747";
String uri = "sms:3337684747";
String uri = "mailto:[email protected]";
Oltre a quete informazioni un intent può contenere al suo interno dei parametri aggiuntivi (detti Extras) che saranno inoltrati al destinatario insieme al messaggio. Ad esempio nel caso di una mail i vari extras potrebbero essere l‟oggetto, il body, i destinatari per conoscenza, i destinatari per conoscenza nascosta, etc. Intent Espliciti ed Impliciti
Si parla di Intent Esplicito quando è esplicitamente indicato il destinatario del messaggio. Si parla di Intent Implicto quando NON è esplicitamente indicato il destinatario del mesaggio
Per istanziare un Intent esistono ben 7 firme differenti :
1) Intent intent = new Intent()
Istanzia un Intent anonimo. Azione, URI e destinatario verranno specificati in seguito
2) Intent intent = new Intent(Intent i)
Costruttore per copia. Istanzia un Intent copiandolo da un altro Intent passato come parametro.
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 34
3) Intent intent = new Intent(String action)
Istanzia un Intent implicito relativo ad una specifica azione, senza indicare però l‟URI
4) Intent intent = new Intent(String action, Uri uri)
Firma tipica per l‟Istanza di un Intent implicito, in cui viene indicata una specifica azione ed uno specifico URI, ma non viene indicato il destinatario
5) Intent intent = new Intent(Context packageContext, Class cls)
Firma tipica per l‟Istanza di un Intent Esplicito in cui si indica esplicitamente il destinatario dell‟intent. In caso di intent esplicito solitamente non serve indicare né l‟action (che sarà quella predefinita del destinatario) né la URI nel caso in cui il destinatario non debba elaborare una risorsa specifica. Con questa firma, il SO non esegue di solito nessuna valutazione sull‟action da eseguire e si limita ad avviare il componente indicato. 6) Intent intent = new Intent(Component component)
Firma analoga alla precedente in cui, però, il destinatario viene identificato come component invece che
come context + class
Il componente destinatrio può essere indicato in due modi:
come Context (package univoco identificativo della app) seguito dal nome della classe da eseguire all‟interno del package (firma 5)
come Component (firma 6)
Partendo da package e classe si può ottenere il component nel modo seguente:
ComponentName component = new ComponentName(this, SecondActivity.class);
7) Intent intent = new Intent(String action, Uri uri, Context packageContext,
Class<?> cls)
Firma completa per l‟istanzia di un Intent esplicito, contenente però anche la action da eseguire e la risorsa su cui operare. Esempio di istanza di un intent implicito
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(uri));
this.startActivity(intent);
Esempio di istanza di un intent esplicito
Intent intent = new Intent(MainActivity.this, Activity2.class);
this.startActivity(intent);
Utilizzo degli intent impliciti
Nel caso di intent implicito, in che modo il Sistema Operativo riesce ad individuare il destinatrio del messaggio ? Quando si definisce un componente, attraverso il cosiddetto intent-filter è possibile
dichiarare che quel componente è in grado di rispondere a specifici messaggi. L‟ intent-filter può
essere definito staticamente all‟interno del Manifest, oppure anche dinamenicamente all‟interno del codice dell‟Activity, Servizio o Broadcast Receiver.
In corrispondenza di un Intent implicito il SO provvderà ad individuare tutti i receiver compatibili. Nel caso in cui esistano più componenti registrati con lo stesso intent-filter, verrà richiesto all‟utente di decidere quale componente utilizzare per avviare l‟attività. Ad esempio l‟activity principale di un browser avrà, all‟interno del Manifest, un Intent-filter de tipo seguente:
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 35
<activity android:name=".BrowserActivitiy">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="http" />
</intent-filter>
</activity>
Cioè questa activity verrà avviata tutte le volte che verrà richiesta una action di tipo ACTION_VIEW per
una risorsa avente un URI che inizia con i caratteri http:
L’attributo data
Indica il tipo di risorsa che il componente è in grado di servire. Il componente sarà in grado di servire tutte le risorse il cui URI inizia con i caratteri indicati all‟interno dell‟attributo data. L’attributo mimeType
Serve ad identificare meglio (all‟interno dell‟attributo data) il tipo di risorsa che il componente è in grado di servire. Ad esempio un riproduttore audio / video che è in grado di riprodurre qualunque file audio e soltanto i video mpeg potrà avere un attributo mimeType come indicato di seguito.
<intent-filter>
<action android:name=”android.intent.action.ECHO” />
<data android:scheme=“http” android:mimeType=“audio/* />
<data android:scheme=“http” android:mimeType=“video/mpeg />
</intent-filter>
L’attributo category
Fornisce una informazione aggiuntiva rigiardo all‟azione da eseguire. category.LAUNCHER indica che il componente dovrà essere visualizzato nel Launcher come una top-
level application (elenco delle applicazioni disponibili all‟interno dello smartphone) category.DEFAULT indica che il componente deve essere considerato come un componente di
default in grado di soddisfare l‟azione indicata. category.ALTERNATIVE indica che il componente dovrà essere incluso in una lista di componenti
alterntivi comunque in grado di soddisfare l‟azione indicata Se un componente non definisce un apposito intent-filter, potrà essere avviato soltanto tramite un intent esplicito. Metodi per l’invio di un intent al Sistema Operativo
startActivity(intent)
Se si intende inviare il messaggio ad una activity di una app
startService(intent)
Se si intende inviare il messaggio ad un servizio di background in esecuzione sullo smartphone
sendBroadcast(intent)
Se si intende inviare il messaggio in broadcast a tutte i componenti dello smartphone Richiesta di Abilitazione del GPS Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
startActivity(intent);
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 36
Richiesta di Abilitazione del bluetooth Intent intent = new Intent(Settings.ACTION_BLUETOOTH_SETTINGS); // oppure
Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivity(intent);
Richiamo di una Activity mediante un intent esplicito
Per creare una nuova Activity app / java / tasto destro / New / Activity / Empty Activity. Il nome della nuova activity, come tutte le classi, deve iniziare con una lettere Maiuscola
La nuova activity viene automaticamente inserita nel Manifest
senza però la sezione di <intent-filter> (che, come detto, è facoltativa).
Per ciascuna Activity si può creare un apposito layout di riferimento. In realtà però una Activity1 potrebbe utilizzare più Layout diversi senza necessità di dover istanziare una seconda Activity2. Il fatto di creare due Activity separate, ognuna con il suo layout, consente di suddividere meglio il codice
L’activity principale presenta di default un intent-filter di questo tipo:
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
Il valore ACTION_MAIN indica una activity di tipo MAIN direttamente eseguibile dal sistema operativo.
Il valore category.LAUNCHER indica che l‟activity dovrà essere visualizzata all‟interno del Launcher
con una sua icona. In linea di massima all‟interno di una APP una sola Activity può essere di tipo MAIN / LUNCHER. Nel caso di più Activity di questo tipo, l’Activity da lanciare all’avvio dovrà essere specificata
all’interno della finestra EDIT CONFIGURATION.
Le activity secondarie possono, facoltativamente, essere dichiarate con il seguente intent-filter :
<intent-filter>
<action android:name="android.intent.action.secondActivity"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
dove action startSecondActivity indica l‟azione a cui questa activity dovrà rispondere
Intent Esplicto di avvio della seconda activity
Intent intent = new Intent(this, Activity2.class);
this.startActivity(intent);
Intent implicito di avvio della seconda activity
Intent intent = new Intent("android.intent.action.secondActivity");
oppure
Intent intent = new Intent();
intent.setAction("android.intent.action.secondActivity");
this.startActivity(intent)
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 37
Ciclo di vita di una Activity
Tutti questi eventi devono, per prima cosa, richiamare l‟evento corrispondente della super-classe, esattamente come avviene per onCreate(). Però, a differenza di onCreate non hanno parametri.
onCreate : evento richiamato al momento del caricamento in memoria
onPause : evento che si verifica quando viene aperta una nuova activity e l‟activity corrente, pur
rimanendo visibile, non è più in esecuzione (equivalente allo stato di ready dei processi) Gli eventi onPause e onResume sono utilizzati per attivare/terminare servizi che devono attivi solo
quando l'activity è in uso. Ad esempio in conseguenza ad una AlertDialog.
onStop : Quando una Activity lancia in esecuzione un‟altra Activity tramite un Intent, automaticamente
passa nello stato di STOP, cioè si trova nello stack delle Activity in esecuzione ma non è al momento visibile. Nel momento in dovesse essere riavviata (mediante REORDER_TO_FRONT oppure se viene di nuovo a trovarsi in primo piano per la chiusura della Activity che si trovava in esecuzione), non viene più generato l‟evento onCreate ma gli eventi onRestart e onStart.
onStop()
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 38
In caso di satuazione della memoria, Il Sistema Operativo ha facoltà di „terminare‟ un processo che si trova in stato di STOP. Il processo che viene terminato è quello che si trova in fondo allo stack, cioè quello con utilizzo più lontano nel tempo.
onDestroy : Quest‟evento si verifica quando l‟actvity viene terminata esplicitamente mediante il
metodo finish oppure mediante il tasto BACK dello smartphone.
Il metodo finish() ed il pulsante BACK
il pulsante Back dello smartphone ed il metodo finish() provvedono a terminare l‟activity corrente rimuovendo fisicamente l‟istanza dalla memoria e generando l‟evento onDestroy. Il metodo finish() è un metodo relativo al contesto corrente:
this.finish();
Activity2.this.finish();
Notare che il tasto App recenti dello spartphone apre una schermata con le applicazioni usate recentemente sotto forma di anteprima. In questo elenco compaiono
sia le app in stato di stop (che mantengono lo stato attuale)
sia le app terminate tramite finish o pulsante BACK che, viceversa, non mantengono lo stato. In corrispondenza del loro riavvio verrà rigenerato l‟evento onCreate()
In Lollipop è stata introdotta una grafica a carosello che facilita un rapido passaggio da un'app all'altra
Come riportare una Activity in primo piano
Per riportare una Activity in primo piano è sufficiente settare un apposito flag prima di eseguire startActivity. Con questo flag, se l’activity non è istanziata viene istanziata, altrimenti viene
semplicemente riportata in primo piano.
Intent intent = new Intent(Activity2.this, Activity1.class);
intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
this.startActivity(intent);
Il metodo moveTaskToBack(true)
Consente di portare l‟activity corrente in stato di stop minimizzandola. A seguito della minimizzazione viene visualizzata la seconda app in cima allo stack. Quando l‟Activity ritornerà in primo piano verranno generati gli eventi onRestart() e poi onStart(), ma NON onCreate(). Il parametro True fa sì che la app venga minimizzata anche se il focus non è sulla Activity principale. False invece provoca la minimizzazione solo se il focus è sulla activity principale.
Passaggio di parametri ad una Activity
Prima di richiamare il metodo startActiity(intent), si possono aggiungere all‟interno dell‟intent
eventuali parametri (detti Extras) 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);
this.startActivity(intent);
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 39
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");
E‟ anche possibile utilizzare il metodo getExtras()che restituisce tutti gli extras all‟interno di un unico
Bundle. Il Bundle (letteralmente fascio) è un oggetto di tipo contentiore che consente di raggruppare
parametri di diversa natura. E‟ lo stesso che viene passano a onCreate() all‟avvio dell‟applicazione, nel qual caso contiene informazioni mirate al mantenimento dello stato.
Bundle bundle = this.getIntent().getExtras();
if (bundle != null && bundle.containsKey(“parametro1”)){
parametro1 = bundle.getString("parametro1");
Passaggio di un oggetto ad Activity2
Tramite il Bundle è anche possibile passare ad Activity2 un intero Object a patto che implementi la classe Serializable
public class Studente implements Serializable {
}
Bundle bundle = new Bundle();
bundle.putSerializable("studente", studente);
bundle.putInt("index", 35);
intent.putExtras(bundle);
In lettura si può utilizzare la seguente sintassi
Bundle bundle = this.getIntent().getExtras();
Studente studente = (Studente) bundle.getSerializable("studente");
int index = bundle.getInt(index)
Proprietà Statiche
Invere di passare un intero oggetto ad Activity2 (ad esempio un intero ArrayList) è possibile dichiarare l‟ArrayList in modo statico all‟interno di Activity1. In tal modo Activity2 potrà accedere all‟oggetto scrivendo semplicemente :
Activity1.myArrayList
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 40
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 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", "parametro aggiuntivo");
this.setResult(RESULT_OK, intent); // RESULT_OK = -1
this.finish();
}
public void btnAnnulla_Click(View v) {
this.setResult(RESULT_CANCELED); // RESULT_CANCELED = 0
this.finish();
}
Notare che setResult() imposta il risulato ma non termina l’Activity corrente che dovrà necessariamente essere chiusa tramite finish (concetto di finestra modale). Su activity1 l‟evento onActivityResult viene generato soltanto in corrispondenza del finish(). Se Activity2 non esegue il finish ma si limita a riportare Activity1 in primo piano tramite il metodo startActivity( ), l‟evento onActivityResult su Activity1 non viene generato. Lettura del risultato da parte di Activity1
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){
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");
textView.setText(pulsantePremuto + “ – “ + par);
}
}
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 41
Menù
Ogni applicazione può possedere un menù attraverso il quale è possibile associare delle azioni di interazione con l‟activity corrente. Ci sono due tipi di menù: OptionsMenu e ContextMenu.
OptionsMenu
E‟ il tipico menù che si apre clickando sui “tre puntini” in alto a destra all‟interno della Action Bar (barra del titolo). Gli OptionsMenù sono associati ad una singola Activity ed espongono le operazioni più importanti che un utente può eseguire relativamente a quella Activity. Da Android 5 in avanti le voci vengono visualizzate in verticale una sotto l‟altra. Le voci scompaiono automaticamente dopo il click su una delle voci oppure dopo un click sull‟area dell‟Activity.
Evento OnCreateOptionMenu
All‟avvio Android crea automaticamente un menù vuoto per l‟Activity corrente. Al termine della fase di avvio richiama automaticamente l‟evento OnCreateOptionMenu (callback
method) a cui viene iniettato come parametro il riferimento al menu. Il programmatore può utilizzare questo evento per popolare il menù.
public boolean onCreateOptionsMenu(Menu menu){
}
Il metodo restituisce un booleano che indica al SO se deve visualizzare oppure no i “tre puntini” di accesso al menù
return true siginifica che il menù è stato popolato e si chiede al SO di visualizzare i tre puntini di apertura del menù medesimo
return false si chiede al SO di NON visualizzare i tre puntini di apertura del menù che potranno essere eventualmente visualizzati in un altro momento.
In genere però, nel caso in cui il menù non sia stato popolato, non si esegue il return false, ma si
richiama il metodo corrispondente della superclasse per dare al sistema l‟opportunità di popolare il menù con eventuali items di sistema.
if (ok) // se il menù è stato popolato
return true;
else
return super.onCreateOptionsMenu(menu);
Aggiunta di nuove voci tramite il metodo add()
menu.add(int groupId, int itemId, int order, CharSequence title)
groupId è un parametro che indica il gruppo di appartenenza della voce menu.NONE = no group
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
Esempio: menu.add(Menu.NONE, 1, 1, "Voce 1");
menu.add(Menu.NONE, 2, 2, "Voce 2");
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 42
Il gruppo non ha alcuna funzione visuale, ma serve semplicemente a raggruppare le voci da un punto di vista logico in modo da poter applicare al gruppo i metodi di gruppo tipo setGroupCheckable, setGroupEnabled, setGroupVisible. Accesso all’item e relativi metodi
Il metodo .add() restituisce un riferimento all‟item appena aggiunto. Questo riferimento può essere
utilizzato per aggiungere una icona alla voce oppure, ad esempio per renderla selezionabile tramite un checkbox:
MenuItem item = menu.add(Menu.NONE, 3, 3, "voce3"); // oppure
MenuItem item = menu.findItem(R.id.myId); //item id
MenuItem item = menu.getItem(0); //item index
Sul singolo item sono disponibilii seguenti metodi:
item.getItemId() // restitusice l‟ID dell‟item
item.getTitle() // restitusice il testo dell‟item item.setCheckable(true);
item.setChecked(true/false);
item.isChecked() // restitusice true se l‟item è selezionato item.setEnabled(true/false);
item.setVisible(true/false);
item.setIcon(R.drawable.myicon);
Il setCheckable rende la voce selezionabile, ma il check deve essere impostato esplicitamente da
codice in corrispondenza del click tramite setChecked(true) Aggiunta di un Submenu
L‟oggetto Submenu consente di aggiungere al menù corrente un sottomenù a cui possono essere applicati tutti gli stessi identici metodi applicabili al menù principale.
SubMenu menu2 = menu.addSubMenu(Menu.NONE, 6, 6,"Opzioni");
menu2.add(GRUPPO, 11, 1, "Opzione 1");
menu2.add(GRUPPO, 12, 2, "Opzione 2");
menu2.setGroupCheckable(GRUPPO, true, false);
Il metodo .setGroupCheckable consente di rendere selezionabili (con chekbox) tutte le voci del
gruppo. Il secondo parametro se true rende le voci selezionabili.
Il terzo parametro (boolean exclusive), se impostato a true rende le varie voci esclusive,
trasformando di fatto i checkbox in radiobutton (i quadratini dei chekbox diventano cerchi). Evento onOptionsItemSelected di risposta al click sulle voci
Evento generato In corrispondenza del click su una qualunque voce del Menù. Riceve come parametro un riferimento all‟item che lo ha generato. Utilizzabile per impostare / rimuovere manualmente il check
alert(item.getTitle().toString());
alert( Integer.toString(item.getItemId()));
if (item.isCheckable()){
if (item.isChecked())
item.setChecked(false);
else
item.setChecked(true);
}
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 43
Risultato restituito da onOptionsItemsSelected
Anche l‟evento onOptionsItemSelected dispone di un return finale.
return true. Significa che l‟evento è stato consumato dal gestore corrente.
return false Significa invece che l‟evento non è stato consumato dal gestore corrente e viene
inoltrato ad altri eventuali gestori che potranno eventualmente gestirlo
Come nel caso precdente, se l‟evento non è stato consumato, al termine si richiama il metodo corrispondente della superclasse e si ritorna il suo risultato.
int id = item.getItemId();
switch (id) {
case 1: ……… break;
case 2: ……… break;
case 3: ……… break;
default: return super.onOptionsItemSelected(item);
}
return true;
Creazione statica di un OptionMenu tramite file xml
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
Viene creata, all‟interni di res, una nuova sottocartella menu con all‟interno il nuovo file xml. 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/gruppo1">
<item android:id = "@+id/item1"
android:icon="@drawable/myicon"
android:title="voce1">
</item>
<item android:id = "@+id/item2" android:title="voce2"> </item>
<item android:id = "@+id/item3" android:title="voce3"> </item>
</group>
<!-- check box -->
<item android:id = "@+id/chkBox"
android:title="voce9"
android:checkable="true"
android:order="100">
</item>
<!-- radio button -->
<item android:id = "@+id/opzioni" android:title="opzioni">
<menu>
<group android:checkableBehavior="single">
<item android:id = "@+id/opzione1" android:title="opzione1"></item>
<item android:id = "@+id/opzione2" android:title="opzione2"></item>
</group>
</menu>
</item>
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 44
L‟opzione android:checkableBehavior è l‟analogo del metodo .setGroupCheckable e consente
di applicare l‟attributo checkable a tutti gli item di un gruppo senza dover applicare il :checkable ad
ogni singolo elemento. I valori possibili sono: all rende selezionalbili tutti gli elementi del gruppo, visualizzando a fianco di ciascun item un
tipico checkbox single rende selezionalbile un solo elemento alla volta, visualizzando a fianco di ciascun item un
tipico radio button di esclusività none rende gli elementi non selezionabili e a fianco degli item non viene visualizzato nulla.
Impostando il valore android:checkableBehavior=”single” sul gruppo NON bisogna più impostare l‟attributo checkable all‟interno delle opzioni perché altrimenti prevalgono e ritrasformano gli item in check box. Accesso al file di risorsa tramite l’inflater
L'inflating è il meccanismo che permette di istanziare un'oggetto a partire da un file di risorsa.
All‟interno dell‟evento OnCreateOptionMenu occorre „collegare‟ il puntatore menu ricevuto dall‟evento
con il menu creato staticamente all‟interno del file xml. A tal fine si usa l‟oggetto MenuInflater.
public boolean onCreateOptionsMenu (Menu menu){
MenuInflater inflater = this.getMenuInflater();
inflater.inflate(R.menu.menu_statico.xml, menu);
try {
inflater.inflate(R.menu.menu_statico, menu);
return true;
}
catch(Exception ex) {
return super.onCreateOptionsMenu(menu);
}
}
Evento di risposta al click
L‟evento onOptionsItemSelected rimane lo stesso identico utilizzato nel caso del menù dinamico.
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 (long click) sull'elemento.
Si possono associare ContextMenu defferenti per ogni controllo. La sintassi è la seguente :
public void onCreate() {
this.registerForContextMenu(lbl);
this.registerForContextMenu(btn);
}
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 45
Evento OnCreateOptionMenu
Se un controllo ha associato un ContextMenu, nel momento in cui si esegue un long click sul controllo, viene automaticamente generato l‟evento onCreateContextMenu nel quale è possibile creare dinamicamente il menù da visualizzare. Il menù pertanto può può variare a seconda dei contesti Il problema principale legato all‟evento onCreateContextMenu è che, a differenza dell‟evento OnCreateOptionMenu che viene richiamato soltanto all‟avvio dell‟applicazione, quest’evento viene
richiamato ogni volta che si esegue un long click, per cui il menù viene ogni volta ricreato e lo stato delle varie opzioni deve eventualmente essere salvato all‟interno di una variabile di programma.
pubiic void onCreateContextMenu(
ContextMenu menu,
View v,
ContextMenu.ContextMenuInfo menuInfo) { }
Il 1° parametro è il puntatore al menu a cui appendere le nuove voci Il 2° parametro è un puntatore al controllo che ha scatenato l‟evento Il 3° parametro è un puntatore ad eventuali ulteriori informazioni aggiuntive
// per prima cosa si richiama la superclasse
super.onCreateContextMenu(menu, v, menuInfo);
if(v.getId() == R.id.lblTitolo) {
MenuItem item=menu.add(Menu.NONE, 1, 1, "grassetto");
item.setCheckable(true);
item.setChecked(grassetto);
}
else if(v.getId() == R.id.btn) {
menu.setHeaderTitle("link disponibili:");
menu.add(Menu.NONE, 3, 1,"www.google.com");
menu.add(Menu.NONE, 4, 2,"www.vallauri.edu");
}
dove grassetto è una variabile globale booleana che vale :
true se il grassetto è impostato
false se il grssetto non è impostato
Il metodo activity.openContextMenu(myControl)
Questo metodo, richiamabile da qualunque sezione di codice, consente di aprire da programma il Context Menù dell‟oggetto myControl senza dover eseguire un long click. Evento onContextItemSelected(MenuItem item)
Questo evento viene generato ogni volta che si clicca su una delle voci del menù, esattamente come per gli OptionMenu. Il parametro item rappresenta il puntatore alla voce cliccata.
if(item.getItemId()==1){
grassetto=!grassetto;
if(grassetto) lbl.setTypeface(null,Typeface.BOLD);
}
Dopo il click il menu viene distrutto e, in corripondenza del prossimo long-click, viene rigenerato.
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 46
Device File Explorer
Da Android Studio 3.0 in avanti il vecchio Android Device Monitor (DDMS = Dalvik Device Monitor
Service Tools/Android/AndroidDeviceMonitor) non è più integrato in Android Studio, ma è possibile accedere singolarmente ai suoi componenti dal menù View / Tools Windows.
L‟intero ambiente Android Device Monitor rimane comunque disponibile all‟interno dell‟SDK android-sdk/tools/monitor.bat
View / Tool Windows / Device File Explorer è l‟utility che funge da terminale grafico verso l‟emulatore dello smartphone. Consente di accedere al file system dello smatphone, come se avessimo un computer connesso tramite USB allo smartphone.
Le cartelle dati a cui l‟utente ha accesso in scrittura sono sostanzialmente due:
Data (memoria interna)
SdCard (memory card esterna)
In realtà negli smartphone attuali data e sdcard sono due sezioni della stessa memoria
Le apk caricate all‟interno dello smartphone sono posizionate nella cartella data / app
I dati sono invece posizionati all‟interno della cartella data / data / myApp / files /
La cartella files viene creata automaticamente nel momento in cui si istanzia un FileReader oppure un
FileWriter su un file interno a quella cartella. La cartella può anche essere creata manualmente.
Per creare un nuovo file o una nuova cartella all‟interno di una certa cartella è sufficiente fare tasto destro sulla cartella / New. Sempre tramite tasto destro sono disponibili i comandi upload, save as e synchronize che esegue un refresh della cartella. Dentro Data/Data/files / si può ad esempio creare una cartella Music in cui salvare files.mp3.
Nota: In realtà a Run Time eventuali files uploadati all‟interno di Data / Data / files vengono copiati ed utilizzati in una posizione diversa: data / user / 0 / myApp / files /
Per aprire un file un file è sufficiente un doppio click. Attenzione però che Android Studio saves files you open this way in a temporary directory outside of your project. If you make modifications to a file you opened using the Device File Explorer, and would like to save your changes back to the device, you must manually upload the modified version of the file to the device. In pratica occorre gestire il file esternamento e poi fare l‟upload.
Le impostazioni dello smartphone (ad esempio un valore simulato delle coordinate GPS) sono ora configurabili dal menù di comandi sul lato destro dell‟emulatore stesso.
Accesso al File System
Le aree di memoria di una app sono sostanzialmente tre:
Internal Storage (memoria interna) visibile da Android Device Monitor e suddivisa per app. Contiene i dati specifici delle singole app.
External Storage (memoria esterna o SD Memory) che consente di estendere la memoria interna del dispositivo. Essendo rimuovibile, se si effettua il salvataggio di dati sulla SD, è opportuno che i dati salvati non siano indispensabili per il funzionamento dell'applicazione.
Assets / Row Sono aree di memoria interne all‟apk. I dati di queste cartelle sono utilizzabili in sola lettura (essendo interni all‟apk non è consentito modificare la loro dimensione) e NON sono accessibili tramite File System del dispositivo. Sono accessibili da Android Studio in sviluppo.
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 47
Accesso ai files memorizzati nel Internal Storage
String path = this.getFilesDir().getPath().toString() + "/filename.txt";
FileReader fileReader = new FileReader(path);
BufferedReader bufferedReader = new BufferedReader(fileReader);
FileWriter fileWriter = new FileWriter(path, true/false)
BufferedWriter bufferedwriter = new BufferedWriter(fileWriter);
// oppure
FileInputStream stream = context.openFileInput("filename.txt");
InputStreamReader streamReader = new InputStreamReader(stream, "UTF-8");
BufferedReader bufferedReader = new BufferedReader(streamReader);
FileOutputStream stream = context.openFileOutput("myText.txt",MODE_PRIVATE);
OutputStreamWriter streamWriter = new OutputStreamWriter(stream);
BufferedWriter bufferedwriter = new BufferedWriter(streamWriter);
Per vedere se un file esiste / creare un nuovo file
File myFile = new File(path);
if(myFile.isFile()) // è un file e non una cartella
FileReader reader = new FileReader(myFile);
else
myFile.createNewFile();
Per leggere il contenuto di una cartella :
File dir = new File(path);
String [] vect = dir.list();
Lettura / Scrittura dei Text File
public void leggi() throws IOException {
try{
FileReader fileReader = new FileReader(path);
BufferedReader br = new BufferedReader(fileReader);
String s, testo="";
// readline elimina il \r\n finale
while((s=br.readLine()) != null)
testo += s + "\r\n";
br.close();
reader.close();
alert(testo);
}
catch (Exception ex) { // pokemon exception ! gotta catch „em all
alert(ex.getMessage());
}
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 48
public void salva() throws IOException {
try{
// true = append; false=overwrite (default);
FileWriter fileWriter = new FileWriter(path, true)
BufferedWriter writer = new BufferedWriter(fileWriter);
writer.write("Lorem Ipsum\r\n");
writer.flush();
writer.close();
alert("File Salvato Correttamente");
}
catch (IOException ex) {
alert(ex.getMessage());
}
Accesso ai files memorizzati nell’External Storage
Occorre innanzittutto 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";
Dopo di che si possono utilizzare gli stessi metodi dell‟Internal Storage
Accesso ai files memorizzati nella cartella Assets
Come detto la cartella Assets viene creata direttamente all‟interno dell‟apk ed i files sono readonly Da Andorid Studio è possibile creare staticamente una cartella Assets aggiungendo al suo interno i files desiderati. A tal fine andare su : app / tasto destro / New / Folder / Assets Folder Come percorso tra Main, Debug, Release va benissimo Main All‟interno della cartella Assets creare il file desiderato.
InputStream stream = context.getAssets().open(“filename.txt”);
InputStreamReader streamReader = new InputStreamReader(stream, "UTF-8");
BufferedReader bufferedReader = new BufferedReader(streamReader);
L’oggetto FileOutputStream
Gli oggetti FileWriter e FileReader sono mirati all‟utilizzo di stream testuali.
FileOutputStream è invece utilizzato nel caso di stream binari che vengono letti byte a byte senza interpretazione dei dati. In questo caso filename non è il path completo, ma il semplice nome del file.
Scrittura
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.
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 49
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
ListView e Adapter
Le liste visualizzano i record uno sotto l'altro con la possibilità di effettuare il classico scroll.
Principali proprietà:
// come width e height impostare SEMPRE match_parent
android:layout_width:”match_parent”
android:layout_height:”match_parent”
// impostare SEMPRE tutti e 4 i constraint come indicato di seguito
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@+id/menuLayout"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:choiceMode="singleChoice" // oppure multiple choice
android:fastScrollAlwaysVisible="true" // Barra scorrimento sempre visibile
android:fastScrollEnabled="true"
android:listSelector="blue"
il listSelector definisce il colore di sfondo dell‟elemento selezionato.
Per cambiare il colore del testo dell‟elemento selezionato occorre utilizzare la direttiva android:textColor="@drawable/text_selector"
dove text_selector è un file .xml definito all‟interno della cartella drawable
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.
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 50
Scansione degli item di un ListView
listView.getCount(); ritorna il numero di item complesivamente contenuti nel ListView
listView.getChildCount(); ritorna il numero di item attualmente visualizzati for(int i=0 ; i < listView.getCount(); i++) listView.setItemChecked(i, false);
Scroll del List View
Come detto all‟inizio, per avere uno scroll efficace occorre impostare entrambe le dimensioni a match_parent e ancorare il ListView su tutti e 4 i lati
Per eseguire lo scroll da codice : lstCitta.setSelection(index);
La lista verrà visualizzata a partire dall‟elemento avente l‟indice index (ovviamente a base 0). lstCitta.setSelection(2); // La lista viene visualizzata a partire dall‟elemento indicato.
ArrayAdapter
Per il caricamento dei dati da un ArrayList ad 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 i dati memorizzati sotto forma di array.
Istanza dell’Adapter e associazione con la sorgente dati
ArrayAdapter<String> adapter = new ArrayAdapter<String>(
this,
android.R.layout.simple_list_item_1,
list);
Al costruttore dell'ArrayAdapter occorre passare 3 parametri: 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ò è molto più flessibile.
android.R.layout.simple_list_item_1 è un layout predefinito molto semplice che visualizza su
ogni riga un singolo elemento di testo (ottenuto ad esempio tramite il metodo .toString di un object) android.R.layout.simple_list_item_2 è un layout simile al precedente in cui ogni item viene
suddiviso in due righe, una sotto l‟altra android.R.layout.simple_list_item_checked visualizza a fianco di ogni voce un checkbox
che si attiva in corrispondenza della selezione android.R.layout.simple_list_item_single_choice visualizza a fianco di ogni voce un
radiobutton che si attiva in corrispondenza della selezione android.R.layout.simple_list_item_multiple_choice visualizza a fianco di ogni voce un
checkbox (simile all‟item_checked) che si attiva in corrispondenza della selezione android.R.layout.simple_list_item_activated_1 applica una colore di sfondo all‟item
selezionato.
E‟ inoltre possibile creare layout personalizzati contenenti la rappresentazione di ogni singola riga tramite la combinazione arbitraria di più TextView uno per ogni campo del record.
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 51
Note
Tutti questi layout modificano esclusivamente l‟aspetto grafico degli item. Per abilitare / disabilitare la selezione multipla occorre impostare sul ListView : android:choiceMode="multipleChoice" // oppure single choice
L‟utilizzo di list_item_checked o list_item_single_choice che visualizzano graficamente
l‟item selezionato, rendono inutile il listSelector per l‟assegnazione di un colore di sfondo all‟item
L‟oggetto adapter opera soltanto associato ad oggetti come ListView e Spinner ma non ad esempio con un TextView che può contenere un solo elemento.
Collegamento di ListView ad un ArrayList tramite ArrayAdapter
ArrayList<Studente> list;
ArrayAdapter<String> adapter;
ListView lstStudenti;
protected void onCreate() {
list = new ArrayList<Studente>();
String[] vect = new String[] { "Cuneo", "Alba", "Genola", "Bra" };
list.addAll(Arrays.asList(vect));
adapter = new ArrayAdapter<String>(this, simple_list_item_1, list);
lstStudenti = (ListView) findViewById(R.id.lstStudenti);
lstStudenti.setAdapter(adapter);
lstStudenti.setOnItemClickListener(listener);
adapter.notifyDataSetChanged(); // refresh della visualizzazione
Evento ListView.onItemClick
Il metodo di evento onItemClick presenta quattro parametri aventi i seguenti default names:
parent riferimento al controllo che ha scatenato l‟evento (cioè puntatore al ListView)
v Pointer to the item tha was clicked. Riferimento al “personal row_layout” relativo
all’item cliccato. Tramite il metodo v.findViewById(R.id.txtMyTextBox) è
possibile accedere ad un qualsiasi widget interno al row_layout dell‟item cliccato. pos La posizione assoluta dell‟elemento attualmente cliccato.
id The row id of the item that was clicked.
ListView.OnItemClickListener listener = new ListView.OnItemClickListener()
{
public void onItemClick(AdapterView<?> listView, View layout, int pos, long id)
String item = listView.getItemAtPosition(pos).toString();
alert(item);
CheckBox chk = (CheckBox) layout.findViewById(R.id.chkMyCheckBox);
chk.setChecked(!chk.isChecked());
}
};
Il metodo getItemAtPosition restitusice un object, per cui occorre utilizzare .toString().
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 52
Evento ListView.onItemLongClick
Il metodo di evento onItemLongClick presenta gli stessi parametri dell‟evento precedente e deve
ritornare un booleano che ha il seguente significato:
return false fa si che che, dopo onItemLongClick venga generato anche onItemClick
return true fa si che che onItemClick non venga generato
Accesso alle voci selezionate in caso di "multipleChoice"
In caso di abilitazione alla selezione multipla, all‟interno dell‟evento onItemClick è possibile
individuare direttamente le voci selezionate utilizzando il seguente codice:
SparseBooleanArray sparseBooleanArray = lstContatti.getCheckedItemPositions();
String s = "Elementi selezionati: " ;
for(int i=0; i < sparseBooleanArray.size(); i++) {
if(sparseBooleanArray.valueAt(i))
s += sparseBooleanArray.keyAt(i) + "; ";
}
alert(s);
Lo SparseBooleanArray serve a mappare numeri interi in booleani in modo molto efficiente.
Spinner
Implementa il tipico Combo Box. Il funzionamento è praticamente identico rispetto al ListView anche se esteticamente meno bello. Occorre definire una sorgente dati di tipo ArrayList ed un apposito adapter. L‟evento non è più setOnItemClickListener ma setOnItemSelectedListener
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 = parent.getItemAtPosition(pos).toString();
alert(item);
}
public void onNothingSelected(AdapterView<?> arg0) { }
});
GridView
Le GridView operano in modo simile rispetto alle ListView. La differenza sta nel fatto, nel caso della GridView, i dati dell‟ArrayList vengono spalmati sulle varie colonne.
Dal layout l‟unica cosa da definire è il numero di colonne: android:numColumns="3"
E‟ anche possibile definire android:numColumns="auto_fit" specificando però columnWidth
final GridView grid = (GridView) findViewById(R.id.grid);
grid.setAdapter(adapter);
Anche la procedura di risposta al click è la stessa identica del ListView. 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 listView.
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 53
Creazione di un Adapter personalizzato
Per la visualizzazione di un elenco di dati, in alternativa ad un Layout costruito dinamicamente da codice, è possibile creare tramite Designer un layout personalizzato in cui si definisce la struttura grafica di ogni singolo record che compone l‟elenco. Per collegare il layout personalizzato alla sorgente dati occorre ancora definire un adapter personalizzato che funge da Binding tra il layout di visualizzazione e la sorgente dati. In questo modo si definisce la struttura grafica del record tramite Designer senza dover far ricorso alla creazione dinamica con codice java. Step 1 Definizione di un rowlayout personalizzato
Come layout base di ogni singolo record si può utilizzare un Linear Layout orizzontale avente: larghezza = match_parent altezza = wrap_content
Al suo interno si posizionano tutti i vari widget relativi al record da visualizzare, ciascuno con un suo ID. Step 2 Definizione di un Adapter personalizzato per la gestione dei dati
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 occorre implementare soltanto il metodo
getView(int position, View layout, ViewGroup parent)
Questo metodo viene richiamato automaticamente nel momento in cui, tramite il metodo setAdapter(), si collega un controllo di visualizzazione al Personal Adapter.
In questo momento il Run Time provvede 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. NOTA: Il controllo linkato deve assolutamente avere la proprietà layout_height="match_parent"
In caso di wrap_content l‟evento viene richiamato più volte per ogni record.
I tre parametri iniettati al metodo getView 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 personalizzato in modo che, nei giri successivi, il riferimento possa essere passato come secondo parametro al metodo stesso, evitando di re-istanziarlo ogni volta. public class MyAdapter extends ArrayAdapter<Studente> {
private final Context context;
private final int layout;
private final List<Studente> list;
public MyAdapter(Context context, int layout, List<Studente> list) {
super(context, layout, list);
this.context = context;
this.layout = layout;
this.list = list;
}
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 54
public View getView(int position, View layout, ViewGroup parent) {
if(layout==null) {
LayoutInflater inflater = (LayoutInflater)
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
layout = inflater.inflate(this.layout, parent, false);
}
Contatto contatto = list.get(position);
TextView txtId = (TextView) layout.findViewById(R.id.txtId);
TextView txtName = (TextView) layout.findViewById(R.id.txtName);
txtId.setText(Integer.toString(contatto.getId()));
txtName.setText(contatto.getName());
return layout;
}
Nel momento in cui il chiamante esegue il metodo listView.setAdapter(adapter), l‟adapter
provvede automaticamente a leggere l‟oggetto list (passato al costruttore) e a richiamare n volte il
metodo getView() passandogli i seguenti parametri:
come primo parametro un indice via via crescente che rappresenta l‟indice del contatto da visualizzare
come secondo parametro null se non ci sono layout liberi da poter utilizzare
il terzo parametro è poco chiaro Non vengono però caricati TUTTI i record contenuti all‟interno di list ma, nel momento in cui l‟area visibile del listView risulta “piena”, il meccanismo si interrompe. L'inflating è il meccanismo che permette di istanziare un'oggetto a partire da una risorsa XML.
In pratica :
se il parametro layout è null, tramite l‟inflater viene creato un nuovo oggetto row_layout in cui vengono caricati tutti i dati del contatto corrente.
Al termine l‟oggetto viene restituito al chiamante che provvederà ad appenderlo al ListView.
Finchè il ListView non risulta “pieno”, ad ogni giro viene creato un nuovo row_layout ed appeso al ListView
Nel momento in cui l‟utente eseguirà uno scroll verso il basso, occorrerà caricare un nuovo contatto, però al contempo il primo contatto in alto uscirà dall‟area di visualizzazione, per cui l‟applicazione richiama il metodo getView() passandogli come layout il layout ormai libero in cui era visualizzato il primo item Note
1) Una importante conseguenza di quanto descritto sopra è che all‟interno di un Adapter personalizzato non è pensabile gestire campi di memoria (ad esempio un checkbox) scollegati dalla sorgente dati, che serve appunto a “memorizzare” i dati mentre l‟Adapter funge soltanto da visualizzatore
2) All‟interno degli Adapter personalizzati NON è consentito l‟utilizzo dei Toast
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 55
Activity Principale
Dichiarazione e istanza dell‟Adapter hanno la stessa firma vista per l‟Adapter base ma non sono tipizzate
ArrayList<Studente> list;
ListView lstStudenti;
Adapter adapter;
protected void onCreate() {
list = new ArrayList<Studente>();
caricaLista();
adapter = new Adapter(this, R.layout.row_layout, list);
lstStudenti = (ListView) findViewById(R.id.lstStudenti);
lstStudenti.setAdapter(adapter);
lstStudenti.setOnItemClickListener(listener);
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;
alert("posiz="+pos + " nome="+studente.getName());
}
Nota sul ClickListener
Attenzione che se il layout dell’adapter personalizzato contiene un check box, il click listener del checkbox sovrascrive il click listener del ListView che non viene più eseguito. Il problema può essere risolto impostando all‟interno dei checkbox del Personal Adapter le seguenti proprietà:
android:focusable="false"
android:focusableInTouchMode="false"
Pulsanti interni all’Adapter
Su un pulsante definito all‟interno di un Adapter è possibile associare in “forma breve” (tramite Designer) un listener definito all‟interno dell‟Activity principale oppure, in alternativa, è anche possibile definire il listener direttamente all‟interno dell‟Adapter.
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 56
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.
L‟oggetto Editor consente di modificare le singole variabili di un gruppo di SharedPreferences.
public void btnAggiungiPreferenza(){
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
alert("prefernza aggiunta correttamente"); }
public void btnLeggiPreferenza(){
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);
String s = nome + " - " + cognome + " - " + Integer.toString(eta);
// cognome restituisce “nessun valore”;
alert(s);
}
Gestione dei thread
Utilizzo di un thread generico
Per gestire un thread occorre istanziare due oggetti:
un oggetto Runnable che consente di definire una singola procedura all‟interno di un thread
separato. Runnable è una classe Astratta la cui istanza deve implementare il metodo Run, che è il metodo che consente di definire la procedura da eseguire in un thread separato
un oggetto Handler che è un oggetto contenuto nel namespace osHandler che consente di
controllare il thread definito all‟interno di Runnable (avvio / arresto / terminazione).
La procedura invocata all‟interno di un thread separato può comunque accedere ai controlli dell‟interfaccia grafica.
private Handler threadHandler;
final long INTERVALLO = 1000;
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 57
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 1000 ms
// Si aspetta come primo parametro una ISTANZA della classe Runnable
threadHandler.postDelayed(aggiornaGrafica, INTERVALLO);
}
Terminate le istruzioni da eseguire il thread si arresterebbe. Volendo ottenere una esecuzione ciclica, occorre aggiungere un comando di riavvio in modo ricorsivo in coda al thread stesso:
private Runnable aggiornaGrafica = new Runnable() {
@Override
public void run() {
alert("tick"(;
threadHandler.postDelayed(this, INTERVALLO);
}
};
// Arresto del thread
public void btnArresta_Click(View v){
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
Sleep di un thread
Esistono due metodi per forzare un thread in stato di sleep:
SystemClock.sleep(100);
Thread.sleep(100);
Nessuno dei però può essere utilizzato all‟interno del thread principale di tipo UI, ma solo ed esclusivamente all‟interno di thread separati. Il secondo interrompe anche il refresh grafico della pagina. Abbastanza pericoloso.
L’oggetto Timer
Molto più complesso del 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
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)); }
}
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 58
Basic Activity
La basic activity rappresenta un buon punto di partenza per la realizzazione di applicazioni più moderne. Il layout principale è costituito da un CoordinatorLayout, un Frame Layout potenziato.
Il CoordinatorLayout consente di inserire:
una Toolbar personalizzabile
un FloatingActionButton cioè un pulsante fluttuante posizionato “al di sopra” degli elementi
del layout.
La caratteristica principale del CoordinatorLayout è la possibilità di coordinare l‟animazione e la transizione dei controlli interni, come ad esempio la possibilità di accompagnare un restringimento della Toolbar con corrispondente risalita nell‟interfaccia di una eventuale ListView sottostante. Oppure la possibilità di ancorare il Floating Button ad un altro widget del layout e muoverlo insieme ad esso. Il Floating Button si sposta automaticamente verso l‟alto in corrispondenza della comparsa di uno SnackBar.
Per approfondimenti sul CoordinatorLayout con esempi di utilizzo dei FAB si rimanda al link: https://www.androidauthority.com/using-coordinatorlayout-android-apps-703720/
Material Design: gli oggetti AppBarLayout e Toolbar
Le Activity che ereditano da AppCompatActivity consentono di personalizzare la barra
dell‟applicazione sulle base delle linee guida dettate dal cosiddetto Material Desgin.
L‟oggetto Toolbar è una estensione della vecchia ActionBar (barra del titolo) controllata
esclusivamente dal framework. La Toolbar è disponibile al programmatore sotto forma di widget e deve essere inserita all‟interno di un layout (qualunque layout, anche annidato). Tipicamente viene utilizzata all‟interno di un layout di tipo AppBarLayout
L‟applicazione può „decidere‟ quale Toolbar utilizzare in certo istante utilizzando il seguente metodo :
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
this.setSupportActionBar(toolbar);
Una tool bar può contenere i seguenti elementi:
Navigation Button che può essere una freccia in alto, un menu di navigazione
(DrawerButton), un pulsante di chiusura, di collapse, o altro.
Logo Image che può avere una altezza arbitraria.
Titolo e sottotitolo Se si utilizza un logo, è consigliabile omettere titolo e sottotitolo
Eventuali Custom Views che possono avere una altezza arbitraria.
Action Menu caratterizzato dai tre puntini verticali di destra e contenente il menù settings
Option Menu
All‟interno dell‟applicazione è definito anche un menu_main.xml avente al suo interno una unico item
denominato Settings con id =action_settings
In corrispondenza dell‟avvio l‟evento onCreateOptionsMenu associa il menù al contenuto del file xml.
In corrispondenza del click sulle voci di menù verrà eseguito l‟evento onOptionsItemSelected.
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 59
L’oggetto FloatingActionButton (FAB)
Il Floating Action Button (FAB) è un pulsante circolare /sovrapposto ai controlli sottostanti al quale è possibile associare una azione particolarmente importante all‟interno del layout in uso (ad esempio la scrittura di una nuova mail). Come width e height si imposta di solito il valore wrap_content. Come immane da utilizzare come icona richiede escusivamente una immagine .png All‟interno di un layout è possibile inserire contemporaneamente più FloatingActionButtons
Il FloatingActionButton dell‟esempio ha come icona la tipica icona di invio mail (definita all‟interno
della cartella drawable):
app:srcCompat="@android:drawable/ic_dialog_email"
Per modificare icona e colore del Floating Action Button da designer:
android:src="@mipmap/myicon"
app:backgroundTint="@android:color/white"
Per modificare il colore del Floating Action Button da codice:
fab.setBackgroundTintList(ColorStateList.valueOf(Color.BLUE));
fab.setRippleColor(Color.RED); // when pressed
in corrispondenza del click, visualizza uno Snackbar di esempio :
fab.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) { }
}
La proprietà elevation consente di „sollevare‟ verticalmente l‟oggetto con la possibilità di applicare
ombre o altri effetti.
L’oggetto Snakebar
Introdotto in Android 5 è simile al Toast ma, rispetto al Toast, consente di aggiugere un pulsante di feedbak (tipicamente un pulsante di Annulla).
Lo snackBar compare automaticamente sul lato inferiore del layout e scompare sempre automaticamente dopo un certo tempo oppure quando si clicca sul pulsante di feedback.
Il pulsante di feedback viene aggiunto nel momento in cui si assegna un .setAction() allo snakebar
Snakebar senza action
Snackbar.make(view, msg, Snackbar.LENGTH_LONG).show();
Il primo parametro è un puntatore al layout all‟interno del quale far comparire lo snackBar (tipicamente il layout genitore). E‟ possibile passare come parametro un qualunque widget nel qual caso lo snackbar provvederà da solo ad individuare il genitore del widget ricevuto: Il secondo parametro rappresenta il testo da visaulizzare all‟interno dello snackbar Il terzo parametro rappresenta il tempo di visaulizzare dello snackbar public void onClick(View v) {
Snackbar.make(v, "Testo dello sncakbar", Snackbar.LENGTH_LONG).show();
}
Il metodo statico .make() ritorna un puntatore allo snackbar appena creato.
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 60
Snakebar con action
// Salvo dentro snack il puntatore allo snackbar
Snackbar snack = Snackbar.make(v, “Messaggio Inviato”, Snackbar.LENGTH_LONG);
// Accedo al contenuto dello snackbar (area interna)
View snackView = snack.getView();
// cambio il colore di sfondo dell’area interna allo snackbar
snackView.setBackgroundColor(Color.GRAY);
// Accedo al testo dello snackbar e lo imposto a giallo
TextView txt = snackView.findViewById(android.support.design.R.id.snackbar_text);
txt.setTextColor(Color.YELLOW); // colore del testo dello snackbar
// Aggiungo un pulsante ed imposto il colore del testo a rosso
// passando null cume 2° parametro viene inserito il pulsante ma non viene associato un listener snack.setAction("Annulla", myListener);
snack.setActionTextColor(Color.RED);
snack.show();
final View.OnClickListener myListener = new View.OnClickListener(){
@Override
public void onClick(View v) {
alert("operazione annullata"); }
};
Navigation Drawer Activity
Il layout principale è costituito da un DrawerLayout (layout a cassetto) che è equivalente ad un
CoordinatorLayout con la possibilità di gestire al suo interno un widget particolare di tipo
NavigationView. All‟interno del DrawerLayout sono presenti:
Un layout di nome app_bar_main.xml che è identico al coordinator layout principale della Basic
Activity.
un widget di tipo NavigationView che punta al layout denominato nav_header_main.xml
Il NavigationView è un menu a comparsa che viene visualizzato in corrispondenza del click sulla
„cassettiera‟ di sinistra (Drawer Button) dell‟Action Bar .
Il menu a comparsa NavigationView è strutturato in due parti:
Una parte superiore di intestazione a sfondo verde strutturata sotto forma di Linear Layout
verticale in cui l‟utente può inserire uno sotto l‟altro i widget desiderati
Una parte inferiore a sfondo bianco contenente il menu vero e proprio, memorizzato all‟interno di un file menu.xml
All‟interno del layout principale, all‟interno del widget NavigationView, le ultime due righe consentono
di specificare :
il layout da utilizzare nella parte superiore di intestazione del NavigationView
il menu da utilizzare nella parte inferiore del NavigationView relativa ai comandi
app:headerLayout="@layout/nav_header_main"
app:menu="@menu/activity_main_drawer"
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 61
Il menù activity_main_drawer è a sua volta strutturato in due parti:
Nella prima parte ci sono 4 radio button alternativi rappresentati non con il semplice pallino ma tramite una icona che sovrascrive il pallino. La voce selezionata viene visualizzata a sfondo scuro e rimane “selezionata” anche dopo che viene chiuso il NavigationView. Essendo i pulsanti alternativi (checkableBehavior=”single”), quando si seleziona una nuova voce, la voce
precedente viene automaticamente deselezionata (riprende lo sfondo chiaro).
Nella seconda parte c‟è un sottomenù Communicate costituito da alcuni comandi indipendenti
fra loro che NON mantengono lo stato dopo che sono stati premuti. Codice di Visualizzazione del Drawer Button e apertura del menù
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(this, drawer,
toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
drawer.addDrawerListener(toggle);
toggle.syncState();
La classe ActionBarDrawerToggle consente di associare un Drawer Layout con una Toolbar
interna ad una Action Bar dell‟Activity corrente, con conseguente visualizzazione del Drawer Button all‟interno della toolbar. Il Navigation Layout verrà automaticamente aperto in corrispondenza del click sul Drawer Button, e automaticamente chiuso in corrsipondenza del click in un‟area dell‟Activity oppure in corrispondenza della seleziona di una delle voci del menu. Presenta i seguenti parametri:
1. Il contest (Activity) corrente 2. Il puntatore al Drawer Layout principale 3. Il puntatore alla Toolbar che si desidera associare con il Drawer Layout 4. Puntatore alla Stringa (costante) da visualizzare in fase di aperture del Drawer Layout 5. Puntatore alla Stringa (costante) da visualizzare in fase di chiusura del Drawer Layout
Il richiamo del metodo .syncState() è obbligatorio e serve a sincronizzare il collegamento (aperture /
chiusura) fra Drawer Layout e Toolbar. Gestione degli eventi
La gestione del click sulle varie voci del menu viene gestita mediante un listener di tipo onNavigationItemSelected che riceve come parametro l‟item su cui è stato eseguito il click :
NavigationView nav = (NavigationView) findViewById(R.id.nav_view);
nav.setNavigationItemSelectedListener(this);
public boolean onNavigationItemSelected(MenuItem item) { }
Al termine della procedura di evento si provvede alla chiusura manuale del menù a comparsa mediante il metodo drawer.closeDrawer().
Evento onBackPressed
Questo evento viene generato in corrispondenza del click sul pulsante BACK dello smartphone. Se il menu a comparsa è aperto viene chiuso, altrimenti viene richiamato il metodo della superclasse.
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 62
La gestione dei permessi
Ogni app deve dichiarare all‟inerno del manifest l‟elenco dei permessi di cui ha necessità. Esempi:
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.CALL_PHONE"/>
<uses-permission android:name="android.permission.SEND_SMS"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
Se la app richiede permessi “normali” (che non mettono a rischiola privacy dell‟utente o il funzionamento del dispositivo), automaticamente il sistema concede i pemessi richiesti. Nel caso invece in cui I permessi richiesti siano ritenuti rischiosi per la privacy o per il dispositivo, dalla versione 6.0 (API 23) in avanti occorre prevedere una esplicita conferma da parte dell‟utente. Nei dispositivi con SO antecedente alla versione 23, il consenso veniva richiesto in fase di installazione. If the user clicks Accept, all permissions the app requests are granted. If the user denies the permissions request, the system cancels the installation of the app. Nei dispositivi con SO maggiore o uguale alla versione 23, il consenso deve essere esplicitamente richiesto dalla app in fase di runtime. La app deve cioè provvedere a visualizzare una finestra di dialogo di sistema che elenca i permessi necessari e visualizza i pulsanti DENY and ALLOW. Se l‟utente spunta l‟opzione Never ask again, l‟ozione selezionata (DENY oppure ALLOW) l‟opzione viene mantenuta anche nei successive utilizzi. Per richiedere il consenso all‟utente si può utilizzare il seguente codice:
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
== PackageManager.PERMISSION_GRANTED)
lm.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 10, this);
else
ActivityCompat.requestPermissions(this,
new String[]{ Manifest.permission.ACCESS_FINE_LOCATION},
ACCESS_FINE_LOCATION_REQUEST_CODE);
dove ACCESS_FINE_LOCATION_REQUEST_CODE è una semplice costante utente che assegna un codice
alla richiesta di permesso.
Il metodo requestPermissions(), al termine, genera l‟evento onRequestPermissionsResult a cui
inietta all‟interno del vettore grantResults il contenuto della decisione presa dall‟utente:
public void onRequestPermissionsResult(int requestCode, String permissions[],
int[] grantResults) {
switch (requestCode) {
case ACCESS_FINE_LOCATION_REQUEST_CODE: {
if (grantResults.length > 0 &&
grantResults[0] == PackageManager.PERMISSION_GRANTED) {
try {
lm.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 10, this);
}
catch(SecurityException ex ) {
Toast.makeText(this, ex.getMessage(), Toast.LENGTH_LONG).show();
}
}
else
Toast.makeText(this, "Permessi GPS negati. La app verrà terminata");
return;
}
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 63
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"/>
Il primo permesso in realtà contempla anche il secondo L’oggetto Runnable
L‟oggetto Runnable è l‟oggetto base di java per la realizzazione del multi 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. Dichiarazione di una classe che eredita da AsyncTask
La classe astratta AsynkTask è una Typed Class (come ad esempio ArrayList) definita però da tre tipizzazioni:
AsyncTask<TParams, TProgress, TResult>
TParams rappresenta il tipo delle variabili che il chiamante può passare al metodo execute (che a sua
volta richiama il metodo 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() Esempio di dichiarazione di una classe InviaRichiesta che eredita da AysncTask:
public class InviaRichiesta 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.
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 64
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 astratto che deve obbligatoriamente essere gestito. Gli altri tre metodi sono callback richiamate rispettivamente
in corrispondenza dell‟avvio del thread,
a tempo durante l‟esecuzione del thread,
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. protected void onPreExecute() {
progress = ProgressDialog.show(this, "Connecting...", "Please wait!");
}
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. Si tratta di una
callback richiamata al termine del metodo doInBackground (cioè in corrispondenza del ricevimento
dei dati dal server).
Il parametro result viene iniettato da AsynkTask. Utilizzato per elaborare il risultato ricevuto dal
server scrivendolo all‟interno di un Object ricevuto dall‟Activity principale.
MainActivity: Istanza della classe ed invo della richiesta
L‟activity principale dovrà istanziare la classe InviaRichiesta e richiamare il metodo execute che
provvede ad attivare il metodo doInBackground della classe AsynkTask.
I parametri possono essere passati indifferentemente al costruttore (che provvederà a salvarli all‟interno di appositi campi privati) oppure direttamente al metodo execute().
InviaRichiesta inviaRichiesta = new InviaRichiesta(url)
inviaRichiesta.execute(“get”, "/cercaStudente", "nomeStudente=pippo");
Per certi versi è preferibile passare i parametri al costruttore perché accetta anche parametri di tipo diverso, mentre execute accetta soltanto ellissi con parametri tutti dello stesso tipo.
Se non si passano parametri a execute, nella dichiarazione della classe InviaRichiesta come primo
parametro TParams si può impostare Void e doInBackground riceverà una ellissi vuota (Void … args).
Se il thread deve effettuare un Toast, occorre passare anche il context.
Se l‟unico metodo utilizzato all‟interno della classe InviaRichiesta è execute() non serve nemmeno salvare il riferimento all‟interno di una variabile
new InviaRichiesta(myParam).execute(“get”, "/cercaStudente",
"nomeStudente=pippo");
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 65
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 InviaRichiesta extends AsyncTask <Void, Void, String> {
private Context context;
private String url;
// costruttore
public InviaRichiesta (String url, Context context) {
this.url = url;
this.context = context;
}
protected String doInBackground(String ... args) {
String ris = "";
String metodo = args[0];
String risorsa = args[1];
String parametro = args[2];
url += 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); }
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 66
// 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
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 metodo conn.setDoOutput(true) abilita la scrittura di dati sull connessione e trasforma la
chiamata da GET a POST
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 67
public class InviaRichiesta extends AsyncTask<Void, Void, String> {
String url;
private ArrayList<Pair> get = new ArrayList<Pair>();
private ArrayList<Pair> post = new ArrayList<Pair>();
public InviaRichiesta(String url) {
this.url=url;
}
protected String doInBackground(Void ... args) {
try{
// lettura dei parametri contenuti nel vettore "get" e concatenamento alla url
for(Pair item : this.get) {
String divisor;
if(url.indexOf('?') == -1)
divisor="?";
else
divisor="&";
url += divisor + item.first + "=" +Uri.encode(item.second.toString())
}
// parsing della url testuale nel corrispondente oggetto
URL _url = new URL(url);
// Apertura connessione TCP
HttpURLConnection conn = (HttpURLConnection) _url.openConnection();
// Aggiunta eventuali parametri POST e impostazione della modalità POST
if (!this.post.isEmpty()) {
// imposta method POST e abilita la scrittura dei par sulla connessione
conn.setDoOutput(true);
String data = "";
// lettura dei parametri contenuti nel vettore "post"
for(Pair item : this.post)
data += item.first + "=" + Uri.encode(item.second.toString()) +"&";
data = data.substring(0, data.length() - 1);
OutputStreamWriter wr = new OutputStreamWriter(conn.getOutputStream());
wr.write(data);
wr.flush();
wr.close();
}
// invio della richiesta
conn.connect();
// lettura della risposta
InputStream in = null;
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK)
in = conn.getInputStream(); // bloccante
else {
in = conn.getErrorStream();
ris = "errore server:\n"+Integer.toString(conn.getResponseCode())+"-";
}
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
ris+=reader.readLine();
reader.close();
in.close();
}
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 68
catch (Exception ex) {
ris="ERRORE connessione al sever - " + ex.getMessage();
}
return ris;
}
Note:
Il conn.connect() non è indispensabile. Se non lo si fa, viene fatto in automatico da
getInputStream()
le istruzioni di elaborazione della risposta sono leggermente modificate rispetto alla soluzione
precedente in quanto ora ricevono un oggetto URLConnection e non 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 InviaRichiesta.
In questo modo : 1) si evita di dover passare al costruttore i controlli da aggiornare ed il context. L‟unico parametro da
passare al costruttore oppure al metodo execute() sarà la risorsa da richiedere al server 2) Poiché il metodo onPostExecute() deve elaborare lo stream json ricevuto dal server, il suo
contenuto dipende strettamente dal tipo di json ricevuto. Quindi se si dovessero inviare più richieste relative a stream json differenti (elenco, dettagli, etc.), occorrerebbe definire una classe inviaRichiesta per ogni singola richiesta. Oppure definire un‟unica classe e all‟interno del metodo onPostExecute() eseguire uno switch() per identificare la richiesta. Spostando il metodo onPostExecute() dalla classe alla singola istanza, si risolve questo problema. Ogni istanza avrà il proprio metodo onPostExecute() che intercettarà il proprio stream json.
Gestione 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
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 69
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,
Gestione del Layout all‟interno dell‟Activity principale
public void onCreate(Bundle savedInstanceState) {
final ListView lstCitta= (ListView) findViewById(R.id.lstCitta);
final TextView txtError = (TextView) findViewById(R.id.txtError);
final ArrayList<String> listaCitta = new ArrayList<String>();
final ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1, listaCitta);
lstCitta.setAdapter(adapter);
InviaRichiesta request = new InviaRichiesta(listCitta, txtErrore, adapter)
request.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} \
]}
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 70
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());
}
}
Richiesta di una immagine al server
public class InviaRichiestaImg extends AsyncTask <Void, Void, Bitmap> {
private String url;
public InviaRichiestaImg (String url) {
this.url = url;
}
@Override
protected Bitmap doInBackground(Void ... args) {
InputStream in=null;
Bitmap bitmap = null;
try {
URL _url = new URL(url);
HttpURLConnection conn = (HttpURLConnection) _url.openConnection();
/* oppure:
URLConnection conn = url.openConnection();
if (!(conn instanceof HttpURLConnection))
throw new IOException("Not an HTTP connection");
HttpURLConnection httpConn = (HttpURLConnection) conn;
*/
conn.setAllowUserInteraction(false);
conn.setInstanceFollowRedirects(true);
conn.setRequestMethod("GET"); // default
conn.connect();
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK){
in = conn.getInputStream();
bitmap = BitmapFactory.decodeStream(in);
in.close();
}
else{
// In caso di NOK in==null : la funzione restituisce un bitmap null
}
}
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 71
catch (IOException ex) {
//ex.printStackTrace("Internal Server Error - " + ex.getMessage());
ex.printStackTrace();
}
return bitmap;
}
main
final ImageView img = (ImageView) v.findViewById(R.id.img);
if (path != "") {
InviaRichiestaImg request = new InviaRichiestaImg(url+"/" + path){
@Override
protected void onPostExecute(Bitmap bitmap){
if(bitmap != null)
img.setImageBitmap(bitmap);
}
};
request.execute();
Gestione di uno stream XML
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).
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 root = xmlDoc.getDocumentElement(); (Restituisce un Element)
Node root = xmlDoc.getChildNodes().item(0);
Node root = xmlDoc.getElementsByTagName("bookstore")[0];
Node root = 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
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 72
.getChildNodes(); collezione (NodeList) dei figli del nodo corrente
.getFirstChild(); primo figlio del nodo corrente
.getLastChild(); ultimo figlio del nodo corrente
.getNextSibling(); prossimo fratello del nodo corrente
.getPreviousSibling(); fratello precedente
.getParentNode(); padre del nodo corrente
.getOwnerDocument() 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 (restituita da .getChildNodes())
int getLength() numero dei nodi
Node item(int index) accede all‟elemento iesimo (a base 0)
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
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 73
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 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
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
Element node = xmlDoc.createElement("nodeName");
root.appendChild(node);
node.setTextContent("valore");
// oppure
Text foglia = xmlDoc.createTextNode("salve mondo"); // Nodo di tipo foglia
node.appendChild(foglia);
Esempio di creazione di un nuovo albero
<A id="aa">
<B/>
<C>text</C>
</A>
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 74
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);
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 uno dei seguenti oggetti:
un File XML
un oggetto InputStream
un oggetto InputSource che è un stream testuale „specializzato‟ per xml, 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();
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 75
Accesso a SQLite in locale 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();
}
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 76
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++; }
}
Aggiunta di nuovo studente : il metodo db.insert()
public static void aggiungiStudente(Studente studente) {
final SQLiteDatabase db = open();
String name = sqlEscape(studente.getNome());
String email = sqlEscape(studente.getEmail());
ContentValues record = new ContentValues();
record.put(STUDENTE_NOME, name);
record.put(STUDENTE_EMAIL, email);
db.insert(TABELLA_STUDENTI, null, record);
db.close();
}
Query di ricerca : il metodo db.query()
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;
}
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();
Let's suppose you have a table named foo where all columns either allow NULL
values or have defaults.
In some SQL implementations, this would be valid SQL: INSERT INTO foo;
That's not valid in SQLite. You have to have at least one column specified. Il secondo parametro serve a specificare il nome della colonna a cui assegnare NULL per impedire che l’operazione vada in errore.
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 77
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=?";
modifica di uno 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;
}
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();
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 78
Utilizzo dei sensori
Simulazione delle coordinate GPS
L‟emulatore non può leggere le coordinate GPS, che possono comunque essere impostate :
tramite telnet telnet localhost 5555 (porta di ascolto del dispositivo)
tramite DDMS / Emulator Control geo fix 44.552304, 7.762786
nelle versioni più recenti (android studio 3.0) tramite i comandi a destra dell‟emulatore
Le coordinate GPS vengono azzerate ogni volta che si ricarica il programma nell‟emulatore. Android Manifest
Nell‟android manifest occorre richiedere i diritti per utilizzare il sensore GPS e/o per accedere alla posizione sulla base della cella WiFi a cui si è agganciati.
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
ACCESS_COARSE_LOCATION allows an app to access approximate location derived from network
location sources such as cell towers (cella su cui è agganciato il servizio dati) oppure Wi-Fi. Se si intende accedere alla psozione tramite WiFi occorre anche richiedere il permesso ACCESS_WIFI_STATE
Inoltre, per poter accedere alle google maps: <uses-permission android:name="android.permission.INTERNET"/>
Avvio del sensore GPS
L‟Activity principale, oltre ad ereditare da Activity, deve implementare anche l‟interfaccia LocationListener la quale richiede l‟implementazione di quattro metodi astratti (implementabili al
solito con ALT + INVIO) di cui il più importante è onLocationChanged
Prima di andare a leggere la posizione occorre (sull‟onCreate oppure su apposito pulsante) abilitare il sistema di acquisizione: LocationManager lm = (LocationManager) getSystemService(LOCATION_SERVICE);
// getting GPS status
boolean isGPSEnabled = lm.isProviderEnabled(LocationManager.GPS_PROVIDER);
// getting network status
boolean isNetworkEnabled = lm.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
// Se il sistema di localizzazione GPS non è abilitato
if (!lm.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
// Invio un intent implicito per l'abilitazione del sistema di localizzazione
Intent i = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
startActivity(i);
}
else {
String provider = LocationManager.GPS_PROVIDER;
try {
lm.requestLocationUpdates(provider, 1000, 10, this); // Gestione PERMESSI
abilitato = true;
}
catch(SecurityException ex ) {
Toast.makeText(this, "Errore accesso GPS", Toast.LENGTH_LONG).show();
}
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 79
Il metodo lm.isProviderEnabled consente di verificare se è abilitato il GPS oppure se è abilitata la
lettura COARSE relativa alla cella WiFi. Nel caso in cui il GPS non sia attivato, si può richiamare tramite intent implicito un‟apposita Activity di sistema che provvede a richiedere all‟utente se acconsente all‟utilizzo del sistema di localizzazione:
In alternative si potrebbe eseguire una gestione più fine in cui, se non è attivo il GPS ma è attivo il COARSE, si accede alla posizione tramite COARSE nel modo seguente :
Criteria criteria = new Criteria();
// Sets the criteria for a fine and low power provider
criteria.setAccuracy(Criteria.ACCURACY_FINE);
criteria.setPowerRequirement(Criteria.POWER_LOW);
// Scelgo il provider, tra quelli attivi, che meglio soddisfa i criteri impostati
String provider = lm.getBestProvider(criteria, true);
In caso di almeno un provider 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 provider da utilizzare per le registrazioni (COARSE 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)
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();
String s = Double.toString(lat) + " - " + Double.toString(lng);
Toast.makeText(this,"s", Toast.LENGTH_LONG).show();
}
Visualizzazione del percorso sulle Google Maps
Per visualizzare una posizione o un percorso sulle google maps si può accedere dirattamente alle ggogle maps passando la posizione oppure punto di partenza e punto di arrivo del percorso:
String addr = "http:/www.google.com/maps/@45,7.7,14z"; // zoom
String addr = "http:/www.google.com/maps?saddr="+coord1+"&daddr="+coord2;
Dopo di che, in entrambii casi, si può utilizzare il seguente codice:
Uri uri = Uri.parse(addr);
Intent intent=new Intent(Intent.ACTION_VIEW, uri);
// intent.setPackage("com.google.android.apps.maps");
// Controllo se il pacchetto per visualizzare mappa è stato caricato
if(intent.resolveActivity(getPackageManager())!=null)
startActivity(intent);
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 80
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);
Tecnologie - Classe Quinta robertomana.it
Java per Android
pag 81
// 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));
}
Test sulla versione dell’SDK in uso
try {
final PackageInfo info = context.getPackageManager().getPackageInfo(
context.getPackageName(), 0);
targetSdkVersion = info.applicationInfo.targetSdkVersion;
}
catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
// Android M significa Marshmallow (ver 6.0, API 23)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (targetSdkVersion >= Build.VERSION_CODES.M) {
}
else {
}
}