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 sottoclasseView
(ad esempioButton
oEditText
). - 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 metodiView
comeonDraw()
eonMeasure()
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
- Crea un'app Kotlin con il titolo
CustomFanController
utilizzando il modello Attività vuota. Assicurati che il nome del pacchetto siacom.example.android.customfancontroller
. - Apri
activity_main.xml
nella scheda Testo per modificare il codice XML. - 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"/>
- 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"/>
- Estrai risorse di stringa e dimensione in entrambi gli elementi dell'interfaccia utente.
- Fai clic sulla scheda Design. Il layout dovrebbe avere il seguente aspetto:
Passaggio 2. Crea il tuo corso con visualizzazione personalizzata
- Crea una nuova classe Kotlin chiamata
DialView
. - Modifica la definizione della classe per estendere
View
. Importaandroid.view.View
quando richiesto. - Fai clic su
View
e poi sulla lampadina rossa. Scegli Aggiungi costruttori di visualizzazione Android utilizzando '@JvmOverloads'. Android Studio aggiunge il costruttore della classeView
. 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) {
- Sopra la definizione della classe
DialView
, appena sotto le importazioni, aggiungi unenum
di primo livello per rappresentare le velocità della ventola disponibili. Tieni presente che questo tipoenum
è di tipoInt
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);
}
- 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
- All'interno della classe
DialView
, definisci diverse variabili necessarie per disegnare la visualizzazione personalizzata. Importaandroid.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 diFanSpeed
. 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.
- Sempre all'interno della definizione della classe
DialView
, inizializza un oggettoPaint
con alcuni stili di base. Importaandroid.graphics.Paint
eandroid.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)
}
- 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 oggettoCanvas
definito da un oggettoPaint
. - 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 nuovamenteonDraw()
.
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:
- Disegnare testo con
drawText()
. Specifica il tipo di carattere chiamandosetTypeface()
e il colore del testo chiamandosetColor()
. - Disegna forme primitive con
drawRect()
,drawOval()
edrawArc()
. Modifica se le forme possono essere riempite, contornate o entrambe chiamatasetStyle()
. - Disegna le bitmap utilizzando
drawBitmap()
.
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
- Nella classe
DialView
, sotto le inizializzazioni, sostituisci il metodoonSizeChanged()
della classeView
per calcolare le dimensioni del tastierino della visualizzazione personalizzata. Importakotlin
.math.min
quando richiesto.
Il metodoonSizeChanged()
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 dionSizeChanged()
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 utilizzionSizeChanged()
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()
}
- Sotto
onSizeChanged()
, aggiungi questo codice per definire una funzione di estensionecomputeXYForSpeed()
per la classePointF
. Importakotlin.math.cos
ekotlin.math.sin
quando richiesto. Questa funzione di estensione nella classePointF
calcola le coordinate X, Y sullo schermo per l'etichetta di testo e l'indicatore corrente (0, 1, 2 o 3), data la posizioneFanSpeed
e il raggio correnti del tastierino. La utilizzerai traonDraw().
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
}
- Sostituisci il metodo
onDraw()
per eseguire il rendering della visualizzazione sullo schermo con le classiCanvas
ePaint
. Importaandroid.graphics.Canvas
quando richiesto. Ecco la bozza di uno scheletro:
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
}
- 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 siaOFF
o qualsiasi altro valore. Importaandroid.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
- 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
eheight
sono membri della superclasseView
e indicano le dimensioni correnti della vista.
// Draw the dial.
canvas.drawCircle((width / 2).toFloat(), (height / 2).toFloat(), radius, paint)
- 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 utilizzaPointF
.Metodo di estensionecomputeXYforSpeed()
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)
- 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'oggettopointPosition
per evitare allocazioni. Utilizza ledrawText()
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.
- In
activity_main.xml
, modifica il tagImageView
perdialView
incom.example.android.customfancontroller.DialView
ed elimina l'attributoandroid:background
. SiaDialView
siaImageView
originale ereditano gli attributi standard dalla classeView
, quindi non è necessario modificare altri attributi. Il nuovo elementoDialView
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" />
- 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 sutrue
. Ciò consente alla tua visualizzazione personalizzata di rispondere ai clic. - Implementa la
performClick
()
della classeView
per eseguire operazioni quando viene fatto clic sulla visualizzazione. - Chiama il metodo
invalidate()
. In questo modo indichi al sistema Android di chiamare il metodoonDraw()
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.
- In
DialView.kt
, all'interno dell'enumerazione diFanSpeed
, aggiungi una funzione di estensionenext()
che cambi la velocità attuale della ventola alla velocità successiva nell'elenco (daOFF
aLOW
,MEDIUM
eHIGH
, quindi torna aOFF
). 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
}
}
- All'interno della classe
DialView
, appena prima del metodoonSizeChanged()
, aggiungi un bloccoinit()
. Se imposti la proprietàisClickable
della vista su true, la vista accetta l'input dell'utente.
init {
isClickable = true
}
- Sotto
init(),
, sostituisci il metodoperformClick()
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().
.
- 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.
- Crea e apri
res/values/attrs.xml
. - All'interno di
<resources>
, aggiungi un elemento di risorse<declare-styleable>
. - All'interno dell'elemento della risorsa
<declare-styleable>
, aggiungi tre elementiattr
, uno per ogni attributo, conname
eformat
.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>
- Apri il file di layout
activity_main.xml
. - Nel campo
DialView
, aggiungi gli attributi perfanColor1
,fanColor2
efanColor3
e imposta i rispettivi valori sui colori mostrati di seguito. Utilizzaapp:
come prefazione per l'attributo personalizzato (come inapp:fanColor1
) anzichéandroid:
, perché gli attributi personalizzati appartengono allo spazio dei nomi dischemas.android.com/apk/res/
your_app_package_name
e non a quello diandroid
.
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.
- Apri il file del corso
DialView.kt
. - 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
- Nel blocco
init
, aggiungi il seguente codice utilizzando la funzione di estensionewithStyledAttributes
. Sei tu a fornire gli attributi e la vista e a impostare le variabili locali. L'importazione diwithStyledAttributes
comporta anche l'importazione della funzionegetColor()
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)
}
- 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
- 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.
- Su un dispositivo Android o un emulatore, seleziona Impostazioni > Accessibilità > TalkBack.
- Tocca il pulsante di attivazione/disattivazione On/Off per attivare TalkBack.
- Tocca OK per confermare le autorizzazioni.
- 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.
- 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.
- 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'etichettaTextView
("Gestione ventola"). Tuttavia, se tocchi la vistaDialView
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.
- In fondo alla classe
DialView
, dichiara una funzioneupdateContentDescription()
senza argomenti o tipi di ritorno.
fun updateContentDescription() {
}
- 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 inonDraw()
quando viene tracciato il tastierino sullo schermo.
fun updateContentDescription() {
contentDescription = resources.getString(fanSpeed.label)
}
- Scorri verso l'alto fino al blocco
init()
e, alla fine del blocco, aggiungi una chiamata aupdateContentDescription()
. Questo inizializza le descrizioni dei contenuti quando la visualizzazione viene inizializzata.
init {
isClickable = true
// ...
updateContentDescription()
}
- Aggiungi un'altra chiamata al numero
updateContentDescription()
nel metodoperformClick()
, immediatamente prima del giornoinvalidate()
.
override fun performClick(): Boolean {
if (super.performClick()) return true
fanSpeed = fanSpeed.next()
updateContentDescription()
invalidate()
return true
}
- 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.
- Nel blocco
init
diDialView.kt
, imposta una delega per l'accessibilità sulla vista come nuovo oggettoAccessibilityDelegateCompat
. Importaandroidx.core.view.ViewCompat
eandroidx.core.view.AccessibilityDelegateCompat
quando richiesto. Questa strategia garantisce il massimo livello di compatibilità con le versioni precedenti dell'app.
ViewCompat.setAccessibilityDelegate(this, object : AccessibilityDelegateCompat() {
})
- All'interno dell'oggetto
AccessibilityDelegateCompat
, sostituisci la funzioneonInitializeAccessibilityNodeInfo()
con un oggettoAccessibilityNodeInfoCompat
e chiama il metodo super's. Importaandroidx.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.
- All'interno di
onInitializeAccessibilityNodeInfo()
, crea un nuovo oggettoAccessibilityNodeInfoCompat.AccessibilityActionCompat
e assegnalo alla variabilecustomClick
. Passa al costruttore la costanteAccessibilityNodeInfo.ACTION_CLICK
e una stringa segnaposto. ImportaAccessibilityNodeInfo
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.
- Sostituisci la stringa
"placeholder"
con una chiamata acontext.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)
)
}
})
- Dopo le parentesi di chiusura per la definizione di
customClick
, utilizza il metodoaddAction()
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)
}
})
- In
res/values/strings.xml
, aggiungi le risorse stringa per "Change" e "Reset".
<string name="change">Change</string>
<string name="reset">Reset</string>
- 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.
- Per creare una vista personalizzata che erediti l'aspetto e il comportamento di una sottoclasse
View
, ad esempioEditText
, 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
comeonDraw()
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 utilizzareonClickListener()
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 cartellavalues
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:
- Creazione di viste personalizzate
@JvmOverloads
- Componenti personalizzati
- Come Android attira visualizzazioni
onMeasure()
onSizeChanged()
onDraw()
Canvas
Paint
drawText()
setTypeface()
setColor()
drawRect()
drawOval()
drawArc()
drawBitmap()
setStyle()
invalidate()
- View (Visualizzazione)
- Input eventi
- Pittura
- Raccolta dell'estensione Kotlin android-ktx
withStyledAttributes
- Documentazione relativa ad Android KTX
- Blog di annuncio originale di Android KTX
- Rendere più accessibili le visualizzazioni personalizzate
AccessibilityDelegateCompat
AccessibilityNodeInfoCompat
AccessibilityNodeInfoCompat.AccessibilityActionCompat
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.