Creare visualizzazioni personalizzate

Questo codelab fa parte del corso Advanced Android in Kotlin. Per ottenere il massimo valore da questo corso, ti consigliamo di seguire i codelab in sequenza, ma non è obbligatorio. Tutti i codelab del corso sono elencati nella pagina di destinazione dei codelab Advanced Android in Kotlin.

Introduzione

Android offre un ampio insieme di sottoclassi View, come Button, TextView, EditText, ImageView, CheckBox o RadioButton. Puoi utilizzare queste sottoclassi per creare un'interfaccia utente che consenta l'interazione degli utenti e visualizzi le informazioni nella tua app. Se nessuna delle sottoclassi View soddisfa le tue esigenze, puoi creare una sottoclasse View nota come visualizzazione personalizzata.

Per creare una visualizzazione personalizzata, puoi estendere una sottoclasse View esistente (ad esempio Button o EditText) o creare una sottoclasse di View. Estendendo direttamente View, puoi creare un elemento UI interattivo di qualsiasi dimensione e forma eseguendo l'override del metodo onDraw() per disegnare View.

Dopo aver creato una visualizzazione personalizzata, puoi aggiungerla ai layout delle attività nello stesso modo in cui aggiungeresti un TextView o un Button.

Questa lezione mostra come creare una visualizzazione personalizzata da zero estendendo View.

Cosa devi già sapere

  • Come creare un'app con un'attività ed eseguirla utilizzando Android Studio.

Obiettivi didattici

  • Come estendere View per creare una visualizzazione personalizzata.
  • Come disegnare una visualizzazione personalizzata di forma circolare.
  • Come utilizzare i listener per gestire l'interazione dell'utente con la visualizzazione personalizzata.
  • Come utilizzare una visualizzazione personalizzata in un layout.

In questo lab proverai a:

  • Espandi View per creare una visualizzazione personalizzata.
  • Inizializza la visualizzazione personalizzata con i valori di disegno e pittura.
  • Esegui l'override di onDraw() per disegnare la vista.
  • Utilizza i listener per fornire il comportamento della visualizzazione personalizzata.
  • Aggiungi la visualizzazione personalizzata a un layout.

L'app CustomFanController mostra come creare una sottoclasse di visualizzazione personalizzata estendendo la classe View. La nuova sottoclasse si chiama DialView.

L'app mostra un elemento UI circolare che assomiglia a un controllo fisico della ventola, con impostazioni per spento (0), basso (1), medio (2) e alto (3). Quando l'utente tocca la visualizzazione, l'indicatore di selezione si sposta sulla posizione successiva: 0-1-2-3 e torna a 0. Inoltre, se la selezione è 1 o superiore, il colore di sfondo della parte circolare della visualizzazione cambia da grigio a verde (a indicare che la ventola è accesa).

Le visualizzazioni sono i componenti di base della UI di un'app. La classe View fornisce molte sottoclassi, chiamate widget UI, che coprono molte delle esigenze dell'interfaccia utente di una tipica app per Android.

I componenti di base dell'interfaccia utente, come Button e TextView, sono sottoclassi che estendono la classe View. Per risparmiare tempo e impegno di sviluppo, puoi estendere una di queste sottoclassi View. La visualizzazione personalizzata eredita l'aspetto e il comportamento del relativo elemento principale e puoi ignorare il comportamento o l'aspetto che vuoi modificare. Ad esempio, se estendi EditText per creare una visualizzazione personalizzata, questa si comporta esattamente come una visualizzazione EditText, ma può anche essere personalizzata per mostrare, ad esempio, un pulsante X che cancella il testo dal campo di inserimento del testo.

Puoi estendere qualsiasi sottoclasse View, ad esempio EditText, per ottenere una visualizzazione personalizzata. Scegli quella più vicina a ciò che vuoi ottenere. Puoi quindi utilizzare la visualizzazione personalizzata come qualsiasi altra sottoclasse View in uno o più layout come elemento XML con attributi.

Per creare una visualizzazione personalizzata da zero, estendi la classe View. Il tuo codice esegue l'override dei metodi View per definire l'aspetto e la funzionalità della visualizzazione. La chiave per creare una visualizzazione personalizzata è che sei responsabile del disegno dell'intero elemento UI di qualsiasi dimensione e forma sullo schermo. Se crei una sottoclasse di una visualizzazione esistente come Button, questa classe gestisce il disegno per te. Scoprirai di più sul disegno più avanti in questo codelab.

Per creare una vista personalizzata, segui questi passaggi generali:

  • Crea una classe di visualizzazione personalizzata che estenda View o una sottoclasse View (ad esempio Button o EditText).
  • Se estendi una sottoclasse View esistente, esegui l'override solo del comportamento o degli aspetti dell'aspetto che vuoi modificare.
  • Se estendi la classe View, disegna la forma della visualizzazione personalizzata e controllane l'aspetto eseguendo l'override dei metodi View come onDraw() e onMeasure() nella nuova classe.
  • Aggiungi il codice per rispondere all'interazione dell'utente e, se necessario, ridisegna la visualizzazione personalizzata.
  • Utilizza la classe di visualizzazione personalizzata come widget UI nel layout XML dell'attività. Puoi anche definire attributi personalizzati per la visualizzazione, in modo da personalizzarla in layout diversi.

In questa attività:

  • Crea un'app con un ImageView come segnaposto temporaneo per la visualizzazione personalizzata.
  • Espandi View per creare la visualizzazione personalizzata.
  • Inizializza la visualizzazione personalizzata con i valori di disegno e pittura.

Passaggio 1: crea un'app con un segnaposto ImageView

  1. Crea un'app Kotlin con il titolo CustomFanController utilizzando il modello Empty Activity. Assicurati che il nome del pacchetto sia com.example.android.customfancontroller.
  2. Apri activity_main.xml nella scheda Testo per modificare il codice XML.
  3. Sostituisci il codice TextView esistente con questo codice. Questo testo funge da etichetta nell'attività per la visualizzazione personalizzata.
<TextView
       android:id="@+id/customViewLabel"
       android:textAppearance="@style/Base.TextAppearance.AppCompat.Display3"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:padding="16dp"
       android:textColor="@android:color/black"
       android:layout_marginStart="8dp"
       android:layout_marginEnd="8dp"
       android:layout_marginTop="24dp"
       android:text="Fan Control"
       app:layout_constraintLeft_toLeftOf="parent"
       app:layout_constraintRight_toRightOf="parent"
       app:layout_constraintTop_toTopOf="parent"/>
  1. Aggiungi questo elemento ImageView al layout. Questo è un segnaposto per la visualizzazione personalizzata che creerai in questo codelab.
<ImageView
       android:id="@+id/dialView"
       android:layout_width="200dp"
       android:layout_height="200dp"
       android:background="@android:color/darker_gray"
       app:layout_constraintTop_toBottomOf="@+id/customViewLabel"
       app:layout_constraintLeft_toLeftOf="parent"
       app:layout_constraintRight_toRightOf="parent"
       android:layout_marginLeft="8dp"
       android:layout_marginRight="8dp"
       android:layout_marginTop="8dp"/>
  1. Estrai le risorse stringa e dimensione in entrambi gli elementi dell'interfaccia utente.
  2. Fai clic sulla scheda Design. Il layout dovrebbe avere il seguente aspetto:

Passaggio 2. Crea la classe di visualizzazione personalizzata

  1. Crea una nuova classe Kotlin chiamata DialView.
  2. Modifica la definizione della classe per estendere View. Importa android.view.View quando richiesto.
  3. Fai clic su View e poi sull'icona della lampadina rossa. Scegli Aggiungi costruttori di visualizzazioni Android utilizzando "@JvmOverloads". Android Studio aggiunge il costruttore dalla classe View. L'annotazione @JvmOverloads indica al compilatore Kotlin di generare overload per questa funzione che sostituiscono i valori predefiniti dei parametri.
class DialView @JvmOverloads constructor(
   context: Context,
   attrs: AttributeSet? = null,
   defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
  1. Sopra la definizione della classe DialView, appena sotto le importazioni, aggiungi un enum di primo livello per rappresentare le velocità della ventola disponibili. Tieni presente che questo enum è di tipo Int perché i valori sono risorse stringa anziché stringhe effettive. Android Studio mostrerà gli errori per le risorse stringa mancanti in ciascuno di questi valori, che correggerai in un passaggio successivo.
private enum class FanSpeed(val label: Int) {
   OFF(R.string.fan_off),
   LOW(R.string.fan_low),
   MEDIUM(R.string.fan_medium),
   HIGH(R.string.fan_high);
}
  1. Sotto enum, aggiungi queste costanti. Li utilizzerai per disegnare gli indicatori e le etichette del quadrante.
private const val RADIUS_OFFSET_LABEL = 30      
private const val RADIUS_OFFSET_INDICATOR = -35
  1. All'interno della classe DialView, definisci diverse variabili necessarie per disegnare la visualizzazione personalizzata. Importa android.graphics.PointF se richiesto.
private var radius = 0.0f                   // Radius of the circle.
private var fanSpeed = FanSpeed.OFF         // The active selection.
// position variable which will be used to draw label and indicator circle position
private val pointPosition: PointF = PointF(0.0f, 0.0f)
  • radius è il raggio attuale del cerchio. Questo valore viene impostato quando la visualizzazione viene disegnata sullo schermo.
  • fanSpeed è la velocità attuale della ventola, che è uno dei valori dell'enumerazione FanSpeed. Per impostazione predefinita, questo valore è OFF.
  • Infine postPosition è un punto X,Y che verrà utilizzato per disegnare diversi elementi della visualizzazione sullo schermo.

Questi valori vengono creati e inizializzati qui anziché quando la visualizzazione viene effettivamente disegnata, per garantire che il passaggio di disegno effettivo venga eseguito il più rapidamente possibile.

  1. Inoltre, all'interno della definizione della classe DialView, inizializza un oggetto Paint con alcuni stili di base. Importa android.graphics.Paint e android.graphics.Typeface quando richiesto. Come in precedenza con le variabili, questi stili vengono inizializzati qui per velocizzare il passaggio di disegno.
private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
   style = Paint.Style.FILL
   textAlign = Paint.Align.CENTER
   textSize = 55.0f
   typeface = Typeface.create( "", Typeface.BOLD)
}
  1. Apri res/values/strings.xml e aggiungi le risorse stringa per le velocità della ventola:
<string name="fan_off">off</string>
<string name="fan_low">1</string>
<string name="fan_medium">2</string>
<string name="fan_high">3</string>

Una volta creata una visualizzazione personalizzata, devi essere in grado di disegnarla. Quando estendi una sottoclasse View come EditText, questa sottoclasse definisce l'aspetto e gli attributi della visualizzazione e si disegna sullo schermo. Di conseguenza, non devi scrivere codice per disegnare la visualizzazione. Puoi sostituire i metodi dell'organizzazione principale per personalizzare la visualizzazione.

Se crei una visualizzazione personalizzata da zero (estendendo View), sei responsabile del disegno dell'intera visualizzazione ogni volta che lo schermo si aggiorna e dell'override dei metodi View che gestiscono il disegno. Per disegnare correttamente una visualizzazione personalizzata che si estende a View, devi:

  • Calcola le dimensioni della visualizzazione quando viene visualizzata per la prima volta e ogni volta che cambiano, eseguendo l'override del metodo onSizeChanged().
  • Esegui l'override del metodo onDraw() per disegnare la visualizzazione personalizzata, utilizzando un oggetto Canvas con lo stile di un oggetto Paint.
  • Chiama il metodo invalidate() quando rispondi a un clic dell'utente che modifica il modo in cui viene disegnata la visualizzazione per invalidare l'intera visualizzazione, forzando così una chiamata a onDraw() per ridisegnare la visualizzazione.

Il metodo onDraw() viene chiamato ogni volta che lo schermo si aggiorna, il che può avvenire molte volte al secondo. Per motivi di prestazioni e per evitare problemi visivi, devi svolgere il minor lavoro possibile in onDraw(). In particolare, non inserire allocazioni in onDraw(), perché le allocazioni possono comportare una garbage collection che può causare un'interruzione visiva.

Le classi Canvas e Paint offrono una serie di utili scorciatoie di disegno:

Scoprirai di più su Canvas e Paint in un codelab successivo. Per scoprire di più su come Android disegna le visualizzazioni, consulta Come Android disegna le visualizzazioni.

In questa attività disegnerai la visualizzazione personalizzata del controller della ventola sullo schermo, ovvero il quadrante stesso, l'indicatore della posizione corrente e le etichette degli indicatori, con i metodi onSizeChanged() e onDraw(). Creerai anche un metodo helper, computeXYForSpeed(),, per calcolare la posizione X,Y corrente dell'etichetta dell'indicatore sul quadrante.

Passaggio 1: Calcolare le posizioni e disegnare la visualizzazione

  1. Nella classe DialView, sotto le inizializzazioni, esegui l'override del metodo onSizeChanged() della classe View per calcolare le dimensioni del quadrante della visualizzazione personalizzata. Importa kotlin.math.min quando richiesto.

    Il metodo onSizeChanged() viene chiamato ogni volta che le dimensioni della visualizzazione cambiano, inclusa la prima volta che viene disegnata quando il layout viene gonfiato. Esegui l'override di onSizeChanged() per calcolare posizioni, dimensioni e qualsiasi altro valore correlato alle dimensioni della visualizzazione personalizzata, anziché ricalcolarli ogni volta che disegni. In questo caso, utilizzi onSizeChanged() per calcolare il raggio attuale dell'elemento circolare del quadrante.
override fun onSizeChanged(width: Int, height: Int, oldWidth: Int, oldHeight: Int) {
   radius = (min(width, height) / 2.0 * 0.8).toFloat()
}
  1. Sotto onSizeChanged(), aggiungi questo codice per definire una funzione di estensione computeXYForSpeed() per la classe PointF . Importa kotlin.math.cos e kotlin.math.sin quando richiesto. Questa funzione di estensione della classe PointF calcola le coordinate X e Y sullo schermo per l'etichetta di testo e l'indicatore corrente (0, 1, 2 o 3), data la posizione FanSpeed corrente e il raggio del quadrante. Lo utilizzerai in onDraw().
private fun PointF.computeXYForSpeed(pos: FanSpeed, radius: Float) {
   // Angles are in radians.
   val startAngle = Math.PI * (9 / 8.0)   
   val angle = startAngle + pos.ordinal * (Math.PI / 4)
   x = (radius * cos(angle)).toFloat() + width / 2
   y = (radius * sin(angle)).toFloat() + height / 2
}
  1. Esegui l'override del metodo onDraw() per visualizzare la schermata con le classi Canvas e Paint. Importa android.graphics.Canvas quando richiesto. Questo è l'override dello scheletro:
override fun onDraw(canvas: Canvas) {
   super.onDraw(canvas)
   
}
  1. All'interno di onDraw(), aggiungi questa riga per impostare il colore della vernice su grigio (Color.GRAY) o verde (Color.GREEN) a seconda che la velocità della ventola sia OFF o un altro valore. Importa android.graphics.Color quando richiesto.
// Set dial background color to green if selection not off.
paint.color = if (fanSpeed == FanSpeed.OFF) Color.GRAY else Color.GREEN
  1. Aggiungi questo codice per disegnare un cerchio per il quadrante con il metodo drawCircle(). Questo metodo utilizza la larghezza e l'altezza della visualizzazione corrente per trovare il centro del cerchio, il raggio del cerchio e il colore della pittura corrente. Le proprietà width e height sono membri della superclasse View e indicano le dimensioni attuali della visualizzazione.
// Draw the dial.
canvas.drawCircle((width / 2).toFloat(), (height / 2).toFloat(), radius, paint)
  1. Aggiungi il seguente codice per disegnare un cerchio più piccolo per il segno dell'indicatore della velocità della ventola, anche con il metodo drawCircle(). Questa parte utilizza PointF.Metodo di estensione computeXYforSpeed() per calcolare le coordinate X,Y del centro dell'indicatore in base alla velocità della ventola corrente.
// Draw the indicator circle.
val markerRadius = radius + RADIUS_OFFSET_INDICATOR
pointPosition.computeXYForSpeed(fanSpeed, markerRadius)
paint.color = Color.BLACK
canvas.drawCircle(pointPosition.x, pointPosition.y, radius/12, paint)
  1. Infine, disegna le etichette della velocità della ventola (0, 1, 2, 3) nelle posizioni appropriate intorno al quadrante. Questa parte del metodo chiama di nuovo PointF.computeXYForSpeed() per ottenere la posizione di ogni etichetta e riutilizza l'oggetto pointPosition ogni volta per evitare allocazioni. Utilizza drawText() per disegnare le etichette.
// Draw the text labels.
val labelRadius = radius + RADIUS_OFFSET_LABEL
for (i in FanSpeed.values()) {
   pointPosition.computeXYForSpeed(i, labelRadius)
   val label = resources.getString(i.label)
   canvas.drawText(label, pointPosition.x, pointPosition.y, paint)
}

Il metodo onDraw() completato ha il seguente aspetto:

override fun onDraw(canvas: Canvas) {
   super.onDraw(canvas)
   // Set dial background color to green if selection not off.
   paint.color = if (fanSpeed == FanSpeed.OFF) Color.GRAY else Color.GREEN
   // Draw the dial.
   canvas.drawCircle((width / 2).toFloat(), (height / 2).toFloat(), radius, paint)
   // Draw the indicator circle.
   val markerRadius = radius + RADIUS_OFFSET_INDICATOR
   pointPosition.computeXYForSpeed(fanSpeed, markerRadius)
   paint.color = Color.BLACK
   canvas.drawCircle(pointPosition.x, pointPosition.y, radius/12, paint)
   // Draw the text labels.
   val labelRadius = radius + RADIUS_OFFSET_LABEL
   for (i in FanSpeed.values()) {
       pointPosition.computeXYForSpeed(i, labelRadius)
       val label = resources.getString(i.label)
       canvas.drawText(label, pointPosition.x, pointPosition.y, paint)
   }
}

Passaggio 2: Aggiungere la visualizzazione al layout

Per aggiungere una visualizzazione personalizzata all'interfaccia utente di un'app, specificala come elemento nel layout XML dell'attività. Controlla il suo aspetto e il suo comportamento con gli attributi degli elementi XML, come faresti per qualsiasi altro elemento dell'interfaccia utente.

  1. In activity_main.xml, modifica il tag ImageView per dialView in com.example.android.customfancontroller.DialView ed elimina l'attributo android:background. Sia DialView sia l'originale ImageView ereditano gli attributi standard dalla classe View, quindi non è necessario modificare nessuno degli altri attributi. Il nuovo elemento DialView ha il seguente aspetto:
<com.example.android.customfancontroller.DialView
       android:id="@+id/dialView"
       android:layout_width="@dimen/fan_dimen"
       android:layout_height="@dimen/fan_dimen"
       app:layout_constraintTop_toBottomOf="@+id/customViewLabel"
       app:layout_constraintLeft_toLeftOf="parent"
       app:layout_constraintRight_toRightOf="parent"
       android:layout_marginLeft="@dimen/default_margin"
       android:layout_marginRight="@dimen/default_margin"
       android:layout_marginTop="@dimen/default_margin" />
  1. Esegui l'app. La visualizzazione del controllo della ventola viene visualizzata nell'attività.

L'ultima attività consiste nell'attivare la visualizzazione personalizzata per eseguire un'azione quando l'utente tocca la visualizzazione. Ogni tocco deve spostare l'indicatore di selezione alla posizione successiva: off-1-2-3 e di nuovo off. Inoltre, se la selezione è 1 o superiore, cambia lo sfondo da grigio a verde, indicando che la ventola è accesa.

Per attivare la possibilità di fare clic sulla visualizzazione personalizzata:

  • Imposta la proprietà isClickable della vista su true. In questo modo, la visualizzazione personalizzata può rispondere ai clic.
  • Implementa il metodo performClick() della classe View per eseguire operazioni quando viene fatto clic sulla visualizzazione.
  • Chiama il metodo invalidate(). In questo modo, il sistema Android chiama il metodo onDraw() per ridisegnare la visualizzazione.

Normalmente, con una visualizzazione Android standard, implementi OnClickListener() per eseguire un'azione quando l'utente fa clic su quella visualizzazione. Per una visualizzazione personalizzata, implementa invece il metodo performClick() della classe View e chiama super.performClick(). Il metodo performClick() predefinito chiama anche onClickListener(), quindi puoi aggiungere le tue azioni a performClick() e lasciare onClickListener() disponibile per ulteriori personalizzazioni da parte tua o di altri sviluppatori che potrebbero utilizzare la tua visualizzazione personalizzata.

  1. In DialView.kt, all'interno dell'enumerazione FanSpeed, aggiungi una funzione di estensione next() che cambia la velocità della ventola attuale con la velocità successiva nell'elenco (da OFF a LOW, MEDIUM e HIGH e poi di nuovo a OFF). L'enumerazione completa ora è simile a questa:
private enum class FanSpeed(val label: Int) {
   OFF(R.string.fan_off),
   LOW(R.string.fan_low),
   MEDIUM(R.string.fan_medium),
   HIGH(R.string.fan_high);

   fun next() = when (this) {
       OFF -> LOW
       LOW -> MEDIUM
       MEDIUM -> HIGH
       HIGH -> OFF
   }
}
  1. All'interno della classe DialView, appena prima del metodo onSizeChanged(), aggiungi un blocco init(). Se la proprietà isClickable della visualizzazione è impostata su true, la visualizzazione può accettare l'input dell'utente.
init {
   isClickable = true
}
  1. Sotto init(),, esegui l'override del metodo performClick() con il codice riportato di seguito.
override fun performClick(): Boolean {
   if (super.performClick()) return true

   fanSpeed = fanSpeed.next()
   contentDescription = resources.getString(fanSpeed.label)
  
   invalidate()
   return true
}

La chiamata a super.performClick() deve avvenire per primo, in quanto attiva gli eventi di accessibilità e le chiamate onClickListener().

Le due righe successive aumentano la velocità della ventola con il metodo next() e impostano la descrizione dei contenuti della visualizzazione sulla risorsa stringa che rappresenta la velocità corrente (off, 1, 2 o 3).

Infine, il metodo invalidate() invalida l'intera visualizzazione, forzando una chiamata a onDraw() per ridisegnare la visualizzazione. Se qualcosa nella visualizzazione personalizzata cambia per qualsiasi motivo, inclusa l'interazione dell'utente, e la modifica deve essere visualizzata, chiama invalidate().

  1. Esegui l'app. Tocca l'elemento DialView per spostare l'indicatore da Off a 1. Il quadrante dovrebbe diventare verde. A ogni tocco, l'indicatore deve spostarsi nella posizione successiva. Quando l'indicatore torna su Off, il quadrante dovrebbe tornare grigio.

Questo esempio mostra il meccanismo di base dell'utilizzo degli attributi personalizzati con la visualizzazione personalizzata. Definisci gli attributi personalizzati per la classe DialView con un colore diverso per ogni posizione del selettore della ventola.

  1. Crea e apri res/values/attrs.xml.
  2. All'interno di <resources>, aggiungi un elemento risorsa <declare-styleable>.
  3. All'interno dell'elemento risorsa <declare-styleable>, aggiungi tre elementi attr, uno per ogni attributo, con un name e un format. format è come un tipo e, in questo caso, è color.
<?xml version="1.0" encoding="utf-8"?>
<resources>
       <declare-styleable name="DialView">
           <attr name="fanColor1" format="color" />
           <attr name="fanColor2" format="color" />
           <attr name="fanColor3" format="color" />
       </declare-styleable>
</resources>
  1. Apri il file di layout activity_main.xml.
  2. In DialView, aggiungi gli attributi per fanColor1, fanColor2 e fanColor3 e imposta i relativi valori sui colori mostrati di seguito. Utilizza app: come prefisso per l'attributo personalizzato (come in app:fanColor1) anziché android: perché gli attributi personalizzati appartengono allo spazio dei nomi schemas.android.com/apk/res/your_app_package_name anziché allo spazio dei nomi android.
app:fanColor1="#FFEB3B"
app:fanColor2="#CDDC39"
app:fanColor3="#009688"

Per utilizzare gli attributi nella classe DialView, devi recuperarli. Vengono memorizzati in un AttributeSet, che viene consegnato alla classe al momento della creazione, se esiste. Recuperi gli attributi in init e assegni i valori degli attributi alle variabili locali per la memorizzazione nella cache.

  1. Apri il file del corso DialView.kt.
  2. All'interno di DialView, dichiara le variabili per memorizzare nella cache i valori degli attributi.
private var fanSpeedLowColor = 0
private var fanSpeedMediumColor = 0
private var fanSeedMaxColor = 0
  1. Nel blocco init, aggiungi il seguente codice utilizzando la funzione di estensione withStyledAttributes. Fornisci gli attributi e la visualizzazione e imposta le variabili locali. L'importazione di withStyledAttributes importerà anche la funzione getColor() corretta.
context.withStyledAttributes(attrs, R.styleable.DialView) {
   fanSpeedLowColor = getColor(R.styleable.DialView_fanColor1, 0)
   fanSpeedMediumColor = getColor(R.styleable.DialView_fanColor2, 0)
   fanSeedMaxColor = getColor(R.styleable.DialView_fanColor3, 0)
}
  1. Utilizza le variabili locali in onDraw() per impostare il colore del quadrante in base alla velocità della ventola corrente. Sostituisci la riga in cui è impostato il colore della pittura (paint.color = if (fanSpeed == FanSpeed.OFF) Color.GRAY else Color.GREEN) con il codice riportato di seguito.
paint.color = when (fanSpeed) {
   FanSpeed.OFF -> Color.GRAY
   FanSpeed.LOW -> fanSpeedLowColor
   FanSpeed.MEDIUM -> fanSpeedMediumColor
   FanSpeed.HIGH -> fanSeedMaxColor
} as Int
  1. Esegui l'app, fai clic sul quadrante e l'impostazione del colore dovrebbe essere diversa per ogni posizione, come mostrato di seguito.

Per saperne di più sugli attributi delle visualizzazioni personalizzate, consulta Creazione di una classe di visualizzazione.

L'accessibilità è un insieme di tecniche di progettazione, implementazione e test che consentono a tutti di utilizzare la tua app, comprese le persone con disabilità.

Le disabilità comuni che possono influire sull'utilizzo di un dispositivo Android includono cecità, ipovisione, daltonismo, sordità o perdita dell'udito e capacità motorie limitate. Se sviluppi le tue app tenendo presente l'accessibilità, migliori l'esperienza utente non solo per gli utenti con queste disabilità, ma anche per tutti gli altri.

Android fornisce diverse funzionalità di accessibilità per impostazione predefinita nelle visualizzazioni dell'interfaccia utente standard, come TextView e Button. Quando crei una visualizzazione personalizzata, devi però considerare in che modo questa fornirà funzionalità di accessibilità come le descrizioni vocali dei contenuti sullo schermo.

In questa attività imparerai a utilizzare TalkBack, lo screen reader di Android, e a modificare l'app per includere suggerimenti e descrizioni vocali per la visualizzazione personalizzata DialView.

Passaggio 1. Esplorare TalkBack

TalkBack è lo screen reader integrato di Android. Con TalkBack attivato, l'utente può interagire con il proprio dispositivo Android senza vedere lo schermo, perché Android descrive ad alta voce gli elementi dello schermo. Gli utenti con disabilità visive potrebbero fare affidamento su TalkBack per utilizzare la tua app.

In questa attività, attivi TalkBack per capire come funzionano gli screen reader e come navigare nelle app.

  1. Su un dispositivo o emulatore Android, vai a Impostazioni > Accessibilità > TalkBack.
  2. Tocca il pulsante di attivazione/disattivazione On/Off per attivare TalkBack.
  3. Tocca Ok per confermare le autorizzazioni.
  4. Se richiesto, conferma la password del dispositivo. Se è la prima volta che esegui TalkBack, viene avviato un tutorial. Il tutorial potrebbe non essere disponibile sui dispositivi meno recenti.
  5. Potrebbe essere utile seguire il tutorial a occhi chiusi. Per aprire di nuovo il tutorial in futuro, vai a Impostazioni > Accessibilità > TalkBack > Impostazioni > Avvia tutorial di TalkBack.
  6. Compila ed esegui l'app CustomFanController oppure aprila con il pulsante Panoramica o Recenti sul tuo dispositivo. Con TalkBack attivo, noterai che viene annunciato il nome dell'app, nonché il testo dell'etichetta TextView ("Controllo ventola"). Tuttavia, se tocchi la visualizzazione DialView, non vengono fornite informazioni sullo stato della visualizzazione (l'impostazione corrente del quadrante) o sull'azione che verrà eseguita quando tocchi la visualizzazione per attivarla.

Passaggio 2: Aggiungere descrizioni dei contenuti per le etichette del quadrante

Le descrizioni dei contenuti descrivono il significato e lo scopo delle visualizzazioni nella tua app. Queste etichette consentono agli screen reader come la funzionalità TalkBack di Android di spiegare con precisione la funzione di ogni elemento. Per le visualizzazioni statiche come ImageView, puoi aggiungere la descrizione dei contenuti alla visualizzazione nel file di layout con l'attributo contentDescription. Le visualizzazioni di testo (TextView e EditText) utilizzano automaticamente il testo della visualizzazione come descrizione dei contenuti.

Per la visualizzazione del controllo personalizzato della ventola, devi aggiornare dinamicamente la descrizione dei contenuti ogni volta che viene fatto clic sulla visualizzazione, per indicare l'impostazione della ventola corrente.

  1. Nella parte inferiore della classe DialView, dichiara una funzione updateContentDescription() senza argomenti o tipo restituito.
fun updateContentDescription() {
}
  1. All'interno di updateContentDescription(), modifica la proprietà contentDescription per la visualizzazione personalizzata nella risorsa stringa associata alla velocità della ventola attuale (spenta, 1, 2 o 3). Queste sono le stesse etichette utilizzate in onDraw() quando il quadrante viene disegnato sullo schermo.
fun updateContentDescription() {
   contentDescription = resources.getString(fanSpeed.label)
}
  1. Scorri verso l'alto fino al blocco init() e aggiungi una chiamata a updateContentDescription() alla fine del blocco. In questo modo, la descrizione dei contenuti viene inizializzata all'inizializzazione della visualizzazione.
init {
   isClickable = true
   // ...

   updateContentDescription()
}
  1. Aggiungi un'altra chiamata a updateContentDescription() nel metodo performClick(), appena prima di invalidate().
override fun performClick(): Boolean {
   if (super.performClick()) return true
   fanSpeed = fanSpeed.next()
   updateContentDescription()
   invalidate()
   return true
}
  1. Compila ed esegui l'app e assicurati che TalkBack sia attivo. Tocca per modificare l'impostazione per la visualizzazione del quadrante e nota che ora TalkBack annuncia l'etichetta corrente (off, 1, 2, 3) e la frase "Tocca due volte per attivare".

Passaggio 3: Aggiungere ulteriori informazioni per l'azione di clic

Puoi fermarti qui e la tua visualizzazione sarà utilizzabile in TalkBack. Tuttavia, sarebbe utile se la visualizzazione potesse indicare non solo che può essere attivata ("Tocca due volte per attivare"), ma anche spiegare cosa succederà quando la visualizzazione viene attivata ("Tocca due volte per modificare" o "Tocca due volte per ripristinare").

Per farlo, aggiungi informazioni sull'azione della visualizzazione (in questo caso, un clic o un tocco) a un oggetto di informazioni sul nodo di accessibilità tramite un delegato di accessibilità. Un delegato di accessibilità ti consente di personalizzare le funzionalità correlate all'accessibilità della tua app tramite la composizione (anziché l'ereditarietà).

Per questa attività utilizzerai le classi di accessibilità nelle librerie Android Jetpack (androidx.*) per garantire la compatibilità con le versioni precedenti.

  1. In DialView.kt, nel blocco init, imposta un delegato di accessibilità nella visualizzazione come nuovo oggetto AccessibilityDelegateCompat. Importa androidx.core.view.ViewCompat e androidx.core.view.AccessibilityDelegateCompat quando richiesto. Questa strategia consente la massima compatibilità con le versioni precedenti nella tua app.
ViewCompat.setAccessibilityDelegate(this, object : AccessibilityDelegateCompat() {
   
})
  1. All'interno dell'oggetto AccessibilityDelegateCompat, esegui l'override della funzione onInitializeAccessibilityNodeInfo() con un oggetto AccessibilityNodeInfoCompat e chiama il metodo della superclasse. Importa androidx.core.view.accessibility.AccessibilityNodeInfoCompat quando richiesto.
ViewCompat.setAccessibilityDelegate(this, object : AccessibilityDelegateCompat() {
   override fun onInitializeAccessibilityNodeInfo(host: View, 
                            info: AccessibilityNodeInfoCompat) {
      super.onInitializeAccessibilityNodeInfo(host, info)

   }  
})

Ogni visualizzazione ha una struttura ad albero di nodi di accessibilità, che possono corrispondere o meno ai componenti di layout effettivi della visualizzazione. I servizi di accessibilità di Android esplorano questi nodi per trovare informazioni sulla visualizzazione (ad esempio descrizioni dei contenuti leggibili o possibili azioni che possono essere eseguite su quella visualizzazione). Quando crei una visualizzazione personalizzata, potresti anche dover ignorare le informazioni sul nodo per fornire informazioni personalizzate per l'accessibilità. In questo caso, sostituirai le informazioni del nodo per indicare che sono presenti informazioni personalizzate per l'azione della visualizzazione.

  1. All'interno di onInitializeAccessibilityNodeInfo(), crea un nuovo oggetto AccessibilityNodeInfoCompat.AccessibilityActionCompat e assegnalo alla variabile customClick. Passa al costruttore la costante AccessibilityNodeInfo.ACTION_CLICK e una stringa segnaposto. Importa AccessibilityNodeInfo quando richiesto.
ViewCompat.setAccessibilityDelegate(this, object : AccessibilityDelegateCompat() {
   override fun onInitializeAccessibilityNodeInfo(host: View, 
                            info: AccessibilityNodeInfoCompat) {
      super.onInitializeAccessibilityNodeInfo(host, info)
      val customClick = AccessibilityNodeInfoCompat.AccessibilityActionCompat(
         AccessibilityNodeInfo.ACTION_CLICK,
        "placeholder"
      )
   }  
})

La classe AccessibilityActionCompat rappresenta un'azione su una visualizzazione a scopo di accessibilità. Un'azione tipica è un clic o un tocco, come in questo caso, ma altre azioni possono includere l'acquisizione o la perdita dello stato attivo, un'operazione sugli appunti (taglia/copia/incolla) o lo scorrimento all'interno della visualizzazione. Il costruttore di questa classe richiede una costante di azione (in questo caso, AccessibilityNodeInfo.ACTION_CLICK) e una stringa utilizzata da TalkBack per indicare l'azione.

  1. Sostituisci la stringa "placeholder" con una chiamata a context.getString() per recuperare una risorsa stringa. Per la risorsa specifica, verifica la velocità attuale della ventola. Se la velocità è attualmente FanSpeed.HIGH, la stringa è "Reset". Se la velocità della ventola è diversa, la stringa è "Change.". Creerai queste risorse stringa in un passaggio successivo.
ViewCompat.setAccessibilityDelegate(this, object : AccessibilityDelegateCompat() {
   override fun onInitializeAccessibilityNodeInfo(host: View, 
                            info: AccessibilityNodeInfoCompat) {
      super.onInitializeAccessibilityNodeInfo(host, info)
      val customClick = AccessibilityNodeInfoCompat.AccessibilityActionCompat(
         AccessibilityNodeInfo.ACTION_CLICK,
        context.getString(if (fanSpeed !=  FanSpeed.HIGH) R.string.change else R.string.reset)
      )
   }  
})
  1. Dopo la parentesi chiusa della definizione di customClick, utilizza il metodo addAction() per aggiungere la nuova azione di accessibilità all'oggetto info nodo.
ViewCompat.setAccessibilityDelegate(this, object : AccessibilityDelegateCompat() {
   override fun onInitializeAccessibilityNodeInfo(host: View, 
                            info: AccessibilityNodeInfoCompat) {
       super.onInitializeAccessibilityNodeInfo(host, info)
       val customClick = AccessibilityNodeInfoCompat.AccessibilityActionCompat(
           AccessibilityNodeInfo.ACTION_CLICK,
           context.getString(if (fanSpeed !=  FanSpeed.HIGH) 
                                 R.string.change else R.string.reset)
       )
       info.addAction(customClick)
   }
})
  1. In res/values/strings.xml, aggiungi le risorse stringa per "Modifica" e "Reimposta".
<string name="change">Change</string>
<string name="reset">Reset</string>
  1. Compila ed esegui l'app e assicurati che TalkBack sia attivo. Ora noterai che la frase "Tocca due volte per attivare" è diventata "Tocca due volte per cambiare" (se la velocità della ventola è inferiore a elevata o 3) o "Tocca due volte per ripristinare" (se la velocità della ventola è già elevata o 3). Tieni presente che il prompt "Tocca due volte per…" viene fornito dal servizio TalkBack stesso.

Scarica il codice per il codelab completato.

$  git clone https://github.com/googlecodelabs/android-kotlin-drawing-custom-views


In alternativa, puoi scaricare il repository come file ZIP, decomprimerlo e aprirlo in Android Studio.

Scarica zip

  • Per creare una visualizzazione personalizzata che erediti l'aspetto e il comportamento di una sottoclasse View come EditText, aggiungi una nuova classe che estenda la sottoclasse e apporti modifiche eseguendo l'override di alcuni metodi della sottoclasse.
  • Per creare una visualizzazione personalizzata di qualsiasi dimensione e forma, aggiungi una nuova classe che estenda View.
  • Esegui l'override dei metodi View, ad esempio onDraw(), per definire la forma e l'aspetto di base della visualizzazione.
  • Utilizza invalidate() per forzare il disegno o il ridisegno della visualizzazione.
  • Per ottimizzare le prestazioni, alloca le variabili e assegna i valori richiesti per il disegno e la pittura prima di utilizzarli in onDraw(), ad esempio nell'inizializzazione delle variabili membro.
  • Esegui l'override di performClick() anziché di OnClickListener() nella visualizzazione personalizzata per fornire il comportamento interattivo della visualizzazione. In questo modo, tu o altri sviluppatori Android che potrebbero utilizzare la tua classe di visualizzazione personalizzata potrete utilizzare onClickListener() per fornire un comportamento aggiuntivo.
  • Aggiungi la visualizzazione personalizzata a un file di layout XML con attributi per definirne l'aspetto, come faresti con altri elementi dell'interfaccia utente.
  • Crea il file attrs.xml nella cartella values per definire gli attributi personalizzati. Puoi quindi utilizzare gli attributi personalizzati per la visualizzazione personalizzata nel file di layout XML.

Corso Udacity:

Documentazione per sviluppatori Android:

Video:

Questa sezione elenca i possibili compiti a casa per gli studenti che seguono questo codelab nell'ambito di un corso guidato da un insegnante. Spetta all'insegnante:

  • Assegna i compiti, se richiesto.
  • Comunica agli studenti come inviare i compiti.
  • Valuta i compiti a casa.

Gli insegnanti possono utilizzare questi suggerimenti nella misura che ritengono opportuna e sono liberi di assegnare qualsiasi altro compito a casa che ritengono appropriato.

Se stai seguendo questo codelab in autonomia, sentiti libero di utilizzare questi compiti per casa per mettere alla prova le tue conoscenze.

Domanda 1

Per calcolare le posizioni, le dimensioni e qualsiasi altro valore quando alla visualizzazione personalizzata viene assegnata una dimensione per la prima volta, quale metodo esegui l'override?

onMeasure()

onSizeChanged()

invalidate()

onDraw()

Domanda 2

Per indicare che vuoi che la visualizzazione venga ridisegnata con onDraw(), quale metodo chiami dal thread UI dopo che il valore di un attributo è cambiato?

▢ onMeasure()

▢ onSizeChanged()

▢ invalidate()

▢ getVisibility()

Domanda 3

Quale metodo View devi eseguire l'override per aggiungere interattività alla tua visualizzazione personalizzata?

▢ setOnClickListener()

▢ onSizeChanged()

▢ isClickable()

▢ performClick()

Per i link ad altri codelab di questo corso, consulta la pagina di destinazione dei codelab Advanced Android in Kotlin.