Creazione di viste personalizzate

Questo codelab fa parte del corso Advanced Android in Kotlin. Otterrai il massimo valore da questo corso se lavori in sequenza nei codelab, ma non è obbligatorio. Tutti i codelab del corso sono elencati nella pagina di destinazione avanzata per i codelab di Android in Kotlin.

Introduzione

Android offre una vasta gamma di sottoclassi di View, come Button, TextView, EditText, ImageView, CheckBox o RadioButton. Puoi utilizzare queste sottoclassi per creare un'interfaccia utente che consenta l'interazione dell'utente e mostra 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 vista personalizzata puoi estendere una sottoclasse View esistente (ad es. Button o EditText) oppure creare una tua sottoclasse di View. Estendendo direttamente View, puoi creare un elemento UI interattivo di qualsiasi dimensione e forma eseguendo l'override del metodo onDraw() per tracciarlo con View.

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

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

Informazioni importanti

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

Obiettivi didattici

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

In questo lab proverai a:

  • Estendi View per creare una visualizzazione personalizzata.
  • Inizializza la visualizzazione personalizzata con valori di disegno e pittura.
  • Sostituisci onDraw() per disegnare la visualizzazione.
  • Utilizza gli ascoltatori per fornire il comportamento della visualizzazione personalizzata.
  • Aggiungere la visualizzazione personalizzata a un layout.

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

L'app mostra un elemento dell'interfaccia utente 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 vista, l'indicatore di selezione passa alla posizione successiva: 0-1-2-3 e poi di nuovo a 0. Inoltre, se la selezione è 1 o superiore, il colore di sfondo della parte circolare della visualizzazione passa da grigio a verde (indicando che la ventola è accesa).

Le visualizzazioni sono gli elementi di base dell'interfaccia utente di un'app. Il corso View fornisce molte sottoclassi, chiamate widget dell'interfaccia utente, che coprono molte delle esigenze di una tipica interfaccia utente di app Android.

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

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

Per creare da zero la tua visualizzazione personalizzata, estendi il corso View. Il codice sostituisce i metodi View per definire l'aspetto e la funzionalità della vista. Per creare una visualizzazione personalizzata, hai la responsabilità di disegnare l'intero elemento UI di qualsiasi dimensione e forma sullo schermo. Se sottoclassi una vista esistente come Button, tale corso gestisce il disegno per tuo conto. Scoprirai come disegnare più avanti in questo codelab.

Per creare una vista personalizzata:

  • Crea una classe di visualizzazione personalizzata che estende View o estende una sottoclasse View (ad esempio Button o EditText).
  • Se estendi una sottoclasse View esistente, sostituisci solo il comportamento o gli aspetti dell'aspetto che vuoi modificare.
  • Se estendi la classe View, traccia la forma della vista personalizzata e controllane l'aspetto sostituendo i metodi View come onDraw() e onMeasure() nella nuova classe.
  • Aggiungi codice per rispondere all'interazione dell'utente e, se necessario, ridisegna la visualizzazione personalizzata.
  • Utilizza la classe di visualizzazione personalizzata come widget dell'interfaccia utente nel layout XML delle tue attività. Puoi anche definire gli attributi personalizzati della vista per personalizzarla in layout diversi.

In questa attività dovrai:

  • Crea un'app con un ImageView come segnaposto temporaneo per la visualizzazione personalizzata.
  • Estendi View per creare la visualizzazione personalizzata.
  • Inizializza la visualizzazione personalizzata con 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 Attività vuota. 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 vista 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 risorse di 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 il tuo corso con 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 sulla lampadina rossa. Scegli Aggiungi costruttori di visualizzazione Android utilizzando '@JvmOverloads'. Android Studio aggiunge il costruttore della classe View. L'annotazione @JvmOverloads indica al compilatore Kotlin di generare sovraccarichi per questa funzione che sostituisce i valori parametro predefiniti.
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 tipo enum è di tipo Int perché i valori sono risorse stringa anziché stringhe effettive. Android Studio mostrerà errori per le risorse stringa mancanti in ciascuno di questi valori; dovrai risolvere il problema 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 le enum, aggiungi queste costanti. Le userai per disegnare indicatori e etichette.
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)
  • Il radius è il raggio attuale del cerchio. Questo valore viene impostato quando la visualizzazione viene disegnata sullo schermo.
  • fanSpeed è l'attuale velocità della ventola, che è uno dei valori dell'enumerazione di FanSpeed. Per impostazione predefinita, il valore è OFF.
  • Infine, postPosition è un punto X, Y che verrà utilizzato per disegnare diversi elementi della vista sullo schermo.

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

  1. Sempre 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 la 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 vista personalizzata, devi essere in grado di tracciarla. Quando estendi una sottoclasse View come EditText, la sottoclasse definisce l'aspetto e gli attributi della vista e si disegna sullo schermo. Di conseguenza, non devi scrivere il codice per disegnare la vista. Puoi invece sostituire i metodi del publisher principale per personalizzare la vista.

Se stai creando la tua visualizzazione da zero (estensione estendendo View), hai la responsabilità di disegnare l'intera visualizzazione ogni volta che si aggiorna lo schermo e di sostituire i metodi View che gestiscono il disegno. Per disegnare correttamente una vista personalizzata che includa View, devi:

  • Calcola le dimensioni della vista quando viene visualizzata per la prima volta e ogni volta che la dimensione di quest'ultima cambia, eseguendo l'override del metodo onSizeChanged().
  • Sostituisci il metodo onDraw() per disegnare la visualizzazione personalizzata utilizzando un oggetto Canvas definito da un oggetto Paint.
  • Richiama il metodo invalidate() quando rispondi a un clic dell'utente che cambia la modalità di visualizzazione della vista per invalidare l'intera vista, costringendo l'utente a chiamare nuovamente onDraw().

Il metodo onDraw() viene chiamato a ogni aggiornamento della schermata, che può essere ripetuto più volte al secondo. Per motivi legati alle prestazioni ed evitare problemi visivi, dovresti fare il meno possibile in onDraw(). In particolare, non assegnare allocazioni in onDraw(), perché le allocazioni possono causare una garbage collection che potrebbe causare interruzioni dell'immagine.

I corsi Canvas e Paint offrono una serie di scorciatoie utili per il disegno:

Scoprirai di più su Canvas e Paint in un codelab successivo. Per saperne di più su come Android attira visualizzazioni, consulta In che modo Android attira visualizzazioni.

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

Passaggio 1. Calcola le posizioni e traccia la vista

  1. Nella classe DialView, sotto le inizializzazioni, sostituisci il metodo onSizeChanged() della classe View per calcolare le dimensioni del tastierino della visualizzazione personalizzata. Importa kotlin.math.min quando richiesto.

    Il metodo onSizeChanged() viene chiamato ogni volta che la dimensione della vista cambia, inclusa la prima volta che viene disegnata quando il layout viene gonfiato. Esegui l'override di onSizeChanged() per calcolare le posizioni, le dimensioni e gli altri valori correlati alle dimensioni della vista personalizzata, anziché ricalcolarli ogni volta che disegni. In questo caso utilizzi onSizeChanged() per calcolare il raggio corrente dell'elemento circolare del tastierino.
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 nella classe PointF calcola le coordinate X, Y sullo schermo per l'etichetta di testo e l'indicatore corrente (0, 1, 2 o 3), data la posizione FanSpeed e il raggio correnti del tastierino. La utilizzerai tra 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. Sostituisci il metodo onDraw() per eseguire il rendering della visualizzazione sullo schermo con le classi Canvas e Paint. Importa android.graphics.Canvas quando richiesto. Ecco la bozza di uno scheletro:
override fun onDraw(canvas: Canvas) {
   super.onDraw(canvas)
   
}
  1. All'interno di onDraw(), aggiungi questa linea per impostare il colore della vernice su grigio (Color.GRAY) o verde (Color.GREEN), a seconda che la velocità del ventilatore sia OFF o qualsiasi 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 tastierino, utilizzando il metodo drawCircle(). Questo metodo utilizza la larghezza e l'altezza di visualizzazione correnti per trovare il centro del cerchio, il raggio del cerchio e il colore di vernice corrente. Le proprietà width e height sono membri della superclasse View e indicano le dimensioni correnti della vista.
// Draw the dial.
canvas.drawCircle((width / 2).toFloat(), (height / 2).toFloat(), radius, paint)
  1. Aggiungi questo codice per disegnare un cerchio più piccolo per il segno dell'indicatore della velocità del ventilatore, anche con il metodo drawCircle() Questa parte utilizza PointF.Metodo di estensione computeXYforSpeed() per calcolare le coordinate X e Y per il centro indicatore in base alla velocità corrente del ventilatore.
// 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, traccia le etichette della velocità del ventilatore (0, 1, 2, 3) nelle posizioni appropriate intorno al quadrante. Questa parte del metodo chiama nuovamente PointF.computeXYForSpeed() per ottenere la posizione per ogni etichetta e riutilizza ogni volta l'oggetto pointPosition per evitare allocazioni. Utilizza le 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, devi specificarla come elemento nel layout XML dell'attività. Controlla il suo aspetto e comportamento con gli attributi dell'elemento 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 ImageView originale ereditano gli attributi standard dalla classe View, quindi non è necessario modificare 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. Nell'attività viene visualizzata la visualizzazione di controllo della ventola.

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

Per rendere cliccabile la tua visualizzazione personalizzata:

  • Imposta la proprietà isClickable della vista su true. Ciò consente alla tua visualizzazione personalizzata di rispondere ai clic.
  • Implementa la performClick() della classe View per eseguire operazioni quando viene fatto clic sulla visualizzazione.
  • Chiama il metodo invalidate(). In questo modo indichi al sistema Android di chiamare il metodo onDraw() per ricreare la visualizzazione.

In genere, con una vista Android standard, implementi OnClickListener() per eseguire un'azione quando l'utente fa clic sulla vista. Per una visualizzazione personalizzata, devi implementare il metodo performClick() della classe View e chiamare super.performClick(). Il metodo performClick() predefinito chiama anche onClickListener(), quindi puoi aggiungere le azioni a performClick() e lasciare onClickListener() disponibile per un'ulteriore personalizzazione da parte tua o di altri sviluppatori che potrebbero utilizzare la tua visualizzazione personalizzata.

  1. In DialView.kt, all'interno dell'enumerazione di FanSpeed, aggiungi una funzione di estensione next() che cambi la velocità attuale della ventola alla velocità successiva nell'elenco (da OFF a LOW, MEDIUM e HIGH, quindi torna a OFF). L'enumerazione completa sarà 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 imposti la proprietà isClickable della vista su true, la vista accetta l'input dell'utente.
init {
   isClickable = true
}
  1. Sotto init(),, sostituisci il 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 al numero super.performClick() deve avvenire prima di tutto, il che comporta l'attivazione di eventi di accessibilità e di chiamate onClickListener().

Le prossime due righe aumentano la velocità della ventola con il metodo next() e imposta la descrizione dei contenuti della vista sulla risorsa stringa che rappresenta la velocità attuale (disattivata, 1, 2 o 3).

Fondamentalmente, il metodo invalidate() annulla l'intera visualizzazione, costringendo una chiamata a onDraw() a ripetere la visualizzazione. Se per qualsiasi motivo qualcosa nella visualizzazione personalizzata cambia, ad esempio 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 tastierino dovrebbe diventare verde. A ogni tocco, l'indicatore dovrebbe spostarsi nella posizione successiva. Quando l'indicatore torna a essere spento, il tastierino dovrebbe diventare di nuovo grigio.

Questo esempio mostra i meccanismi di base dell'utilizzo degli attributi personalizzati con la vista personalizzata. Definisci attributi personalizzati per la classe DialView con un colore diverso per ogni posizione del tastierino.

  1. Crea e apri res/values/attrs.xml.
  2. All'interno di <resources>, aggiungi un elemento di risorse <declare-styleable>.
  3. All'interno dell'elemento della risorsa <declare-styleable>, aggiungi tre elementi attr, uno per ogni attributo, con name e format. format è 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. Nel campo DialView, aggiungi gli attributi per fanColor1, fanColor2 e fanColor3 e imposta i rispettivi valori sui colori mostrati di seguito. Utilizza app: come prefazione per l'attributo personalizzato (come in app:fanColor1) anziché android:, perché gli attributi personalizzati appartengono allo spazio dei nomi di schemas.android.com/apk/res/your_app_package_name e non a quello di android.
app:fanColor1="#FFEB3B"
app:fanColor2="#CDDC39"
app:fanColor3="#009688"

Per utilizzare gli attributi del tuo corso DialView, devi recuperarli. Vengono memorizzate in un AttributeSet, che viene fornito al tuo corso al momento della creazione, se esistente. Recupera gli attributi in init e assegna i relativi valori alle variabili locali per la memorizzazione nella cache.

  1. Apri il file del corso DialView.kt.
  2. All'interno del tag 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. Sei tu a fornire gli attributi e la vista e a impostare le variabili locali. L'importazione di withStyledAttributes comporta anche l'importazione della 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. Usa le variabili locali in onDraw() per impostare il colore della composizione in base alla velocità attuale della ventola. Sostituisci la riga in cui è impostato il colore della vernice (paint.color = if (fanSpeed == FanSpeed.OFF) Color.GRAY else Color.GREEN) con il codice seguente.
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 tastierino e l'impostazione del colore deve essere diversa per ogni posizione, come mostrato di seguito.

Per ulteriori informazioni sugli attributi della vista personalizzata, consulta la sezione Creare un corso sulla vista.

L'accessibilità è un insieme di tecniche di progettazione, implementazione e test che consentono alla tua app di essere utilizzabile da tutti, incluse le persone con disabilità.

Le disabilità comuni che possono influire sull'uso di un dispositivo Android da parte di una persona includono non vedenti, ipovedenti, daltonismo, sordità o perdita dell'udito e capacità motorie limitate. Lo sviluppo delle app tenendo conto dell'accessibilità migliora l'esperienza utente non solo per gli utenti con queste disabilità, ma anche per tutti gli altri.

Android fornisce diverse funzioni di accessibilità per impostazione predefinita nelle viste UI standard come TextView e Button. Tuttavia, quando crei una visualizzazione personalizzata, devi considerare in che modo questa visualizzazione fornirà funzionalità accessibili come le descrizioni vocali dei contenuti sullo schermo.

In questa attività imparerai a conoscere TalkBack e lo screen reader di Android e modificherai l'app in modo da includere suggerimenti e descrizioni pronunciabili per la visualizzazione personalizzata di DialView.

Passaggio 1. Esplora TalkBack

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

In questa attività, attivi TalkBack per comprendere il funzionamento degli screen reader e come esplorare le app.

  1. Su un dispositivo Android o un emulatore, seleziona 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 tuo dispositivo. Se è la prima volta che utilizzi TalkBack, viene avviato un tutorial. Il tutorial potrebbe non essere disponibile sui dispositivi meno recenti.
  5. Potrebbe essere utile navigare nel tutorial con gli occhi chiusi. Per riaprire il tutorial in futuro, vai al passaggio Impostazioni > Accessibilità > TalkBack > Impostazioni > Avvia il tutorial su TalkBack.
  6. Compila ed esegui l'app CustomFanController oppure aprila con il pulsante Panoramica o Recenti sul dispositivo. Con TalkBack attivo, nota che viene annunciato il nome dell'app, così come il testo dell'etichetta TextView ("Gestione ventola"). Tuttavia, se tocchi la vista DialView stessa, non verranno dette informazioni sullo stato della vista (l'impostazione corrente per il tastierino) o sull'azione che verrà eseguita quando tocchi la vista per attivarla.

Passaggio 2. Aggiungi descrizioni dei contenuti per le etichette del componente

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 la funzione di ogni elemento in modo accurato. Per le visualizzazioni statiche, come ImageView, puoi aggiungere la descrizione dei contenuti alla vista nel file di layout con l'attributo contentDescription. Le visualizzazioni del testo (TextView e EditText) utilizzano automaticamente il testo visualizzato nella descrizione dei contenuti.

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

  1. In fondo alla classe DialView, dichiara una funzione updateContentDescription() senza argomenti o tipi di ritorno.
fun updateContentDescription() {
}
  1. All'interno di updateContentDescription(), modifica la proprietà contentDescription della vista personalizzata impostandola sulla risorsa stringa associata alla velocità del ventilatore attuale (off, 1, 2 o 3). Si tratta delle stesse etichette utilizzate in onDraw() quando viene tracciato il tastierino sullo schermo.
fun updateContentDescription() {
   contentDescription = resources.getString(fanSpeed.label)
}
  1. Scorri verso l'alto fino al blocco init() e, alla fine del blocco, aggiungi una chiamata a updateContentDescription(). Questo inizializza le descrizioni dei contenuti quando la visualizzazione viene inizializzata.
init {
   isClickable = true
   // ...

   updateContentDescription()
}
  1. Aggiungi un'altra chiamata al numero updateContentDescription() nel metodo performClick(), immediatamente prima del giorno 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 della visualizzazione composizione e nota che ora che TalkBack pronuncia l'etichetta corrente (off, 1, 2, 3) e la frase "Tocca due volte per attivarla".

Passaggio 3. Aggiungi ulteriori informazioni per l'azione clic

Potresti fermarti lì e la tua visualizzazione sarebbe utilizzabile in TalkBack. Sarebbe utile se la vista potesse indicare non solo che può essere attivata ("Tocca due volte per attivare&); ma anche spiegare che cosa accadrà quando la visualizzazione viene attivata ("Tocca due volte per cambiare." o "Tocca due volte per reimpostare."

A tale scopo, aggiungi informazioni sull'azione della vista (qui un'azione clic o tocco) a un oggetto info sul nodo di accessibilità tramite un delegato della accessibilità. Un delegato dell'accessibilità ti consente di personalizzare le funzionalità relative all'accessibilità dell'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. Nel blocco init di DialView.kt, imposta una delega per l'accessibilità sulla vista come nuovo oggetto AccessibilityDelegateCompat. Importa androidx.core.view.ViewCompat e androidx.core.view.AccessibilityDelegateCompat quando richiesto. Questa strategia garantisce il massimo livello di compatibilità con le versioni precedenti dell'app.
ViewCompat.setAccessibilityDelegate(this, object : AccessibilityDelegateCompat() {
   
})
  1. All'interno dell'oggetto AccessibilityDelegateCompat, sostituisci la funzione onInitializeAccessibilityNodeInfo() con un oggetto AccessibilityNodeInfoCompat e chiama il metodo super's. 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 vista ha una struttura ad albero dell'accessibilità, che può corrispondere o meno ai componenti del layout effettivi della vista. I servizi di accessibilità di Android naviga su tali nodi per trovare informazioni sulla vista (come le descrizioni dei contenuti pronunciabili o le possibili azioni che possono essere eseguite su tale visualizzazione). Quando crei una vista personalizzata, potresti anche dover eseguire l'override delle informazioni sul nodo per fornire informazioni personalizzate per l'accessibilità. In questo caso, sovrascriverai le informazioni del nodo per indicare che esistono informazioni personalizzate per l'azione della vista.

  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 vista ai fini dell'accessibilità. In genere, un'azione è un clic o un tocco, poiché viene utilizzato qui, mentre altre azioni possono includere l'acquisizione o la perdita della concentrazione, un'operazione negli appunti (taglio/copia/incolla) o lo scorrimento all'interno della visualizzazione. Il costruttore per questa classe richiede una costante di azione (qui 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, testa la velocità corrente del ventilatore. Se al momento la velocità è FanSpeed.HIGH, la stringa è "Reset". Se la velocità della ventola è diversa, la stringa è "Change." Puoi creare 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 le parentesi di chiusura per la definizione di customClick, utilizza il metodo addAction() per aggiungere la nuova azione di accessibilità all'oggetto informazioni sui nodi.
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 "Change" e "Reset".
<string name="change">Change</string>
<string name="reset">Reset</string>
  1. Compila ed esegui l'app e assicurati che TalkBack sia attivo. Nota ora che la frase "Tocca due volte per attivare" è ora "Tocca due volte per modificare"; (se la velocità della ventola è inferiore a 3) o "Tocca due volte per reimpostare" (se la velocità della ventola è già alta o 3). Tieni presente che la richiesta "Tocca due volte a..." viene fornita dal servizio TalkBack stesso.

Scarica il codice del codelab finito.

$  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 vista personalizzata che erediti l'aspetto e il comportamento di una sottoclasse View, ad esempio EditText, aggiungi una nuova classe che estenda la sottoclasse e apporta le modifiche eseguendo l'override di alcuni metodi della sottoclasse.
  • Per creare una visualizzazione personalizzata di qualsiasi dimensione e forma, aggiungi un nuovo corso che estende View.
  • Sostituisci i metodi View come onDraw() per definire la forma e l'aspetto di base della vista.
  • Utilizza invalidate() per forzare un disegno o una nuova visualizzazione della vista.
  • Per ottimizzare il rendimento, assegna le variabili e assegna gli eventuali valori necessari per il disegno e la pittura prima di utilizzarle in onDraw(), ad esempio nell'inizializzazione delle variabili dei membri.
  • Sostituisci performClick() anziché OnClickListener() alla visualizzazione personalizzata per fornire il comportamento interattivo della vista. Consente a te o ad altri sviluppatori Android che potrebbero utilizzare la tua classe di visualizzazione personalizzata di utilizzare onClickListener() per fornire ulteriori comportamenti.
  • Aggiungi la visualizzazione personalizzata a un file di layout XML con attributi per definire il suo 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 vista personalizzata nel file di layout XML.

Corso Udacity:

Documentazione per gli sviluppatori Android:

Video:

In questa sezione sono elencati i possibili compiti per gli studenti che lavorano attraverso questo codelab nell'ambito di un corso tenuto da un insegnante. Spetta all'insegnante fare quanto segue:

  • Assegna i compiti, se necessario.
  • Comunica agli studenti come inviare compiti.
  • Valuta i compiti.

Gli insegnanti possono utilizzare i suggerimenti solo quanto e come vogliono e dovrebbero assegnare i compiti che ritengono appropriati.

Se stai lavorando da solo a questo codelab, puoi utilizzare questi compiti per mettere alla prova le tue conoscenze.

Domanda 1

Per calcolare le posizioni, le dimensioni e tutti gli altri valori quando alla visualizzazione personalizzata viene assegnata per la prima volta una dimensione, quale metodo devi eseguire?

onMeasure()

onSizeChanged()

invalidate()

onDraw()

Domanda 2

Per indicare che vuoi ripetere la visualizzazione con onDraw(), quale metodo richiami dal thread dell'interfaccia utente dopo che un valore dell'attributo è cambiato?

▢ onMeasure()

▢ onSizeChanged()

▢ invalidate()

▢ getVisibility()

Domanda 3

Quale metodo View devi ignorare per aggiungere interattività alla visualizzazione personalizzata?

▢ setOnClickListener()

▢ onSizeChanged()

▢ isClickable()

▢ performClick()

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