Top Banner
© 2012 - CEFRIEL Costruire interfacce grafiche complesse Docente: Gabriele Lombardi [email protected]
43

© 2012 - CEFRIEL Costruire interfacce grafiche complesse Docente: Gabriele Lombardi [email protected].

May 01, 2015

Download

Documents

Osvaldo Bucci
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: © 2012 - CEFRIEL Costruire interfacce grafiche complesse Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

© 2012 - CEFRIEL

Costruire interfacce grafiche complesse

Docente: Gabriele [email protected]

Page 2: © 2012 - CEFRIEL Costruire interfacce grafiche complesse Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

© 2012 - CEFRIEL

The present original document was produced by CEFRIEL and the Teacher for the benefit and internal use of this course, and nobody else may claim any right or paternity on it. No right to use the document for any purpose other than the Intended purpose and no right to distribute, disclose, release, furnish or disseminate it or a part of it in any way or form to anyone without the prior express written consent of CEFRIEL and the Teacher.© copyright Cefriel and the Teacher-Milan-Italy-23/06/2008. All rights reserved in accordance with rule of law and international agreements.

Page 3: © 2012 - CEFRIEL Costruire interfacce grafiche complesse Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

© 2012 - CEFRIEL

SommarioSommario

SLIDE CONTENUTO

Progettare e prototipare

Identificare Activity, menu, … Prototipare la GUI

Menu e prefs in XML Come mostrare e organizzare menu e prefs

AsyncTask Gestire task in background

Disegnare Come lavorare su un canvas direttamente

Touch Me Usiamo il touch screen per esplorare

Persistenza su file Salvare contenuti multimediali e non

Page 4: © 2012 - CEFRIEL Costruire interfacce grafiche complesse Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Progettiamo un esploratore di frattali Struttura dell’interfaccia dell’app:

– prevediamo di permettere all’utente la definizione di:•opzioni di generazione del frattale:

– tipo di frattale (nella v1.0 Julia o Mandelbrot, ma estendibile);– eventuali parametri del frattale (Julia ne ha 2, un punto su C).

•preferenze:– profondità di colore (e quindi di calcolo);– tavolozza dei colori; …

– tutto questo in 2 Activity dedicate;– una Activity permetterà di interagire con il frattale;

Per ogni frattale ci piacerebbe offrire:– una descrizione di come viene calcolato;– una descrizione delle sue origini storiche.

© 2012 - CEFRIEL

FractalSetupActivity

PreferencesActivity

FractalExplorerActivity

FractalComputationTask

Page 5: © 2012 - CEFRIEL Costruire interfacce grafiche complesse Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

GUI per l’attività principale (setup del frattale)

Attenzione alla generalizzazione:– ogni frattale ha parametri suoi;– posizione e zoom sono parametri

che hanno tutti i frattali mostrati;– la lista dei parametri è "dinamica";– vogliamo poter salvare e caricare

dei setup di frattali precedenti;– ogni frattale è diverso… ma uguale!

Disegniamo il modello dei dati:– classe Fractal:

– serializzabile: per poterla salvare;– astratta per crearne tanti diversi;

– classe Parameter:– con info sul parametro (estremi, intero);– per trattare i parametri uniformemente.

© 2012 - CEFRIEL

Tipo

Parametro

Parametro

Info Esplora

Salva Carica

Page 6: © 2012 - CEFRIEL Costruire interfacce grafiche complesse Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

RainbowPalette

Struttura del modello di calcolo

© 2012 - CEFRIEL

Fractal

compute(PointF,Settings)getInfoKey(): intdraw(bmp: Bitmap, level: int, …)

<<interface>>

Callback

rowCompletedlevelCompleted

Parameter

getName(): StringgetMin(): floatgetMax(): floatisInteger(): boolean

Settings

cxcyzoomdepthpalette

Mandelbrot

Julia

Palette

forIndex(index: int, depth: int)

RedPalette

GreenPalette

BluePalette

Page 7: © 2012 - CEFRIEL Costruire interfacce grafiche complesse Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Tipo di frattale e parametri

Scelta del tipo di frattale:– scelta multipla equivalente di una ComboBox:

•Spinner utilizza una lista adapter;– all’evento di selezione del frattale:

•creiamo un’istanza della classe giusta;•eliminiamo i controlli esistenti;•creiamo i controlli nuovi per i parametri;

Controlli dei parametri:– supportiamo solo parametri float o int;– supportiamo estremi min e max:

•definendo il tipo di input come decimale;– possiamo usare un EditText oppure una SeekBar:

•supporta solo interi tra 0 e N;•lavoriamo con una percentuale… poi

scaliamo e trasliamo noi i sistema di riferimento.

© 2012 - CEFRIEL

Page 8: © 2012 - CEFRIEL Costruire interfacce grafiche complesse Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Tipo di frattale e parametri In onCreate preparo lo spinner con un adapter. Se un frattale viene scelto:

– creo l’istanza della classe Fractal giusta;– aggiorno le componenti per i parametri.

© 2012 - CEFRIEL

vFractalName = (Spinner)findViewById(R.id.main_fractalName);// Imposto l’adattatore per la lista:ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, getFractalNames()); adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);vFractalName.setAdapter(adapter); vFractalName.setSelection(0);

vFractalName.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override // Se scelto creo il frattale e aggiorno i parametri: public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) { try { fractal = fractalClasses[i].getConstructor().newInstance(); updateParameters(); } catch (Exception e) { Log.e(TAG, "Cannot create the fractal", e); } }

@Override public void onNothingSelected(AdapterView<?> adapterView) { vFractalName.setSelection(0); // Seleziono il mandelbrot:} });

Page 9: © 2012 - CEFRIEL Costruire interfacce grafiche complesse Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Tipo di frattale e parametri

© 2012 - CEFRIEL

private void updateParameters() { vParameters.removeAllViews(); // Svuoto il layout: // Aggiungo i parametri: final Parameter[] parameters = fractal.getParameters(); for (int i=0; i<parameters.length; i++) { final int j = i; // Il parametro: final Parameter p = parameters[j]; TextView label = new TextView(this); // La label: label.setText(p.getName()); vParameters.addView(label); SeekBar bar = new SeekBar(this); // La SeekBar: bar.setMax(100); bar.setProgress((int)((fractal.getParameterValue(j) - p.getMin()) /

(p.getMax() - p.getMin()) * 100)); bar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int ind, boolean b) { fractal.setParameterValue(j, // Cambio il parametro:

(ind / 100.0f) * (p.getMax() - p.getMin()) + p.getMin()); } @Override public void onStartTrackingTouch(SeekBar seekBar) { } @Override public void onStopTrackingTouch(SeekBar seekBar) { } }); vParameters.addView(bar); } }

Page 10: © 2012 - CEFRIEL Costruire interfacce grafiche complesse Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Tipo di frattale e parametri

E se avessimo voluto un campo di testo?– Da configurare come solo numerico:

•supporta numeri con segno e con virgola.– Il resto è uguale, compresa l’etichetta.

© 2012 - CEFRIEL

EditText editor = new EditText(this); // Uso un campo di testo:editor.setInputType(InputType.TYPE_CLASS_NUMBER | // Lo configuro come decimal:

(p.isInteger()? 0: InputType.TYPE_NUMBER_FLAG_DECIMAL) |InputType.TYPE_NUMBER_FLAG_SIGNED);

editor.setText(Float.toString(fractal.getParameterValue(j)));editor.setOnKeyListener(new View.OnKeyListener() { // Ne ascolto i cambiamenti:

@Overridepublic boolean onKey(View view, int i, KeyEvent keyEvent) {

try { fractal.setParameterValue(j,Float.parseFloat(((EditText)view).getText().toString())); }

catch (Throwable t) { }return true;

}}); vParameters.addView(editor);

Page 11: © 2012 - CEFRIEL Costruire interfacce grafiche complesse Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Un menu in XML Definiamo un menu usando l’XML (inflater).

© 2012 - CEFRIEL

@Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.main_menu, menu); return super.onCreateOptionsMenu(menu); }

@Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.main_menu_infos: new AlertDialog.Builder(this).setTitle(R.string.fractalInfos) .setMessage(getResources().getString(fractal.getInfoKey())) .setPositiveButton(R.string.btn_close, null).show(); return true; case R.id.main_menu_preferences: startActivity(new Intent(this, PreferencesActivity.class)); return true; case R.id.main_menu_explore: Intent i = new Intent(this, FractalExplorerActivity.class); i.putExtra(FRACTAL_EXTRA, fractal); i.putExtra(SETTING_EXTRA, settings); startActivity(i); return true; } return super.onOptionsItemSelected(item); }

Page 12: © 2012 - CEFRIEL Costruire interfacce grafiche complesse Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Un menu in XML

© 2012 - CEFRIEL

<?xml version="1.0" encoding="utf-8"?>

<menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/main_menu_infos" android:title="@string/main_menu_infos" android:icon="@android:drawable/ic_menu_help"/> <item android:id="@+id/main_menu_preferences" android:title="@string/main_menu_preferences" android:icon="@android:drawable/ic_menu_preferences"/> <item android:id="@+id/main_menu_explore" android:title="@string/main_menu_explore" android:icon="@drawable/ic_explore"/> <!--item android:id="@+id/main_menu_save" android:title="@string/main_menu_save" android:icon="@android:drawable/ic_menu_save"/> <item android:id="@+id/main_menu_load" android:title="@string/main_menu_load" android:icon="@drawable/ic_menu_archive"/--></menu>

Tutte le stringhe per l’utente sono esterne.

Supporteremo nella

prossima versione

Page 13: © 2012 - CEFRIEL Costruire interfacce grafiche complesse Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Utilizzare le preferences per i settings

Vogliamo salvare i Settings nelle preferences:– strumento: SharedPreferences;

•per ottenerlo abbiamo più possibilità:– context.getPreferences() strettamente legate all’Activity– context.getSharedPreferences() condivise nell’intera app– PreferenceManager.getDefaultSharedPreferences(context)

preferenze dell’app gestite dal PreferenceManager

•usiamo le default per poter offrire l’activity:– subclassando PreferenceActivity possiamo descrivere le

preferences in XML e lasciar gestre l’interfaccia per impostarle.

– ci organizziamo con read e write in Settings:

•attenzione, in futuro ci preoccuperemo dei tempi di accesso allo storage, ora no.

Problemi:– ogni variazione delle preferenze deve essere salvata;– ogni variazione esterna deve aggiornare le preferenze.

© 2012 - CEFRIEL

Page 14: © 2012 - CEFRIEL Costruire interfacce grafiche complesse Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Utilizzare le preferences per i settings

© 2012 - CEFRIEL

<?xml version="1.0" encoding="UTF-8"?><PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> <PreferenceCategory android:title="@string/preference_category_pose"> <ListPreference android:key="zoom" android:title="@string/preference_zoom" android:summary="@string/preference_zoom_summary" android:entries="@array/zoom_values" android:entryValues="@array/zoom_values" android:defaultValue="0.5"/> <EditTextPreference android:key="cx" android:title="@string/preference_cx" android:summary="@string/preference_cx_summary" android:inputType="number|numberDecimal|numberSigned" android:defaultValue="-0.5"/>

… </PreferenceCategory> <PreferenceCategory android:title="@string/preference_category_aspect">

… </PreferenceCategory></PreferenceScreen>

Le preference sono

organizzate in categorie

Vari tipi possono

essere inseriti (classi figlie di

Preference)

Inserite valori di default per il primo setting

Page 15: © 2012 - CEFRIEL Costruire interfacce grafiche complesse Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Utilizzare le preferences per i settings

© 2012 - CEFRIEL

// Aggiornamento dei settings da preferenze:prefs = PreferenceManager.getDefaultSharedPreferences(this);settings.read(prefs);

// A fronte del cambiamento delle preferenze cambio i setting:prefs.registerOnSharedPreferenceChangeListener(onPreferencesChange);

onCreate

private SharedPreferences.OnSharedPreferenceChangeListener onPreferencesChange = new SharedPreferences.OnSharedPreferenceChangeListener() {

@Override public void onSharedPreferenceChanged( // Le leggo:SharedPreferences sharedPreferences, String s) { settings.read(prefs); } };

@Override protected void onDestroy() { // Elimino l'ascoltatore: prefs.unregisterOnSharedPreferenceChangeListener(onPreferencesChange); super.onDestroy(); }

public void read(SharedPreferences prefs) { // Leggo dalle preferenze e scrivo i valori nei settings: setDepth(Integer.parseInt(prefs.getString("depth", "20"))); setCx(Float.parseFloat(prefs.getString("cx", "-0.5"))); setCy(Float.parseFloat(prefs.getString("cy", "0.0"))); setZoom(Float.parseFloat(prefs.getString("zoom", "0.5"))); setPalette(prefs.getString("palette", "Rainbow"));}

Settings

Page 16: © 2012 - CEFRIEL Costruire interfacce grafiche complesse Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

L’esploratore di frattale usa un task asincrono

Dobbiamo permettere all’utente di fruire la UI:– durante i calcoli l’interfaccia deve essere responsiva;– non possiamo eseguire i conti sul thread principale;– dobbiamo consentire l’interrompibilità rapida;– dobbiamo mostrare un avanzamento lavori frequente;

Soluzione da scegliere in generale:– un thread esterno con comunicazione asincrona;– la classe AsyncTask ha proprio questo scopo:

•estenderla implementando doInBackground•eventualmente ovverride di "on*";•verificare frequentemente isCanceled:

– nel qual caso terminare l’esecuzione, il task termina qui.

•usiamo Void per i generics, vedremo + avanti;

– avviamo il task con il metodo "execute";– appena richiesto eseguiamo "task.cancel".

© 2012 - CEFRIEL

Page 17: © 2012 - CEFRIEL Costruire interfacce grafiche complesse Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Il task

© 2012 - CEFRIEL

private class FractalComputationTask extends AsyncTask<Void, Void, Void> { @Override protected Void doInBackground(Void... voids) { int N = 5; // Itero sui livelli: for (int i=N; i>=0; i--) { if (isCancelled()) return null; // Controllo per l’uscita forzata:

// Eseguo il calcolo per un livello: fractal.draw(bitmap, i, i==N, settings, new Fractal.Callback() { @Override public boolean rowCompleted(Bitmap bitmap) {

return isCancelled(); } // Uscita forzata: @Override public void levelCompleted(int level, boolean isFirst) { // Blocco il canvas e ci disegno: canvas = holder.lockCanvas(); canvas.drawBitmap(bitmap, 0.0f, 0.0f, new Paint()); holder.unlockCanvasAndPost(canvas); } }); } return null; } }

Page 18: © 2012 - CEFRIEL Costruire interfacce grafiche complesse Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Disegnare su una surface

Per poter disegnare dobbiamo:– avere una surface come view di visualizzazione;– ottenere un holder, «mantenitore» della bitmap;– creare una bitmap su cui lavorare off-line;– ascoltare gli eventi dell’holder per gestire

creazione della bitmap e distruzione del task.

Quando dobbiamo veramente disegnare:– dobbiamo lockare il canvas su cui disegnare;– disegnarci su la nostra bitmap aggiornata;– «committare» la modifica all’holder.

© 2012 - CEFRIEL

Page 19: © 2012 - CEFRIEL Costruire interfacce grafiche complesse Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Inizializzazione (onCreate)

© 2012 - CEFRIEL

// Inizializzo superficie e holder:surface = (SurfaceView)findViewById(R.id.explorerSurface);holder = surface.getHolder();task = new FractalComputationTask(); // Il task:

// Ascolto gli eventi dell'holder:holder.addCallback(new SurfaceHolder.Callback() {@Override public void surfaceCreated(SurfaceHolder surfaceHolder) { bitmap = Bitmap.createBitmap( // Creo la bitmap:

surface.getWidth(),surface.getHeight(),Bitmap.Config.ARGB_8888);

task.execute(); // Avvio un task asincrono per il disegno del frattale:}@Override public void surfaceChanged(

SurfaceHolder surfaceHolder, int i, int i1, int i2) { }

@Override public void surfaceDestroyed(SurfaceHolder surfaceHolder) { task.cancel(true); // Fermo il task: // Rilascio le risorse: bitmap = null; holder.removeCallback(this);} });

Page 20: © 2012 - CEFRIEL Costruire interfacce grafiche complesse Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Un occhio di insieme (versione 1.0)

© 2012 - CEFRIEL

FractalExplorerActivity

bitmap

surface holder canvas

FractalComputationTask

fractal

settings

La surface ci da l’holder che quando serve fornisce il canvas;

l’activity gestisce il task che lavora su un altro thread;

il task tramite fractal e settings scrive sulla bitmap di dobule-buffering creata da noi;

per gli aggiornamenti viene creato un canvas su cui disegnare la bitmap;

nel canvas un’altra bitmap è nascosta per mantenere l’immagine mostrata all’utente.

Page 21: © 2012 - CEFRIEL Costruire interfacce grafiche complesse Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Refactoring: estraiamo una custom-view Troppe responsabilità nella stessa activity:

– al fine di non rendere il codice troppo complesso:

•in generale conviene fattorizzarlo;•funzionalità coese possono essere

estratte;•se ha senso vanno create view custom;

– ogni view può gestire disegno e interazione con essa;

– l’activity può comporre le view e gestire i menu. Creare una view custom è molto semplice:

– creare una sottoclasse della view di interesse:

•nel nostro caso estendiamo SurfaceView;– override-are i metodi di interesse della view:

– onDraw per disegnarne il contenuto (non draw);– onTouchEvent per gestire gli eventi touch;

– spostiamo anche FractalComputationTask.© 2012 - CEFRIEL

Page 22: © 2012 - CEFRIEL Costruire interfacce grafiche complesse Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

TouchMe Esperienza di interfaccia uomo-macchina:

– Siggraph 2006, il multi-touch era già realtà:• http://www.multitouchtechnology.com/videos/multi-touch-screen-siggraph-2006/

– Cosa si può fare con un dito?• Selezione, trascinamento, tracciamento, tap, tap

lungo, tap multipli… ~un mouse:– interfaccia differente, stessi gradi di libertà…– …più controllo di dimensione e pressione…

» …ovvero? Parliamo di come sono fatti i sensori!

• Meno preciso ma più user-frendly.– Cosa si può fare con due dita?

• Quanto sopra «in parallelo» separatamente;• descrivere trasformazioni di similarità:

– traslazione, scala e rotazione tutto assieme.– Cosa si può fare con tre dita?

• trasformazioni 3d (ad es. si consideri la rotazione);• più dita possono essere gestite ma sono complicate

per l’utente da coordinare. Casi di utilizzo frequente?

– Scorrimento liste, selezione, cambio pagina, …– PTZ (Pan, Tilt, Zoom), ad es. di immagini nella gallery.

Noi possiamo fare PTZ sul nostro frattale (con qualche calcolo).

© 2012 - CEFRIEL

Page 23: © 2012 - CEFRIEL Costruire interfacce grafiche complesse Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Rendiamo il frattale esplorabile con le dita Eventi touch notificati con onTouchEvent:

– ogni evento ha una posizione, e un codice;– metadati sono associati ad ogni evento:

•pressione, dimensione, …– da Android 2.0 si ha il supporto al multitouch:

•ogni evento descrive più pointers. Per gestire il touch interpretiamo gli eventi:

– le sequenze hanno una struttura pressappoco fissa;– aggregazione di eventi possono generare nuove

tipologie di eventi più complessi (es. pinch).

© 2012 - CEFRIEL

ACTION_DOWN

ACTION_MOVE

ACTION_UP

ACTION_MOVE…

Traslazione

Page 24: © 2012 - CEFRIEL Costruire interfacce grafiche complesse Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Gestire il pan (traslazione) Stato di inizio dell’operazione di pan:

– memorizzo la posizione di inizio (startX, startY);– la trasformazione dell’immagine è l’identità.

Durante l’operazione di pan:– aggiorno la trasformazione rispetto alla traslazione:

•(e.get()-startX, e.getY()-startY).– posto una richiesta di invalidazione della view…

•…ovvero dichiaro che andrà ridisegnata;– il metodo onDraw disegnerà sfondo e immagine.

Al termine dell’operazione di pan:– calcolo le nuove coordinate del frattale (cambiando

nei settings in maniera permanente i valori cx e cy);– rendo identità la trasformazione;– avvio un nuovo calcolo per il frattale;– invalido la vista (verrà ridisegnata nel main thread).

© 2012 - CEFRIEL

Page 25: © 2012 - CEFRIEL Costruire interfacce grafiche complesse Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Alcuni dettagli tecnici

Invalidate e postInvalidate:– la prima va chiamata dal main (UI) thread;– la seconda accoda una richiesta (qualunque thread);

onDraw:– chiamata da draw, da non override-are quest’ultima

in caso di SurfaceView (perderemmo le chiamate agli eventi SurfaceHolder.Callback);

– se vogliamo che venga chiamato draw…

•…impostare setWillNotDraw(false); android.graphics.Matrix:

– rappresenta una trasformazione affine in R2;– traslazione, rotazione, scala, skew;– 6 DoF, 6 coefficienti, geometria proiettiva;– canvas.setMatrix qualunque operazione di disegno

può essere attuata «attraverso» una trasformazione affine.

© 2012 - CEFRIEL

Page 26: © 2012 - CEFRIEL Costruire interfacce grafiche complesse Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Dal touch al multi-touch Ogni tocco in Android è potenzialmente multiplo:

– ogni MotionEvent può contenere dati relativi a più tocchi contemporaneamente (pointers);

– ogni pointer ha un id univoco durante una operazione; accesso ai dati di tocco:

– numero puntatori indicato da getPointerCount;– eventi ACTION_DOWN/ACTION_POINTER_DOWN;– eventi ACTION_MOVE;– eventi ACTION_UP/ACTION_POINTER_UP;– …

Troppi eventi… accorpiamoli assieme!– Se troppi eventi vengono generati di seguito vengono

accorpati un una history accessibile dall’evento:

•getHistorySize numero di eventi accorpati;•getHistoricalX/Y coordinate del tocco.

© 2012 - CEFRIEL

Page 27: © 2012 - CEFRIEL Costruire interfacce grafiche complesse Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Gesture detection fai-da-te

Una possibilità per la gesture detection:– analizzare la sequenza di eventi di tocco;– mantenere informazioni salienti sulla sequenza;– identificare le azioni desiderate.

Semplice ma complesso:– in certe situazioni (come la nostra) risulta semplice;– in altre risulta estremamente complesso «a mano»:

•immaginiamo un tocco «circolare». Per varie tipologie di gesture esistono diverse

classi di supporto che analizzano la sequenza:– dobbiamo fornirgli gli eventi che riceviamo;– dobbiamo registrare un nostro ascoltatore di gesture.

© 2012 - CEFRIEL

Page 28: © 2012 - CEFRIEL Costruire interfacce grafiche complesse Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Il GestureDetector

Cosa fa un gestureDetector?– Riceve (da noi) una sequenza di MotionEvent;– la analizza e produce eventi «più complessi»:

•li ascoltiamo con un’ascoltatore:– GestureDetector.OnGestureListener eventi semplici:

» onDown, onFling, onLongPress, onScroll, onShowPress, onSingleTapUp

– GestureDetector.OnDoubleTapListener eventi di tap:» onDoubleTap, onDoubleTapEvent, onSingleTapConfirmed

Come utilizzarlo?– Creiamo il detector e registriamo i listener;– nella nostra view custom catturiamo gli eventi di

touch facendo ovverdide di onTouchEvent;– li notifichiamo al gesture detector con

onTouchEvent;– agiamo nei listener in presenza degli eventi.© 2012 - CEFRIEL

Page 29: © 2012 - CEFRIEL Costruire interfacce grafiche complesse Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Gesture complesse: le gesture library

In alcune app con particolari caratteristiche:– ad esempio dove l’interazione utente è importante:

•videogiochi, ebook-reader, …– eventualmente per un range limitato di azioni

es.: solo volta pagina in un lettore di libri; diventa utile interpretare le sequenze di

tocchi:– una gesture è formata da una o più sequenze

(stroke) che formano un pattern noto a priori;– ogni sequenza contiene uno o più punti nel

percorso; una libreria di gesture è una struttura dati che

contiene una o più descrizioni di gesture:– Android ci fornisce gli strumenti per identificarle

automaticamente tramite una view di overlay.© 2012 - CEFRIEL

Page 30: © 2012 - CEFRIEL Costruire interfacce grafiche complesse Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Creiamo e usiamo una gesture library Creiamo un gestore di note brevi. Usiamo direttamente l’emulatore:

– app Gesture Builder creata apposta;– eliminiamo eventuali gesture;– creiamo le nostre con nome;– copiamo /mnt/sdcard/gestures nel

progetto in res/raw/gestures. Prepariamo il layout:

© 2012 - CEFRIEL

<android.gesture.GestureOverlayView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/gestures" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" android:eventsInterceptionEnabled="true"> <ListView android:id="@android:id/list" android:layout_width="fill_parent" android:layout_height="fill_parent"/></android.gesture.GestureOverlayView>

Page 31: © 2012 - CEFRIEL Costruire interfacce grafiche complesse Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Detection delle gesture

© 2012 - CEFRIEL

// Carico la libreria di gesture:final GestureLibrary gestures = GestureLibraries

.fromRawResource(this, R.raw.gestures);if (!gestures.load()) { Toast.makeText(this,

"Impossibile caricare le gesture", Toast.LENGTH_LONG).show();

finish(); }

gesturesOverlay.addOnGesturePerformedListener(new GestureOverlayView.OnGesturePerformedListener() {

@Override public void onGesturePerformed(GestureOverlayView gestureOverlayView, Gesture gesture) {

// Verifico se una gestura di interesse è arrivata: ArrayList<Prediction> predictions = gestures.recognize(gesture); if (predictions.size() > 0 && predictions.get(0).score > 1.0) { // Identifico l'accaduto (in questo caso aggiungo alla lista): String action = predictions.get(0).name; if ("action_write".equals(action)) doWrite(); else if ("action_delete".equals(action)) doDelete(); else if ("action_save".equals(action)) doSave(); else if ("action_load".equals(action)) doLoad();} } });

Page 32: © 2012 - CEFRIEL Costruire interfacce grafiche complesse Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Menu contestuali Menu menu menu…

– …per richiedere azioni alla nostra app…– …se l’azione dovesse riguardare un item in una lista?

Menu di contesto:– oltre a richiedere un’azione trasporta il contesto in cui

la richiesta è avvenuta (in altre GUI… tasto destro);– il menu si presenta come una tendina di selezione;– possiamo appendere menu di contesto alle view;– sono menu di contesto… li descriviamo in XML.

© 2012 - CEFRIEL

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

super.onCreateContextMenu(menu, v, menuInfo); getMenuInflater().inflate(R.menu.context_menu, menu); }

@Override public boolean onContextItemSelected(MenuItem item) { AdapterView.AdapterContextMenuInfo info =

(AdapterView.AdapterContextMenuInfo) item.getMenuInfo(); switch (item.getItemId()) { case R.id.menu_delete: doDeleteItem(info.position); return true; case R.id.menu_edit: doEdit(info.position); return true; } return super.onContextItemSelected(item); }

// Ci registriam

o per un menu contestuale:

registerForC

ontextMenu(getListV

iew());

Page 33: © 2012 - CEFRIEL Costruire interfacce grafiche complesse Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Tornando a fractal explorer… Salviamo una immagine nella sd-card:

– aggiungiamo un menu in FractalExplorerActivity;– per la voce «Save as image»:

•chiediamo un nome di file;•salviamo nella cartella delle immagini;

– per i permessi abbiamo bisogno di:– android.permission.WRITE_EXTERNAL_STORAGE

© 2012 - CEFRIEL

FileOutputStream out = null; try {

File extDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);

extDir.mkdirs();out = new FileOutputStream(

new File(extDir, input.getText().toString() + ".png"));surface.getBitmap().compress(

Bitmap.CompressFormat.PNG, 40, out);out.getFD().sync();

} catch (Exception e) {…}finally { try { out.close(); } catch (Throwable t) { } }

Page 34: © 2012 - CEFRIEL Costruire interfacce grafiche complesse Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

rows++;…if (callback!=null) callback.progress((float)rows/totalRows);

Aggiungiamo una barra di progresso NON vogliamo aggiungerla a mano:

– potremmo aggiungere una SeekBar a mano:

•dovremmo per forza inserirla nel layout;•noi vogliamo inserirla nella barra del titolo;

– non possiamo usare il layout full-screen:

•eliminare nel manifest l’attributo:– android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen"

– dobbiamo interagire con l’UI creata da Android:– requestWindowFeature(Window.FEATURE_PROGRESS);

dobbiamo fornirci le informazioni dal task asincrono:

© 2012 - CEFRIEL

private int rows;…rows = 0;int totalRows = (int)((2-1.0/N)*bitmap.getHeight())

public interface ProgressCallback {public void start();public void progress(float percentage);public void done(); }

Page 35: © 2012 - CEFRIEL Costruire interfacce grafiche complesse Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Aggiungiamo una barra di progresso

© 2012 - CEFRIEL

surface.setCallback(new FractalSurface.ProgressCallback() {@Override public void start() {

runOnUiThread(new Runnable() { @Override public void run() { // Inizializzo la progress-bar: setProgress(0); setProgressBarVisibility(true); } });

}

@Override public void progress(final float percentage) { runOnUiThread(new Runnable() { @Override public void run() { // Aggiorno l’indicatore di progresso: setProgress((int) (percentage * 10000)); } });

}

@Override public void done() { runOnUiThread(new Runnable() { @Override public void run() { // Nascondo la progress-bar: setProgressBarVisibility(false); } });

} });

Page 36: © 2012 - CEFRIEL Costruire interfacce grafiche complesse Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Parametri: uno strumento per valorizzarli

La SeekBar non ci piace:– non mostra il valore dell’attuale selezione;– ci costringe a gestire «male» gli estremi;– mostra una grafica da progress-bar;– il puntatore non è facilissimo da «acchiappare».

Soluzione? Ce la creiamo noi!– Creiamo un nuovo componente figlio di View;– lo realizziamo come composizione di altri

componenti;– (potremmo disegnare da zero il componente.. scomodo)

– lo personalizziamo come ci sembra meglio;– lo usiamo mille volte come building-block.

© 2012 - CEFRIEL

Page 37: © 2012 - CEFRIEL Costruire interfacce grafiche complesse Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Progettiamo una view custom

© 2012 - CEFRIEL

min maxnome

valore

Barra di scorrimento

Manopolina di selezione

Zona di spostamento del valore

Zona dei testi informativi

Zona «calda» di attivazione selezione

Valore attuale (segue la manopola)

Nome parametro Valore massimoValore minimo

Page 38: © 2012 - CEFRIEL Costruire interfacce grafiche complesse Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Una classe figlia di view Un costruttore nostro:

– non standard, non permette l’utilizzo dell’XML;– riceve un parametro da controllare.

© 2012 - CEFRIEL

public Slider(Context context, Parameter parameter) { super(context); this.parameter = parameter;

barPaint = new Paint(); // Pennello per la barra di scorrimento. barPaint.setColor(Color.GRAY); barPaint.setAntiAlias(true); barPaint.setStrokeWidth(barWidth); barPaint.setStrokeCap(Paint.Cap.ROUND); sliderPaint = new Paint(); // Pennello per la manopola. sliderPaint.setColor(Color.LTGRAY); sliderPaint.setAntiAlias(true); sliderPaint.setStrokeWidth(sliderWidth); sliderPaint.setStrokeCap(Paint.Cap.ROUND); numbersPaint = new Paint(); // Pennello per le scritte. numbersPaint.setColor(Color.LTGRAY); numbersPaint.setAntiAlias(true); numbersPaint.setFakeBoldText(true); numbersPaint.setTextSize(textHeight); /* … */ }

Page 39: © 2012 - CEFRIEL Costruire interfacce grafiche complesse Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Una classe figlia di view Ci disegniamo nello spazio concessoci:

© 2012 - CEFRIEL

// Informazioni:Rect rect = new Rect(); getDrawingRect(rect);float pos = (value-parameter.min)/(parameter.max-parameter.min)*

(rect.right-getPaddingRight()-rect.left);float fromv = (rect.top+2*textHeight); float midv = (fromv+rect.bottom)/2.0f;

// Disegnamo i valori estremi:numbersPaint.setTextAlign(Paint.Align.LEFT);canvas.drawText(formatNumber(parameter.min), rect.left,

rect.top + textHeight, numbersPaint);numbersPaint.setTextAlign(Paint.Align.RIGHT);canvas.drawText(formatNumber(parameter.max),

rect.right, rect.top + textHeight, numbersPaint);numbersPaint.setTextAlign(Paint.Align.CENTER);canvas.drawText(formatNumber(value), pos, rect.top + 2 * textHeight, numbersPaint); canvas.drawText(parameter.name,(rect.left+rect.right)/2,

rect.top+textHeight,numbersPaint);

// Disegnamo lo slider con la posizione attuale:canvas.drawLine(rect.left + barWidth, midv, rect.right - barWidth, midv, barPaint);canvas.drawLine(pos,fromv+sliderWidth,pos,rect.bottom-sliderWidth,sliderPaint);canvas.drawLine(pos,fromv+sliderWidth+barWidth,

pos,rect.bottom-sliderWidth-barWidth,barPaint);

Page 40: © 2012 - CEFRIEL Costruire interfacce grafiche complesse Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Misurare si.. Ma non a caso!

In Android per misurare i widget…– …gli si chiedono le dimensioni minime occupate:

•override di onMeasure;– viene poi delegato il layout per l’effettiva decisione

dello spazio disponibile e delle dimensioni finali:

•basiamoci quindi su quelle forniteci; nel nostro caso 2 righe di testo e l’altezza dello

slider impongono solo un’altezza minima:

© 2012 - CEFRIEL

@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

super.onMeasure(widthMeasureSpec, heightMeasureSpec);setMeasuredDimension(getMeasuredWidth(),

2*textHeight + 4*(barWidth+sliderWidth));}

Page 41: © 2012 - CEFRIEL Costruire interfacce grafiche complesse Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Lasciamoci toccare… non troppo! Ci sono almeno 2 modi per intercettare gli

eventi di tocco dall’interno di una view custom:– la prima l’abbiamo già vista: onTouchEvent;– la seconda consiste nel registrare un listener:

– permette di agire anche senza creare una classe figlia.

© 2012 - CEFRIEL

setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View view, MotionEvent event) { // Dove ero? Rect rect = new Rect(); getDrawingRect(rect); float oldPerc = (value-parameter.min)/(parameter.max-parameter.min); float sliderx = rect.left + oldPerc*(rect.right-rect.left); // Reagisco solo in prossimità dallo slider: float x = event.getX(), y = event.getY(); if (event.getAction()==MotionEvent.ACTION_DOWN && !(y-rect.top > 2*textHeight && Math.abs(x-sliderx) < 3*sliderWidth)) return false; // Catturiamo gli eventi di tocco per spostare lo slider: float perc = (event.getX()-rect.left)/(rect.right-rect.left); value = parameter.min + perc*(parameter.max-parameter.min); if (parameter.integer) value = Math.round(value); // Fatto, gestisco gli eventi: if (onValueChangeListener!=null) onValueChangeListener.valueChanged(value); invalidate(); return true; } });

Page 42: © 2012 - CEFRIEL Costruire interfacce grafiche complesse Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Giochiamo col nuovo componente… …inserendolo al posto della SeekBar:

© 2012 - CEFRIEL

Slider slider = new Slider(this, p);slider.setValue(fractal.getParameterValue(j));slider.setOnValueChangeListener(new Slider.OnValueChangeListener() {

@Override public void valueChanged(float value) { // Cambio il parametro: fractal.setParameterValue(j, value); } });vParameters.addView(slider);

Permettiamo lo scorrimento verticale:<ScrollView android:id="@+id/main_fractalParameters_scroll" android:layout_height="wrap_content" android:layout_width="fill_parent"> <LinearLayout android:id="@+id/main_fractalParameters" android:layout_height="wrap_content" android:layout_width="fill_parent" android:orientation="vertical"/></ScrollView>

Come si propagano

gli eventi???

Page 43: © 2012 - CEFRIEL Costruire interfacce grafiche complesse Docente: Gabriele Lombardi lombardi@dsi.unimi.it.

Altro???

Il framework per la creazione di UI in Android…– …è ampio, semplice e divertente da usare;– molti componenti possono essere usati (giochiamoci);– alcune funzionalità nuove e interessanti le vedremo

nel «capitolo» delle novità da Honeycomb in avanti;– molto più di quanto abbiamo visto può essere definito

in XML… giochiamoci assieme!

Non abbiamo ancora affrontato problemi come:– la persistenza dei dati su database;– l’interazione tra app (es con GoogleMaps);– l’accesso alla rete, ad esempio per servizi RESTful;– l’utilizzo dei sensori (camera, accelerometro, …).

© 2012 - CEFRIEL