Android offre un modello sofisticato e potente basato su componenti per la creazione dell'interfaccia utente, basato sulle classi di layout fondamentali
View e
ViewGroup. La piattaforma include una varietà di classi View e ViewGroup predefinite, chiamate rispettivamente widget e layout, che puoi utilizzare per creare la tua UI.
Un elenco parziale dei widget disponibili include Button,
TextView,
EditText,
ListView,
CheckBox,
RadioButton,
Gallery,
Spinner e quelli più specifici
AutoCompleteTextView,
ImageSwitcher e
TextSwitcher.
Tra i layout disponibili ci sono
LinearLayout,
FrameLayout,
RelativeLayout
e altri. Per altri esempi, vedi
Layout comuni.
Se nessuno dei widget o dei layout predefiniti soddisfa le tue esigenze, puoi creare una sottoclasse
View personalizzata. Se devi apportare solo piccole modifiche a un widget o a un layout esistente, puoi creare una sottoclasse del widget o del layout e sostituire i relativi metodi.
La creazione di sottoclassi View personalizzate ti consente di controllare con precisione l'aspetto e
la funzione di un elemento dello schermo. Per darti un'idea del controllo che ottieni con le visualizzazioni personalizzate, ecco
alcuni esempi di ciò che puoi fare:
-
Puoi creare un tipo
Viewcompletamente personalizzato, ad esempio una manopola di "controllo del volume", visualizzata utilizzando la grafica 2D, che assomiglia a un controllo elettronico analogico. -
Puoi combinare un gruppo di
Viewcomponenti in un unico nuovo componente, ad esempio per creare una casella combinata (una combinazione di elenco popup e campo di testo a inserimento libero), un controllo selettore a due riquadri (un riquadro a sinistra e uno a destra con un elenco in ciascuno in cui puoi riassegnare l'elemento a un elenco), e così via. -
Puoi ignorare il modo in cui un componente
EditTextviene visualizzato sullo schermo. L'app di esempio NotePad utilizza questo elemento in modo efficace per creare una pagina di blocco note a righe. - Puoi acquisire altri eventi, ad esempio la pressione di tasti, e gestirli in modo personalizzato, ad esempio per un gioco.
Le sezioni seguenti spiegano come creare viste personalizzate e utilizzarle nell'applicazione. Per
informazioni di riferimento dettagliate, consulta la
classe View.
L'approccio di base
Ecco una panoramica generale di ciò che devi sapere per creare i tuoi componenti View:
-
Estendi una classe o una sottoclasse
Viewesistente con la tua classe. -
Esegui l'override di alcuni metodi della superclasse. I metodi della superclasse da sostituire iniziano con
on, ad esempioonDraw(),onMeasure(), eonKeyDown(). Questi sono simili agli eventioninActivityoListActivityche esegui l'override per i hook del ciclo di vita e di altre funzionalità. - Utilizza la nuova classe di estensione. Una volta completata, puoi utilizzare la nuova classe di estensione al posto della visualizzazione su cui si basa.
Componenti completamente personalizzati
Puoi creare componenti grafici completamente personalizzati che vengono visualizzati nel modo che preferisci. Forse vuoi un VU meter grafico che assomigli a un vecchio indicatore analogico o una visualizzazione del testo per cantare in cui una palla rimbalza sulle parole mentre canti insieme a una macchina per il karaoke. Potresti volere qualcosa che i componenti integrati non possono fare, indipendentemente da come li combini.
Fortunatamente, puoi creare componenti che hanno l'aspetto e il comportamento che preferisci, limitati solo dalla tua immaginazione, dalle dimensioni dello schermo e dalla potenza di elaborazione disponibile, tenendo presente che la tua applicazione potrebbe dover essere eseguita su un dispositivo con una potenza significativamente inferiore rispetto alla tua workstation desktop.
Per creare un componente completamente personalizzato, tieni presente quanto segue:
-
La visualizzazione più generica che puoi estendere è
View, quindi di solito inizi estendendo questa per creare il nuovo supercomponente. - Puoi fornire un costruttore, che può accettare attributi e parametri dall'XML, e puoi utilizzare i tuoi attributi e parametri, ad esempio il colore e l'intervallo del VU meter o la larghezza e lo smorzamento dell'ago.
- Probabilmente vorrai creare i tuoi listener di eventi, gli accessor e i modificatori di proprietà, nonché un comportamento più sofisticato nella classe del componente.
-
Quasi sicuramente vuoi sovrascrivere
onMeasure()e probabilmente dovrai anche sovrascrivereonDraw()se vuoi che il componente mostri qualcosa. Sebbene entrambi abbiano un comportamento predefinito,onDraw()non fa nulla, mentreonMeasure()imposta sempre una dimensione di 100x100, che probabilmente non ti interessa. -
Puoi anche ignorare altri metodi di
on, se necessario.
Estendi onDraw() e onMeasure()
Il metodo onDraw() fornisce un
Canvas su cui puoi
implementare qualsiasi cosa tu voglia: grafica 2D, altri componenti standard o personalizzati, testo formattato o
qualsiasi altra cosa ti venga in mente.
onMeasure() è un po' più complesso. onMeasure() è un elemento fondamentale
del contratto di rendering tra il componente e il relativo contenitore. onMeasure() deve essere
ignorato per segnalare in modo efficiente e accurato le misurazioni delle parti contenute. Ciò è
reso leggermente più complesso dai requisiti di limite del genitore, che vengono passati al
metodo onMeasure(), e dal requisito di chiamare il
metodo setMeasuredDimension() con la larghezza e l'altezza misurate una volta calcolate. Se non chiami questo metodo da un metodo onMeasure() sottoposto a override, si verifica un'eccezione al momento della misurazione.
A livello generale, l'implementazione di onMeasure() è simile alla seguente:
-
L'override del metodo
onMeasure()viene chiamato con le specifiche di larghezza e altezza, che vengono trattate come requisiti per le limitazioni delle misurazioni di larghezza e altezza che produci. I parametriwidthMeasureSpeceheightMeasureSpecsono entrambi codici interi che rappresentano le dimensioni. Un riferimento completo al tipo di limitazioni che queste specifiche possono richiedere è disponibile nella documentazione di riferimento inView.onMeasure(int, int)Questa documentazione di riferimento spiega anche l'intera operazione di misurazione. -
Il metodo
onMeasure()del componente calcola una larghezza e un'altezza di misurazione, necessarie per il rendering del componente. Deve cercare di rispettare le specifiche fornite, anche se può superarle. In questo caso, il genitore può scegliere cosa fare, ad esempio tagliare, scorrere, generare un'eccezione o chiedere aonMeasure()di riprovare, magari con specifiche di misurazione diverse. -
Una volta calcolate la larghezza e l'altezza, chiama il metodo
setMeasuredDimension(int width, int height)con le misurazioni calcolate. In caso contrario, si verifica un'eccezione.
Ecco un riepilogo di altri metodi standard che il framework chiama nelle visualizzazioni:
| Categoria | Metodi | Descrizione |
|---|---|---|
| Creazione | Costruttori | Esiste una forma del costruttore chiamata quando la visualizzazione viene creata dal codice e una forma chiamata quando la visualizzazione viene creata da un file di layout. Il secondo modulo analizza e applica gli attributi definiti nel file di layout. |
|
Chiamato dopo che una visualizzazione e tutti i relativi elementi secondari sono stati caricati da XML. | |
| Layout | |
Chiamato per determinare i requisiti di dimensioni per questa visualizzazione e tutti i relativi elementi secondari. |
|
Chiamato quando questa visualizzazione deve assegnare una dimensione e una posizione a tutti i suoi elementi secondari. | |
|
Chiamato quando le dimensioni di questa visualizzazione vengono modificate. | |
| Disegno | |
Chiamato quando la visualizzazione deve eseguire il rendering dei contenuti. |
| Elaborazione degli eventi | |
Chiamato quando si verifica un evento di pressione di un tasto. |
|
Chiamato quando si verifica un evento di rilascio del tasto. | |
|
Chiamato quando si verifica un evento di movimento del trackball. | |
|
Chiamato quando si verifica un evento di movimento del touchscreen. | |
| Concentrazione | |
Chiamato quando la visualizzazione acquisisce o perde lo stato attivo. |
|
Chiamato quando la finestra contenente la visualizzazione acquisisce o perde lo stato attivo. | |
| Collegamento in corso | |
Chiamato quando la visualizzazione è collegata a una finestra. |
|
Chiamato quando la visualizzazione viene scollegata dalla finestra. | |
|
Chiamato quando viene modificata la visibilità della finestra contenente la visualizzazione. |
Controlli composti
Se non vuoi creare un componente completamente personalizzato, ma vuoi assemblare un componente riutilizzabile costituito da un gruppo di controlli esistenti, la creazione di un componente composto (o controllo composto) potrebbe essere la soluzione migliore. In sintesi, questo raggruppa una serie di controlli o visualizzazioni più atomici in un gruppo logico di elementi che possono essere trattati come un'unica entità.
Ad esempio, una casella combinata può essere una combinazione di un campo EditText a una sola riga
e un pulsante adiacente con un elenco popup allegato. Se l'utente tocca il pulsante e seleziona un elemento dall'elenco, il campo EditText viene compilato, ma può anche digitare qualcosa direttamente nel campo EditText, se preferisce.
In Android, sono disponibili altre due visualizzazioni per farlo: Spinner e
AutoCompleteTextView. In ogni caso, questo concetto di casella combinata è un buon esempio.
Per creare un componente composto:
-
Come per un
Activity, utilizza l'approccio dichiarativo (basato su XML) per creare i componenti contenuti o nidificarli in modo programmatico dal codice. Il punto di partenza abituale è unLayoutdi qualche tipo, quindi crea una classe che estenda unLayout. Nel caso di una casella combinata, puoi utilizzare unLinearLayoutcon orientamento orizzontale. Puoi nidificare altri layout all'interno, in modo che il componente composto possa essere arbitrariamente complesso e strutturato. -
Nel costruttore della nuova classe, prendi tutti i parametri previsti dalla superclasse e passali
prima al costruttore della superclasse. Poi, puoi configurare le altre visualizzazioni da utilizzare
all'interno del nuovo componente. Qui puoi creare il campo
EditTexte l'elenco a comparsa. Puoi introdurre attributi e parametri personalizzati nell'XML che il costruttore può estrarre e utilizzare. -
(Facoltativo) Crea listener per gli eventi che le visualizzazioni incorporate potrebbero generare. Un esempio è un
metodo di listener per il click listener sull'elemento dell'elenco per aggiornare i contenuti di
EditTextse viene effettuata una selezione dell'elenco. -
Se vuoi, crea le tue proprietà con funzioni di accesso e modificatori. Ad esempio, consenti di impostare inizialmente il valore di
EditTextnel componente e di eseguire query sui relativi contenuti quando necessario. -
(Facoltativo) Esegui l'override di
onDraw()eonMeasure(). In genere non è necessario quando si estende unLayout, poiché il layout ha un comportamento predefinito che probabilmente funziona bene. -
Se vuoi, esegui l'override di altri metodi
on, ad esempioonKeyDown(), per scegliere determinati valori predefiniti dall'elenco popup di una casella combinata quando viene toccato un tasto specifico.
L'utilizzo di un Layout come base per un controllo personalizzato offre diversi vantaggi, tra cui:
- Puoi specificare il layout utilizzando i file XML dichiarativi, proprio come con una schermata di attività, oppure puoi creare viste a livello di programmazione e nidificarle nel layout dal codice.
-
I metodi
onDraw()eonMeasure(), oltre alla maggior parte degli altri metodion, hanno un comportamento adatto, quindi non devi eseguirne l'override. - Puoi creare rapidamente viste composte arbitrariamente complesse e riutilizzarle come se fossero un singolo componente.
Modificare un tipo di visualizzazione esistente
Se esiste un componente simile a quello che vuoi, puoi estenderlo e sostituire
il comportamento che vuoi modificare. Puoi fare tutto ciò che fai con un componente completamente personalizzato, ma iniziando con una classe più specializzata nella gerarchia View, puoi ottenere senza costi un comportamento che fa ciò che vuoi.
Ad esempio, l'app di esempio
NotePad
mostra molti aspetti dell'utilizzo della piattaforma Android. Tra queste, c'è l'estensione di una
visualizzazione EditText per creare un blocco note a righe. Questo non è un esempio perfetto e le API per
eseguire questa operazione potrebbero cambiare, ma illustra i principi.
Se non l'hai ancora fatto, importa l'esempio NotePad in Android Studio o esamina il codice sorgente utilizzando il link fornito. In particolare, consulta la definizione di LinedEditText
nel file
NoteEditor.java.
Ecco alcuni aspetti da notare in questo file:
-
La definizione
La classe è definita con la seguente riga:
public static class LinedEditText extends EditTextLinedEditTextè definita come classe interna all'attivitàNoteEditor, ma è pubblica, quindi è possibile accedervi comeNoteEditor.LinedEditTextdall'esterno della classeNoteEditor.Inoltre,
LinedEditTextèstatic, il che significa che non genera i cosiddetti "metodi sintetici" che gli consentono di accedere ai dati della classe padre. Ciò significa che si comporta come una classe separata anziché come qualcosa di strettamente correlato aNoteEditor. Questo è un modo più pulito per creare classi interne se non hanno bisogno di accedere allo stato della classe esterna. Mantiene la classe generata piccola e ne consente l'utilizzo semplice da altre classi.LinedEditTextestendeEditText, che è la visualizzazione da personalizzare in questo caso. Al termine, la nuova classe può sostituire una normale visualizzazioneEditText. -
Inizializzazione della classe
Come sempre, viene chiamato prima il super. Questo non è un costruttore predefinito, ma è un costruttore parametrizzato.
EditTextviene creato con questi parametri quando viene gonfiato da un file di layout XML. Pertanto, il costruttore deve prenderli e passarli anche al costruttore della superclasse. -
Metodi sostituiti
Questo esempio esegue l'override solo del metodo
onDraw(), ma potresti dover eseguire l'override di altri metodi durante la creazione dei tuoi componenti personalizzati.Per questo esempio, l'override del metodo
onDraw()consente di disegnare le linee blu sul canvas della visualizzazioneEditText. La tela viene passata al metodoonDraw()di override. Il metodosuper.onDraw()viene chiamato prima della fine del metodo. Il metodo della superclasse deve essere richiamato. In questo caso, invoca la funzione alla fine dopo aver disegnato le linee che vuoi includere. -
Componente personalizzato
Ora hai il tuo componente personalizzato, ma come puoi utilizzarlo? Nell'esempio di NotePad, il componente personalizzato viene utilizzato direttamente dal layout dichiarativo, quindi esamina
note_editor.xmlnella cartellares/layout:<view xmlns:android="http://schemas.android.com/apk/res/android" class="com.example.android.notepad.NoteEditor$LinedEditText" android:id="@+id/note" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/transparent" android:padding="5dp" android:scrollbars="vertical" android:fadingEdge="vertical" android:gravity="top" android:textSize="22sp" android:capitalize="sentences" />
Il componente personalizzato viene creato come visualizzazione generica in XML e la classe viene specificata utilizzando il pacchetto completo. La classe interna che definisci viene referenziata utilizzando la notazione
NoteEditor$LinedEditText, che è un modo standard per fare riferimento alle classi interne nel linguaggio di programmazione Java.Se il componente di visualizzazione personalizzato non è definito come classe interna, puoi dichiarare il componente di visualizzazione con il nome dell'elemento XML ed escludere l'attributo
class. Ad esempio:<com.example.android.notepad.LinedEditText id="@+id/note" ... />
Tieni presente che la classe
LinedEditTextora è un file di classe separato. Quando la classe è nidificata nella classeNoteEditor, questa tecnica non funziona.Gli altri attributi e parametri nella definizione sono quelli passati al costruttore del componente personalizzato e poi al costruttore
EditText, quindi sono gli stessi parametri che utilizzi per una visualizzazioneEditText. È possibile aggiungere anche i tuoi parametri.
La creazione di componenti personalizzati è complicata solo quanto necessario.
Un componente più sofisticato può eseguire l'override di un numero ancora maggiore di metodi on e introdurre i propri metodi helper, personalizzando in modo sostanziale le sue proprietà e il suo comportamento. L'unico limite è la tua
immaginazione e ciò che vuoi che faccia il componente.