Creare componenti di visualizzazione personalizzata

Prova il metodo Scrivi
Jetpack Compose è il toolkit consigliato per la UI per Android. Scopri come utilizzare i layout in Compose.

Android offre un modello a componenti sofisticato e potente per creare la tua UI, basato sulle classi di layout di base View e ViewGroup. La piattaforma include una serie di sottoclassi View e ViewGroup predefinite, rispettivamente denominate 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 con uno scopo più specifico AutoCompleteTextView, ImageSwitcher e TextSwitcher.

Tra i layout disponibili ci sono LinearLayout, FrameLayout, RelativeLayout e altri. Per altri esempi, consulta Layout comuni.

Se nessuno dei widget o dei layout predefiniti soddisfa le tue esigenze, puoi creare la tua sottoclasse View. Se devi solo apportare piccole modifiche a un widget o un layout esistente, puoi eseguire una sottoclasse del widget o del layout e sostituire i relativi metodi.

La creazione di sottoclassi View personalizzate ti offre un controllo preciso sull'aspetto e sulla funzione di un elemento dello schermo. Per dare un'idea del controllo che puoi ottenere con le visualizzazioni personalizzate, ecco alcuni esempi di utilizzo:

  • Puoi creare un tipo View con rendering completamente personalizzato, ad esempio una manopola di "controllo del volume", sottoposta a rendering utilizzando grafica 2D, che assomiglia a un controllo elettronico analogico.
  • Puoi combinare un gruppo di componenti View in un nuovo componente singolo, magari per creare qualcosa come una casella combinata (una combinazione di elenco popup e campo di testo a voce libera), un controllo selettore a doppio riquadro (un riquadro di sinistra e di destra con un elenco in cui è possibile riassegnare l'elemento in un determinato elenco) e così via.
  • Puoi eseguire l'override del modo in cui un componente EditText viene visualizzato sullo schermo. L'app di esempio NotePad utilizza questa funzionalità in modo efficace per creare una pagina di blocco note a righe.
  • Puoi acquisire altri eventi, come le pressioni dei tasti, e gestirli in modo personalizzato, ad esempio per un gioco.

Le sezioni seguenti spiegano come creare viste personalizzate e utilizzarle nella tua applicazione. Per informazioni di riferimento dettagliate, consulta la classe View.

Approccio di base

Ecco una panoramica generale di ciò che devi sapere per creare i tuoi componenti View:

  1. Estendi una classe o una sottoclasse View esistente con il tuo corso.
  2. Sostituire alcuni metodi dalla superclasse. I metodi della superclasse da eseguire per eseguire l'override iniziano con on, ad esempio onDraw(), onMeasure() e onKeyDown(). Questo è simile agli eventi on in Activity o ListActivity di cui esegui l'override per gli hook di ciclo di vita e altri funzionalità.
  3. Utilizza la nuova classe di estensione. Al termine, puoi utilizzare la nuova classe di estensione al posto della visualizzazione su cui si basava.

Componenti completamente personalizzati

Puoi creare componenti grafici completamente personalizzati che vengono visualizzati come preferisci. Magari vuoi un VU meter grafico che assomiglia a un vecchio indicatore analogico o una visualizzazione del testo da cantare insieme in cui una palla che rimbalza si muove lungo le parole mentre canti insieme a un apparecchio per il karaoke. Potresti volere qualcosa che i componenti integrati non sono in grado di fare, indipendentemente da come li combini.

Fortunatamente, puoi creare componenti che abbiano l'aspetto e il comportamento che preferisci, limitati solo dall'immaginazione, dalle dimensioni dello schermo e dalla potenza di elaborazione disponibile. Tieni presente che la tua applicazione potrebbe dover essere eseguita su un sistema con una potenza notevolmente inferiore a quella della workstation desktop.

Per creare un componente completamente personalizzato, considera quanto segue:

  • La visualizzazione più generica che puoi estendere è View, quindi in genere inizi con l'estensione per creare il nuovo supercomponente.
  • Puoi fornire un costruttore, che può prendere attributi e parametri dal file XML, e puoi utilizzare questi attributi e parametri personalizzati, come il colore e l'intervallo del VU meter o la larghezza e lo smorzamento dell'ago.
  • Probabilmente vorrai creare listener di eventi, funzioni di accesso alle proprietà e modificatori personalizzati, nonché un comportamento più sofisticato nella classe del componente.
  • Quasi certamente vorrai eseguire l'override di onMeasure() e probabilmente dovrai eseguire l'override di onDraw() se vuoi che il componente mostri qualcosa. Sebbene entrambi abbiano un comportamento predefinito, il valore predefinito onDraw() non fa nulla e il valore predefinito onMeasure() imposta sempre una dimensione di 100 x 100, cosa che probabilmente non ti conviene.
  • Puoi anche sostituire altri metodi di on, se necessario.

Estendi onDraw() e onMeasurement()

Il metodo onDraw() genera un Canvas tramite il quale puoi implementare tutto ciò che vuoi: grafica 2D, altri componenti standard o personalizzati, testo con stili applicati o qualsiasi altra cosa ti venga in mente.

onMeasure() è un po' più coinvolto. onMeasure() è un elemento fondamentale del contratto di rendering tra il tuo componente e il relativo contenitore. È necessario eseguire l'override di onMeasure() per poter registrare in modo efficiente e accurato le misurazioni delle parti contenute. Ciò è reso leggermente più complesso dai requisiti limite dell'elemento padre, che vengono trasferiti nel 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() con override, verrà generata un'eccezione al momento della misurazione.

A livello generale, l'implementazione di onMeasure() ha un aspetto simile al seguente:

  • Il metodo onMeasure() sostituito viene chiamato con le specifiche di larghezza e altezza, che vengono trattate come requisiti per le limitazioni relative alle misurazioni di larghezza e altezza che produci. I parametri widthMeasureSpec e heightMeasureSpec sono entrambi codici interi che rappresentano le dimensioni. Un riferimento completo al tipo di restrizioni che queste specifiche possono richiedere è disponibile nella documentazione di riferimento alla sezione View.onMeasure(int, int) Questa documentazione di riferimento spiega anche l'intera operazione di misurazione.
  • Il metodo onMeasure() del componente calcola la larghezza e l'altezza di misurazione, necessarie per il rendering del componente. Deve cercare di rimanere entro le specifiche trasmesse, anche se possono superarle. In questo caso, il genitore può scegliere cosa fare, ad esempio ritagliare, scorrere, generare un'eccezione o chiedere all'onMeasure() di riprovare, magari con specifiche di misurazione diverse.
  • Quando vengono calcolate la larghezza e l'altezza, chiama il metodo setMeasuredDimension(int width, int height) con le misure calcolate. In caso contrario, verrà applicata un'eccezione.

Ecco un riepilogo degli altri metodi standard richiamati dal framework:

Categoria Metodi Descrizione
creazione Costruttori C'è un modulo del costruttore che viene chiamato quando la vista viene creata dal codice e un modulo che viene chiamato quando la vista viene gonfiata da un file di layout. Il secondo modulo analizza e applica gli attributi definiti nel file di layout.
onFinishInflate() Viene chiamato dopo una visualizzazione e tutti i relativi elementi figlio vengono gonfiati in modo artificioso da XML.
Layout onMeasure(int, int) Viene chiamato per determinare i requisiti di dimensioni per questa vista e per tutte le relative immagini secondarie.
onLayout(boolean, int, int, int, int) Richiamato se questa vista deve assegnare dimensioni e posizione a tutti gli elementi secondari.
onSizeChanged(int, int, int, int) Richiamato se vengono modificate le dimensioni di questa visualizzazione.
Disegno onDraw(Canvas) Richiamato quando la visualizzazione deve eseguire il rendering dei propri contenuti.
Elaborazione degli eventi onKeyDown(int, KeyEvent) Richiamato quando si verifica un evento di disattivazione della chiave.
onKeyUp(int, KeyEvent) Richiamato quando si verifica un evento di key-up.
onTrackballEvent(MotionEvent) Richiamato quando si verifica un evento di movimento trackball.
onTouchEvent(MotionEvent) Richiamato quando si verifica un evento di movimento del touchscreen.
Messa a fuoco onFocusChanged(boolean, int, Rect) Richiamato se la visualizzazione acquisisce o perde l'attenzione.
onWindowFocusChanged(boolean) Richiamato quando la finestra contenente la visualizzazione acquisisce o perde lo stato attivo.
Caricamento in corso... onAttachedToWindow() Richiamato quando la vista è collegata a una finestra.
onDetachedFromWindow() Richiamato quando la visualizzazione viene scollegata dalla finestra.
onWindowVisibilityChanged(int) Richiamato se viene modificata la visibilità della finestra contenente la visualizzazione.

Controlli composti

Se non vuoi creare un componente completamente personalizzato, ma vuoi creare un componente riutilizzabile costituito da un gruppo di controlli esistenti, l'opzione migliore potrebbe essere creare un componente composto (o controllo composto). Riassumendo, questo riunisce una serie di controlli o viste più atomiche in un gruppo logico di elementi che possono essere trattati come un'unica cosa. Ad esempio, una casella combinata può essere una combinazione di un campo EditText a riga singola e di un pulsante adiacente a 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 EditText, se preferisce.

In Android, esistono altre due visualizzazioni immediatamente disponibili: Spinner e AutoCompleteTextView. Indipendentemente da ciò, questo concetto di casella combinata è un buon esempio.

Per creare un componente composto, segui questi passaggi:

  • Come nel caso di un Activity, utilizza l'approccio dichiarativo (basato su XML) per creare i componenti contenuti o nidificarli in modo programmatico a partire dal codice. In genere, il punto di partenza è un Layout di qualche tipo, quindi crea una classe che estende un Layout. Nel caso di una casella combinata, potresti utilizzare un elemento LinearLayout con orientamento orizzontale. Puoi nidificare altri layout all'interno, in modo che il componente composto possa essere arbitrariamente complesso e strutturato.
  • Nel costruttore per la nuova classe, prendi i parametri previsti dalla superclasse e passali prima al costruttore della superclasse. Quindi, puoi configurare le altre viste da utilizzare all'interno del nuovo componente. Qui puoi creare il campo EditText e l'elenco popup. Potresti introdurre i tuoi attributi e parametri nel file XML che il tuo costruttore può estrarre e utilizzare.
  • Facoltativamente, crea listener per gli eventi che potrebbero essere generati dalle viste contenute. Un esempio è un metodo listener per il listener dei clic sugli elementi dell'elenco per aggiornare i contenuti dell'EditText se viene effettuata una selezione da un elenco.
  • Se vuoi, puoi creare le tue proprietà con le funzioni di accesso e i modificatori. Ad esempio, lascia che il valore EditText sia impostato inizialmente nel componente ed esegui una query per i relativi contenuti quando necessario.
  • Se vuoi, esegui l'override di onDraw() e onMeasure(). In genere questa operazione non è necessaria quando estendi un valore Layout, poiché il layout ha un comportamento predefinito che probabilmente funziona correttamente.
  • Se vuoi, sostituisci altri metodi di on, come onKeyDown(), ad esempio per scegliere determinati valori predefiniti dall'elenco popup di una casella combinata quando viene toccata una determinata chiave.

L'utilizzo di un Layout come base per un controllo personalizzato offre alcuni vantaggi, tra cui:

  • Puoi specificare il layout utilizzando i file XML dichiarativi, proprio come con una schermata di attività, oppure puoi creare viste in modo programmatico e nidificarle nel layout dal tuo codice.
  • I metodi onDraw() e onMeasure(), oltre alla maggior parte degli altri metodi on, hanno un comportamento appropriato, quindi non è necessario eseguirne l'override.
  • È possibile costruire rapidamente viste composte complesse e riutilizzarle come se fossero un singolo componente.

Modificare un tipo di visualizzazione esistente

Se esiste un componente simile, puoi estenderlo e sostituire il comportamento da modificare. Puoi fare tutte le cose che fai con un componente completamente personalizzato, ma iniziando con una classe più specializzata nella gerarchia View, puoi ottenere un comportamento che fa ciò che vuoi senza costi.

Ad esempio, l'app di esempio NotePad mostra molti aspetti dell'utilizzo della piattaforma Android. Tra questi c'è l'estensione di una visualizzazione EditText per creare un blocco note allineato. Non è un esempio perfetto e le API per farlo potrebbero cambiare, ma dimostra i principi.

Se non l'hai ancora fatto, importa l'esempio di Blocco note in Android Studio o consulta la fonte utilizzando il link fornito. In particolare, consulta la definizione di LinedEditText nel file NoteEditor.java.

Ecco alcuni aspetti da considerare per questo file:

  1. Definizione

    La classe è definita con la seguente riga:
    public static class LinedEditText extends EditText

    LinedEditText è definita come una classe interna all'interno dell'attività NoteEditor, ma è pubblica, quindi è possibile accedervi come NoteEditor.LinedEditText dall'esterno della classe NoteEditor.

    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 piuttosto che per qualcosa di strettamente correlato a NoteEditor. Questo è un modo più semplice per creare classi interne se non hanno bisogno dell'accesso allo stato dalla classe esterna. La classe generata rimane ridotta e può essere facilmente utilizzata da altre classi.

    LinedEditText estende EditText, che in questo caso è la visualizzazione da personalizzare. Al termine, il nuovo corso può sostituire una visualizzazione EditText normale.

  2. Inizializzazione dei corsi

    Come sempre, la super è la prima chiamata. Questo non è un costruttore predefinito, ma è con parametri. L'elemento EditText viene creato con questi parametri quando viene gonfiato da un file di layout XML. Pertanto, il costruttore deve prenderli e trasmetterli anche al costruttore della superclasse.

  3. Metodi sostituiti

    In questo esempio viene eseguito solo l'override del metodo onDraw(), ma potresti dover eseguire l'override di altri quando crei i tuoi componenti personalizzati.

    Per questo esempio, se esegui l'override del metodo onDraw() puoi colorare le linee blu sul canvas della vista EditText. Il canvas viene passato al metodo onDraw() con override. Il metodo super.onDraw() viene chiamato prima della fine del metodo. Il metodo della superclasse deve essere richiamato. In questo caso, richiamalo alla fine dopo aver dipinto le righe che vuoi includere.

  4. Componente personalizzato

    Ora hai un componente personalizzato, ma come puoi utilizzarlo? Nell'esempio del blocco note, il componente personalizzato viene utilizzato direttamente dal layout dichiarativo, quindi controlla note_editor.xml nella cartella res/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 nel file XML e la classe viene specificata utilizzando il pacchetto completo. Viene fatto riferimento alla classe interna che definisci utilizzando la notazione NoteEditor$LinedEditText, che rappresenta un modo standard per fare riferimento alle classi interne nel linguaggio di programmazione Java.

    Se il componente visualizzazione personalizzato non è definito come una classe interna, puoi dichiarare il componente view con il nome dell'elemento XML ed escludere l'attributo class. Ad esempio:

    <com.example.android.notepad.LinedEditText
      id="@+id/note"
      ... />
    

    Nota che ora il corso LinedEditText è un file di classe separato. Quando la classe è nidificata nella classe NoteEditor, questa tecnica non funziona.

    Gli altri attributi e parametri nella definizione sono quelli passati al costruttore del componente personalizzato e poi trasmessi al costruttore EditText, pertanto sono gli stessi parametri che utilizzi per una vista EditText. Puoi anche aggiungere parametri personalizzati.

Creare componenti personalizzati è complicato solo se necessario.

Un componente più sofisticato può sostituire ancora più 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 il componente deve fare.