Este codelab es parte del curso Aspectos avanzados de Android en Kotlin. Aprovecharás al máximo este curso si trabajas con los codelabs de forma secuencial, aunque no es obligatorio. Todos los codelabs del curso se indican en la página de destino de los codelabs de Aspectos avanzados de Android en Kotlin.
Introducción
Android ofrece un gran conjunto de subclases de View, como Button, TextView, EditText, ImageView, CheckBox o RadioButton. Puedes usar estas subclases para crear una IU que permita la interacción del usuario y muestre información en tu app. Si ninguna de las subclases de View satisface tus necesidades, puedes crear una subclase de View conocida como vista personalizada .
Para crear una vista personalizada, puedes extender una subclase View existente (como Button o EditText) o crear tu propia subclase de View. Si extiendes View directamente, puedes crear un elemento interactivo de la IU de cualquier tamaño y forma anulando el método onDraw() para que View lo dibuje.
Después de crear una vista personalizada, puedes agregarla a los diseños de tu actividad de la misma manera en que agregarías un TextView o un Button.
En esta lección, se muestra cómo crear una vista personalizada desde cero extendiendo View.
Conocimientos que ya deberías tener
- Cómo crear una app con una actividad y ejecutarla con Android Studio
Qué aprenderás
- Cómo extender
Viewpara crear una vista personalizada - Cómo dibujar una vista personalizada con forma circular
- Cómo usar objetos de escucha para controlar la interacción del usuario con la vista personalizada
- Cómo usar una vista personalizada en un diseño
Actividades
La app de CustomFanController muestra cómo crear una subclase de vista personalizada extendiendo la clase View. La nueva subclase se llama DialView.
La app muestra un elemento de IU circular que se asemeja a un control de ventilador físico, con ajustes para apagado (0), bajo (1), medio (2) y alto (3). Cuando el usuario presiona la vista, el indicador de selección se mueve a la siguiente posición: 0-1-2-3 y vuelve a 0. Además, si la selección es 1 o superior, el color de fondo de la parte circular de la vista cambia de gris a verde (lo que indica que el ventilador está encendido).


Las vistas son los componentes básicos de la IU de una app. La clase View proporciona muchas subclases, conocidas como widgets de IU, que satisfacen muchas de las necesidades de la interfaz de usuario de una app para Android típica.
Los componentes de IU, como Button y TextView, son subclases que extienden la clase View. Para ahorrar tiempo y esfuerzo de desarrollo, puedes extender una de estas subclases de View. La vista personalizada hereda el aspecto y el comportamiento de su elemento superior, y puedes anular el comportamiento o el aspecto de la apariencia que desees cambiar. Por ejemplo, si extiendes EditText para crear una vista personalizada, la vista actúa como una vista EditText, pero también se puede personalizar para mostrar, por ejemplo, un botón X que borre texto del campo de entrada de texto.
Puedes extender cualquier subclase de View, como EditText, para obtener una vista personalizada. Elige la que más se acerque a lo que quieres lograr. Luego, puedes usar la vista personalizada como cualquier otra subclase View en uno o más diseños como un elemento XML con atributos.
Para crear tu propia vista personalizada desde cero, extiende la clase View. Tu código anula los métodos View para definir la apariencia y la funcionalidad de la vista. La clave para crear tu propia vista personalizada es que eres responsable de dibujar todo el elemento de la IU de cualquier tamaño y forma en la pantalla. Si creas una subclase de una vista existente, como Button, esa clase se encarga del dibujo por ti. (Obtendrás más información sobre el dibujo más adelante en este codelab).
Para crear una vista personalizada, sigue estos pasos generales:
- Crea una clase de vista personalizada que extienda
Viewo una subclase deView(comoButtonoEditText). - Si extiendes una subclase
Viewexistente, anula solo el comportamiento o los aspectos de la apariencia que desees cambiar. - Si extiendes la clase
View, dibuja la forma de la vista personalizada y controla su apariencia anulando los métodosView, comoonDraw()yonMeasure(), en la nueva clase. - Agrega código para responder a la interacción del usuario y, si es necesario, vuelve a dibujar la vista personalizada.
- Usa la clase de vista personalizada como un widget de IU en el diseño XML de tu actividad. También puedes definir atributos personalizados para la vista y, así, proporcionar personalización para la vista en diferentes diseños.
En esta tarea, harás lo siguiente:
- Crea una app con un
ImageViewcomo marcador de posición temporal para la vista personalizada. - Extiende
Viewpara crear la vista personalizada. - Inicializa la vista personalizada con valores de dibujo y pintura.
Paso 1: Crea una app con un ImageView de marcador de posición
- Crea una app de Kotlin con el título
CustomFanControllerusando la plantilla Empty Activity. Asegúrate de que el nombre del paquete seacom.example.android.customfancontroller. - Abre
activity_main.xmlen la pestaña Text para editar el código XML. - Reemplaza el
TextViewexistente por este código. Este texto actúa como una etiqueta en la actividad para la vista personalizada.
<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"/>- Agrega este elemento
ImageViewal diseño. Este es un marcador de posición para la vista personalizada que crearás en este 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"/>- Extrae recursos de dimensión y cadena en ambos elementos de la IU.
- Haz clic en la pestaña Diseño. El diseño debería verse así:

Paso 2. Crea tu clase de vista personalizada
- Crea una nueva clase de Kotlin llamada
DialView. - Modifica la definición de la clase para extender
View. Importaandroid.view.Viewcuando se te solicite. - Haz clic en
Viewy, luego, en la bombilla roja. Elige Add Android View constructors using '@JvmOverloads'. Android Studio agrega el constructor de la claseView. La anotación@JvmOverloadsindica al compilador de Kotlin que genere sobrecargas para esta función que sustituyan los valores predeterminados de los parámetros.
class DialView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {- Arriba de la definición de la clase
DialView, justo debajo de las importaciones, agrega unenumde nivel superior para representar las velocidades del ventilador disponibles. Ten en cuenta que esteenumes de tipoIntporque los valores son recursos de cadena en lugar de cadenas reales. Android Studio mostrará errores para los recursos de cadena faltantes en cada uno de estos valores. Corregirás esto en un paso posterior.
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);
}- Debajo de
enum, agrega estas constantes. Los usarás como parte del dibujo de los indicadores y las etiquetas del dial.
private const val RADIUS_OFFSET_LABEL = 30
private const val RADIUS_OFFSET_INDICATOR = -35- Dentro de la clase
DialView, define varias variables que necesitas para dibujar la vista personalizada. Importaandroid.graphics.PointFsi se te solicita.
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)- El
radiuses el radio actual del círculo. Este valor se establece cuando la vista se dibuja en la pantalla. - El
fanSpeedes la velocidad actual del ventilador, que es uno de los valores de la enumeraciónFanSpeed. De forma predeterminada, ese valor esOFF. - Finalmente,
postPositiones un punto X,Y que se usará para dibujar varios de los elementos de la vista en la pantalla.
Estos valores se crean y se inicializan aquí en lugar de cuando se dibuja la vista, para garantizar que el paso de dibujo real se ejecute lo más rápido posible.
- También dentro de la definición de la clase
DialView, inicializa un objetoPaintcon algunos diseños básicos. Importaandroid.graphics.Paintyandroid.graphics.Typefacecuando se te solicite. Al igual que con las variables, estos estilos se inicializan aquí para acelerar el paso de dibujo.
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)
}- Abre
res/values/strings.xmly agrega los recursos de cadenas para las velocidades del ventilador:
<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 vez que creaste una vista personalizada, debes poder dibujarla. Cuando extiendes una subclase de View, como EditText, esa subclase define la apariencia y los atributos de la vista, y se dibuja en la pantalla. Por lo tanto, no tienes que escribir código para dibujar la vista. En su lugar, puedes anular los métodos del elemento superior para personalizar tu vista.
Si creas tu propia vista desde cero (extendiendo View), eres responsable de dibujar toda la vista cada vez que se actualiza la pantalla y de anular los métodos View que controlan el dibujo. Para diseñar correctamente una vista personalizada que extienda View, debes hacer lo siguiente:
- Anula el método
onSizeChanged()para calcular el tamaño de la vista cuando aparece por primera vez y cada vez que cambia. - Anula el método
onDraw()para dibujar la vista personalizada con un objetoCanvasdiseñado con un objetoPaint. - Llama al método
invalidate()cuando respondas a un clic del usuario que cambie la forma en que se dibuja la vista para invalidar toda la vista y, de ese modo, forzar una llamada aonDraw()para volver a dibujar la vista.
Se llama al método onDraw() cada vez que se actualiza la pantalla, lo que puede ocurrir muchas veces por segundo. Por motivos de rendimiento y para evitar fallas visuales, debes realizar la menor cantidad posible de trabajo en onDraw(). En particular, no coloques asignaciones en onDraw(), porque las asignaciones pueden conducir a una recolección de elementos no utilizados que puede causar una inestabilidad visual.
Las clases Canvas y Paint ofrecen varias combinaciones de teclas útiles para dibujar:
- Dibuja texto con
drawText(). Para especificar el tipo de letra, llama asetTypeface(). Para especificar el color del texto, llama asetColor(). - Dibuja formas básicas con
drawRect(),drawOval()ydrawArc(). Para usar formas rellenas, contorneadas o ambas, llama asetStyle(). - Dibuja mapas de bits con
drawBitmap().
Aprenderás más sobre Canvas y Paint en un codelab posterior. Para obtener más información sobre cómo Android dibuja vistas, consulta Cómo dibuja vistas Android.
En esta tarea, dibujarás la vista personalizada del controlador del ventilador en la pantalla (el dial, el indicador de posición actual y las etiquetas del indicador) con los métodos onSizeChanged() y onDraw(). También crearás un método asistente, computeXYForSpeed(),,para calcular la posición actual en X e Y de la etiqueta del indicador en el dial.
Paso 1: Calcular las posiciones y dibujar la vista
- En la clase
DialView, debajo de las inicializaciones, anula el métodoonSizeChanged()de la claseViewpara calcular el tamaño del dial de la vista personalizada. Importakotlin.math.mincuando se solicite.
Se llama al métodoonSizeChanged()cada vez que cambia el tamaño de la vista, incluida la primera vez que se dibuja cuando se infla el diseño. AnulaonSizeChanged()para calcular las posiciones, las dimensiones y cualquier otro valor relacionado con el tamaño de tu vista personalizada, en lugar de volver a calcularlos cada vez que diseñes. En este caso, usasonSizeChanged()para calcular el radio actual del elemento circular del dial.
override fun onSizeChanged(width: Int, height: Int, oldWidth: Int, oldHeight: Int) {
radius = (min(width, height) / 2.0 * 0.8).toFloat()
}- Debajo de
onSizeChanged(), agrega este código para definir una función de extensióncomputeXYForSpeed()para la clasePointF. Importakotlin.math.cosykotlin.math.sincuando se te solicite. Esta función de extensión en la clasePointFcalcula las coordenadas X e Y en la pantalla para la etiqueta de texto y el indicador actual (0, 1, 2 o 3), dada la posiciónFanSpeedactual y el radio del dial. Usarás esto enonDraw().
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
}- Anula el método
onDraw()para renderizar la vista en la pantalla con las clasesCanvasyPaint. Importaandroid.graphics.Canvascuando se solicite. Esta es la anulación de esqueleto:
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
}- Dentro de
onDraw(), agrega esta línea para establecer el color de pintura en gris (Color.GRAY) o verde (Color.GREEN) según si la velocidad del ventilador esOFFo cualquier otro valor. Importaandroid.graphics.Colorcuando se solicite.
// Set dial background color to green if selection not off.
paint.color = if (fanSpeed == FanSpeed.OFF) Color.GRAY else Color.GREEN- Agrega este código para dibujar un círculo para el dial con el método
drawCircle(). Este método usa el ancho y el alto de la vista actual para encontrar el centro y el radio del círculo, y el color de pintura actual. Las propiedadeswidthyheightson miembros de la superclaseViewy señalan las dimensiones actuales de la vista.
// Draw the dial.
canvas.drawCircle((width / 2).toFloat(), (height / 2).toFloat(), radius, paint)- Agrega el siguiente código para dibujar un círculo más pequeño para la marca del indicador de velocidad del ventilador, también con el método
drawCircle(). Esta parte usaPointF.Método de extensióncomputeXYforSpeed()para calcular las coordenadas X e Y del centro del indicador según la velocidad actual del ventilador.
// 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)- Por último, dibuja las etiquetas de velocidad del ventilador (0, 1, 2, 3) en las posiciones adecuadas alrededor del dial. Esta parte del método vuelve a llamar a
PointF.computeXYForSpeed()para obtener la posición de cada etiqueta y reutiliza el objetopointPositioncada vez para evitar asignaciones. UsadrawText()para dibujar las etiquetas.
// 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)
}El método onDraw() completado se ve de la siguiente manera:
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)
}
}Paso 2: Agrega la vista al diseño
Para agregar una vista personalizada a la IU de una app, debes especificarla como un elemento en el diseño XML de la actividad. Controla su apariencia y comportamiento con atributos de elementos XML, como lo harías con cualquier otro elemento de la IU.
- En
activity_main.xml, cambia la etiquetaImageViewdeldialViewacom.example.android.customfancontroller.DialViewy borra el atributoandroid:background. TantoDialViewcomo elImageVieworiginal heredan los atributos estándar de la claseView, por lo que no es necesario cambiar ninguno de los otros atributos. El nuevo elementoDialViewse ve de la siguiente manera:
<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" />- Ejecuta la app. La vista de control del ventilador aparecerá en la actividad.

La tarea final es habilitar tu vista personalizada para que realice una acción cuando el usuario la presione. Cada toque debe mover el indicador de selección a la siguiente posición: apagado-1-2-3 y volver a apagado. Además, si la selección es 1 o superior, cambia el fondo de gris a verde, lo que indica que el ventilador está encendido.
Para habilitar la opción de hacer clic en tu vista personalizada, haz lo siguiente:
- Establece la propiedad
isClickablede la vista entrue. Esto permite que tu vista personalizada responda a los clics. - Implementa el método
performClick()de la claseViewpara realizar operaciones cuando se haga clic en la vista. - Llama al método
invalidate(). Esto le indica al sistema Android que llame al métodoonDraw()para volver a dibujar la vista.
Normalmente, con una vista estándar de Android, implementas OnClickListener() para realizar una acción cuando el usuario hace clic en esa vista. En el caso de una vista personalizada, implementas el método performClick() de la clase View y llamas a super.performClick(). El método performClick() predeterminado también llama a onClickListener(), por lo que puedes agregar tus acciones a performClick() y dejar onClickListener() disponible para que tú o los demás desarrolladores que puedan usar tu vista personalizada la personalicen aún más.
- En
DialView.kt, dentro de la enumeraciónFanSpeed, agrega una función de extensiónnext()que cambie la velocidad actual del ventilador a la siguiente velocidad de la lista (deOFFaLOW,MEDIUMyHIGH, y, luego, de vuelta aOFF). La enumeración completa ahora se ve de la siguiente manera:
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
}
}- Dentro de la clase
DialView, justo antes del métodoonSizeChanged(), agrega un bloqueinit(). Si se establece la propiedadisClickablede la vista como verdadera, se habilita la vista para que acepte la entrada del usuario.
init {
isClickable = true
}- Debajo de
init(),, anula el métodoperformClick()con el siguiente código.
override fun performClick(): Boolean {
if (super.performClick()) return true
fanSpeed = fanSpeed.next()
contentDescription = resources.getString(fanSpeed.label)
invalidate()
return true
}Llamada a super.Primero debe ocurrir performClick(), lo que habilita los eventos de accesibilidad y llama a onClickListener().
Las siguientes dos líneas incrementan la velocidad del ventilador con el método next() y establecen la descripción del contenido de la vista en el recurso de cadena que representa la velocidad actual (apagado, 1, 2 o 3).
Por último, el método invalidate() invalida toda la vista, lo que fuerza una llamada a onDraw() para volver a dibujar la vista. Si algo en tu vista personalizada cambia por algún motivo, incluida la interacción del usuario, y el cambio debe mostrarse, llama a invalidate()..
- Ejecuta la app. Presiona el elemento
DialViewpara mover el indicador de apagado a 1. El dial debería ponerse de color verde. Con cada toque, el indicador debería moverse a la siguiente posición. Cuando el indicador vuelva a apagarse, el dial debería volver a ponerse de color gris.


En este ejemplo, se muestran los mecanismos básicos para usar atributos personalizados con tu vista personalizada. Defines atributos personalizados para la clase DialView con un color diferente para cada posición del dial del ventilador.
- Crea y abre
res/values/attrs.xml. - Dentro de
<resources>, agrega un elemento de recurso<declare-styleable>. - Dentro del elemento de recurso
<declare-styleable>, agrega tres elementosattr, uno para cada atributo, con unnamey unformat. Elformates como un tipo y, en este caso, escolor.
<?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>- Abre el archivo de diseño
activity_main.xml. - En
DialView, agrega atributos parafanColor1,fanColor2yfanColor3, y establece sus valores en los colores que se muestran a continuación. Usaapp:como prefacio del atributo personalizado (como enapp:fanColor1) en lugar deandroid:, ya que tus atributos personalizados pertenecen al espacio de nombresschemas.android.com/apk/res/your_app_package_nameen lugar del espacio de nombresandroid.
app:fanColor1="#FFEB3B"
app:fanColor2="#CDDC39"
app:fanColor3="#009688"Para usar los atributos en tu clase DialView, debes recuperarlos. Se almacenan en un AttributeSet, que se entrega a tu clase cuando se crea, si existe. Recuperas los atributos en init y asignas los valores de los atributos a variables locales para el almacenamiento en caché.
- Abre el archivo de la clase
DialView.kt. - Dentro de
DialView, declara variables para almacenar en caché los valores de los atributos.
private var fanSpeedLowColor = 0
private var fanSpeedMediumColor = 0
private var fanSeedMaxColor = 0- En el bloque
init, agrega el siguiente código con la función de extensiónwithStyledAttributes. Proporcionas los atributos y la vista, y configuras tus variables locales. ImportarwithStyledAttributestambién importará la funcióngetColor()correcta.
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 las variables locales en
onDraw()para establecer el color del dial según la velocidad actual del ventilador. Reemplaza la línea en la que se establece el color de pintura (paint.color=if(fanSpeed== FanSpeed.OFF) Color.GRAYelseColor.GREEN) por el siguiente código.
paint.color = when (fanSpeed) {
FanSpeed.OFF -> Color.GRAY
FanSpeed.LOW -> fanSpeedLowColor
FanSpeed.MEDIUM -> fanSpeedMediumColor
FanSpeed.HIGH -> fanSeedMaxColor
} as Int- Ejecuta la app, haz clic en el dial y el parámetro de configuración de color debería ser diferente para cada posición, como se muestra a continuación.
|
|
|
|
Para obtener más información sobre los atributos de vistas personalizados, consulta Cómo crear una clase de View.
La accesibilidad es un conjunto de técnicas de diseño, implementación y prueba que permiten que todas las personas, incluidas las que tienen discapacidades, puedan usar tu app.
Las discapacidades comunes que pueden afectar el uso de un dispositivo Android por parte de una persona incluyen ceguera, visión reducida, daltonismo, sordera o pérdida de audición, y habilidades motoras restringidas. Cuando desarrollas tus apps teniendo en cuenta la accesibilidad, mejoras la experiencia del usuario no solo para los usuarios con estas discapacidades, sino también para todos los demás.
Android proporciona varias funciones de accesibilidad de forma predeterminada en las vistas de IU estándar, como TextView y Button. Sin embargo, cuando creas una vista personalizada, debes tener en cuenta cómo proporcionará funciones de accesibilidad, como descripciones habladas del contenido en pantalla.
En esta tarea, aprenderás sobre TalkBack, el lector de pantalla de Android, y modificarás tu app para incluir sugerencias y descripciones que se puedan leer para la vista personalizada DialView.
Paso 1. Explora TalkBack
TalkBack es el lector de pantalla integrado de Android. Con TalkBack habilitado, el usuario puede interactuar con su dispositivo Android sin ver la pantalla, ya que Android describe los elementos de la pantalla en voz alta. Los usuarios con discapacidad visual podrían depender de TalkBack para usar tu app.
En esta tarea, habilitarás TalkBack para comprender cómo funcionan los lectores de pantalla y cómo navegar por las apps.
- En un dispositivo o emulador Android, navega a Configuración > Accesibilidad > TalkBack.
- Presiona el botón de activación Activar/desactivar para activar TalkBack.
- Presiona Aceptar para confirmar los permisos.
- Confirma la contraseña del dispositivo si se te solicita. Si es la primera vez que ejecutas TalkBack, se iniciará un instructivo. (Es posible que el tutorial no esté disponible en dispositivos más antiguos).
- Puede ser útil navegar por el instructivo con los ojos cerrados. Para volver a abrirlo en el futuro, ve a Configuración > Accesibilidad > TalkBack > Configuración > Iniciar instructivo de TalkBack.
- Compila y ejecuta la app de
CustomFanController, o bien ábrela con el botón Visión general o Recientes de tu dispositivo. Con TalkBack activado, observa que se anuncia el nombre de la app, así como el texto de la etiquetaTextView(“Fan Control”). Sin embargo, si presionas la vistaDialView, no se proporciona información sobre el estado de la vista (el parámetro de configuración actual del dial) ni sobre la acción que se realizará cuando presiones la vista para activarla.
Paso 2: Agrega descripciones de contenido para las etiquetas de los diales
Las descripciones de contenido explican el significado y el propósito de las vistas en tu app. Estas etiquetas permiten que los lectores de pantalla, como la función TalkBack de Android, expliquen la función de cada elemento con precisión. En el caso de las vistas estáticas, como ImageView, puedes agregar la descripción del contenido a la vista en el archivo de diseño con el atributo contentDescription. Las vistas de texto (TextView y EditText) usan automáticamente el texto de la vista como la descripción del contenido.
En el caso de la vista de control de ventilador personalizado, debes actualizar de forma dinámica la descripción del contenido cada vez que se haga clic en la vista para indicar el ajuste actual del ventilador.
- En la parte inferior de la clase
DialView, declara una funciónupdateContentDescription()sin argumentos ni tipo de datos que se muestra.
fun updateContentDescription() {
}- Dentro de
updateContentDescription(), cambia la propiedadcontentDescriptionde la vista personalizada al recurso de cadena asociado con la velocidad del ventilador actual (apagado, 1, 2 o 3). Estas son las mismas etiquetas que se usan enonDraw()cuando el dial se dibuja en la pantalla.
fun updateContentDescription() {
contentDescription = resources.getString(fanSpeed.label)
}- Desplázate hacia arriba hasta el bloque
init()y, al final de ese bloque, agrega una llamada aupdateContentDescription(). Esto inicializa la descripción del contenido cuando se inicializa la vista.
init {
isClickable = true
// ...
updateContentDescription()
}- Agrega otra llamada a
updateContentDescription()en el métodoperformClick(), justo antes deinvalidate().
override fun performClick(): Boolean {
if (super.performClick()) return true
fanSpeed = fanSpeed.next()
updateContentDescription()
invalidate()
return true
}- Compila y ejecuta la app, y asegúrate de que TalkBack esté activado. Presiona para cambiar el parámetro de configuración de la vista de dial y observa que ahora TalkBack anuncia la etiqueta actual (apagado, 1, 2, 3) y la frase "Presiona dos veces para activar".
Paso 3: Agrega más información para la acción de clic
Podrías detenerte ahí y tu vista sería utilizable en TalkBack. Sin embargo, sería útil que la vista indicara no solo que se puede activar ("Presiona dos veces para activar"), sino también que explicara qué sucederá cuando se active la vista ("Presiona dos veces para cambiar" o "Presiona dos veces para restablecer").
Para ello, agrega información sobre la acción de la vista (en este caso, una acción de clic o toque) a un objeto de información del nodo de accesibilidad, a través de un delegado de accesibilidad. Un delegado de accesibilidad te permite personalizar las funciones relacionadas con la accesibilidad de tu app a través de la composición (en lugar de la herencia).
Para esta tarea, usarás las clases de accesibilidad en las bibliotecas de Android Jetpack (androidx.*) para garantizar la retrocompatibilidad.
- En
DialView.kt, en el bloqueinit, establece un delegado de accesibilidad en la vista como un nuevo objetoAccessibilityDelegateCompat. Importaandroidx.core.view.ViewCompatyandroidx.core.view.AccessibilityDelegateCompatcuando se te solicite. Esta estrategia habilita la mayor cantidad de compatibilidad con versiones anteriores en tu app.
ViewCompat.setAccessibilityDelegate(this, object : AccessibilityDelegateCompat() {
})- Dentro del objeto
AccessibilityDelegateCompat, anula la funciónonInitializeAccessibilityNodeInfo()con un objetoAccessibilityNodeInfoCompaty llama al método de la superclase. Importaandroidx.core.view.accessibility.AccessibilityNodeInfoCompatcuando se te solicite.
ViewCompat.setAccessibilityDelegate(this, object : AccessibilityDelegateCompat() {
override fun onInitializeAccessibilityNodeInfo(host: View,
info: AccessibilityNodeInfoCompat) {
super.onInitializeAccessibilityNodeInfo(host, info)
}
})Cada vista tiene un árbol de nodos de accesibilidad, que puede corresponder o no a los componentes de diseño reales de la vista. Los servicios de accesibilidad de Android navegan por esos nodos para obtener información sobre la vista (como descripciones de contenido que se pueden leer o acciones posibles que se pueden realizar en esa vista). Cuando creas una vista personalizada, es posible que también debas anular la información del nodo para proporcionar información personalizada sobre la accesibilidad. En este caso, anularás la información del nodo para indicar que hay información personalizada para la acción de la vista.
- Dentro de
onInitializeAccessibilityNodeInfo(), crea un nuevo objetoAccessibilityNodeInfoCompat.AccessibilityActionCompaty asígnalo a la variablecustomClick. Pasa al constructor la constanteAccessibilityNodeInfo.ACTION_CLICKy una cadena de marcador de posición. ImportaAccessibilityNodeInfocuando se te solicite.
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 clase AccessibilityActionCompat representa una acción en una vista para fines de accesibilidad. Una acción típica es un clic o un toque, como se usa aquí, pero otras acciones pueden incluir obtener o perder el enfoque, una operación del portapapeles (cortar/copiar/pegar) o desplazarse dentro de la vista. El constructor de esta clase requiere una constante de acción (aquí, AccessibilityNodeInfo.ACTION_CLICK) y una cadena que TalkBack usa para indicar qué es la acción.
- Reemplaza la cadena
"placeholder"por una llamada acontext.getString()para recuperar un recurso de cadena. Para el recurso específico, prueba la velocidad actual del ventilador. Si la velocidad actual esFanSpeed.HIGH, la cadena es"Reset". Si la velocidad del ventilador es otra, la cadena es"Change.". Crearás estos recursos de cadena en un paso posterior.
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)
)
}
})- Después del paréntesis de cierre de la definición de
customClick, usa el métodoaddAction()para agregar la nueva acción de accesibilidad al objeto de información del 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)
}
})- En
res/values/strings.xml, agrega los recursos de cadenas para "Cambiar" y "Restablecer".
<string name="change">Change</string>
<string name="reset">Reset</string>- Compila y ejecuta la app, y asegúrate de que TalkBack esté activado. Observa que la frase "Doble toque para activar" ahora es "Doble toque para cambiar" (si la velocidad del ventilador es inferior a alta o 3) o "Doble toque para restablecer" (si la velocidad del ventilador ya está en alta o 3). Ten en cuenta que el mensaje "Presiona dos veces para…" lo proporciona el servicio de TalkBack.
Descarga el código del codelab terminado.
$ git clone https://github.com/googlecodelabs/android-kotlin-drawing-custom-views
También puedes descargar el repositorio como un archivo ZIP, descomprimirlo y abrirlo en Android Studio.
- Para crear una vista personalizada que herede el aspecto y el comportamiento de una subclase
View, comoEditText, agrega una nueva clase que extienda esa subclase y realiza ajustes anulando algunos de los métodos de la subclase. - Para crear una vista personalizada de cualquier tamaño y forma, agrega una clase nueva que extienda
View. - Anula los métodos
View, comoonDraw(), para definir la forma y la apariencia básica de la vista. - Usa
invalidate()para forzar un dibujo o un nuevo dibujo de la vista. - Para optimizar el rendimiento, asigna variables y asigna los valores necesarios para dibujar y pintar antes de usarlos en
onDraw(), como en la inicialización de variables miembro. - Anula
performClick()en lugar deOnClickListener() en la vista personalizada para proporcionar el comportamiento interactivo de la vista. Esto permite que tú o cualquier otro desarrollador de Android que use tu clase de vista personalizada utiliceonClickListener()para proporcionar un comportamiento adicional. - Agrega la vista personalizada a un archivo de diseño XML con atributos para definir su apariencia, como lo harías con otros elementos de la IU.
- Crea el archivo
attrs.xmlen la carpetavaluespara definir atributos personalizados. Luego, puedes usar los atributos personalizados para la vista personalizada en el archivo de diseño XML.
Curso de Udacity:
Documentación para desarrolladores de Android:
- Cómo crear vistas personalizadas
@JvmOverloads- Componentes personalizados
- Cómo dibuja vistas Android
onMeasure()onSizeChanged()onDraw()CanvasPaintdrawText()setTypeface()setColor()drawRect()drawOval()drawArc()drawBitmap()setStyle()invalidate()- Ver
- Eventos de entrada
- Pintar
- Biblioteca de extensiones de Kotlin android-ktx
withStyledAttributes- Documentación de Android KTX
- Blog del anuncio original de Android KTX
- Mejora la accesibilidad de las vistas personalizadas
AccessibilityDelegateCompatAccessibilityNodeInfoCompatAccessibilityNodeInfoCompat.AccessibilityActionCompat
Videos:
En esta sección, se enumeran las posibles actividades para el hogar para los alumnos que trabajan en este codelab como parte de un curso dirigido por un instructor. Depende del instructor hacer lo siguiente:
- Si es necesario, asigna una tarea.
- Comunicarles a los alumnos cómo enviar las actividades para el hogar.
- Califica las actividades para el hogar.
Los instructores pueden usar estas sugerencias en la medida que quieran y deben asignar cualquier otra actividad para el hogar que consideren apropiada.
Si estás trabajando en este codelab por tu cuenta, usa estas actividades para el hogar para probar tus conocimientos.
Pregunta 1
¿Qué método anulas para calcular las posiciones, las dimensiones y cualquier otro valor cuando se le asigna un tamaño a la vista personalizada por primera vez?
▢ onMeasure()
▢ onSizeChanged()
▢ invalidate()
▢ onDraw()
Pregunta 2
Para indicar que deseas que tu vista se vuelva a dibujar con onDraw(), ¿qué método llamas desde el subproceso de IU después de que cambió un valor de atributo?
▢ onMeasure()
▢ onSizeChanged()
▢ invalidate()
▢ getVisibility()
Pregunta 3
¿Qué método View deberías anular para agregar interactividad a tu vista personalizada?
▢ setOnClickListener()
▢ onSizeChanged()
▢ isClickable()
▢ performClick()
Para obtener vínculos a otros codelabs de este curso, consulta la página de destino de los codelabs de Aspectos avanzados de Android en Kotlin.



