Distrito Telefónica. Hub de Innovación y Talento

Volver
Development

Mejora la accesibilidad de Android con conmutables personalizados

Personalizar las vistas en nuestras aplicaciones es algo muy común en nuestro día a día de desarrollo de pantallas pero muchas veces nos olvidamos de revisar la parte de accesibilidad, por lo que acabamos creando componentes visuales atractivos pero poco accesibles para personas con discapacidad. 

En este post vamos a ver cómo podemos mejorar la accesibilidad de forma personalizada para nuestras aplicaciones de Android trabajando con toggleables Views para que puedas entender y aplicar cualquier personalización de accesibilidad según las necesidades de tu aplicación. 

Antes de empezar 
Las pruebas de accesibilidad de este artículo utilizan el servicio de accesibilidad más común en Android: TalkBack No obstante, el código que se implementa es estándar para cualquier otro servicio de accesibilidad que lo requiera. 
 
¡Empecemos! 
Supongamos que tenemos una aplicación que muestra una configuración tipo Switch pero con un diseño avanzado en el que tiene asociado un título, un subtítulo y una imagen. 


A continuación se muestra el código de este componente personalizado. Ten en cuenta que dentro del ConstraintLayout puedes añadir tantas personalizaciones como desees a tu componente conmutable. 
<androidx.constraintlayout.widget.ConstraintLayout
    android:id="@+id/custom_toggleable_root_layout"
    ... >
    <ImageView
        android:id="@+id/custom_toggleable_left_image"
        ... />
    <TextView
        android:id="@+id/custom_toggleable_title"
        ... />
    <TextView
        android:id="@+id/custom_toggleable_subtitle"
        ... />
    <androidx.appcompat.widget.SwitchCompat
        android:id="@+id/custom_toggleable_switch"
        ... />
</androidx.constraintlayout.widget.ConstraintLayout>

From here, let’s continue with this simple example to focus on what’s important: how accessible is this component? How can accessibility be improved?

Here’s a video of Talkback’s default behavior with the component:

Como se ve en el vídeo, el foco de TalkBack se mueve independientemente entre los elementos del componente. Sin embargo, si nos fijamos en otras aplicaciones que siguen las normas de accesibilidad WCAG como la aplicación Android Settings, vemos lo siguiente: 

En este caso, el foco se gestiona como un grupo para todo el componente. Esto nos ofrece dos ventajas principales: 
· Comunicación más eficaz con el usuario anunciando todos los elementos a la vez. 
· Fácil encendido y apagado del interruptor con un único enfoque de grupo. 

Manos a la obra 
Ahora que hemos visto cómo se puede mejorar la accesibilidad de los conmutables personalizados, vamos a entender los pasos para conseguirlo. 
Vamos a ver cómo configurar la accesibilidad del componente inicial para ofrecer la mejor experiencia a nuestros usuarios. 

Desactivar la configuración de accesibilidad actual 
En primer lugar, deshabilitamos la configuración de accesibilidad de nuestras vistas hijas estableciendo importantForAccessibility="no". Se gestionará como un grupo en el ConstraintLayout raíz. 
<ImageView
        android:id="@+id/custom_toggleable_image"
        android:importantForAccessibility="no"
        ... />
<TextView
        android:id="@+id/custom_toggleable_title"
        android:importantForAccessibility="no"
        ... />
<TextView
        android:id="@+id/custom_toggleable_subtitle"
        android:importantForAccessibility="no"
        ... />
<androidx.appcompat.widget.SwitchCompat
        android:id="@+id/custom_toggleable_switch"
        android:clickable="false"
        android:importantForAccessibility="no"
        ... />

Ten en cuenta que hacer clic también está desactivado para Switch, ya que no queremos que Talkback se centre en el Switch en absoluto. Más adelante veremos por qué es necesario. 
Personalizar la accesibilidad del diseño raíz 
Puesto que vamos a aplicar una configuración de accesibilidad personalizada, necesitamos crear una clase de componente Switch personalizada. 
class CustomSwitchComponent @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyle: Int = 0
) : ConstraintLayout(context, attrs, defStyle) {
    // Custom accessibility configuration will go here
}

Ahora podemos cambiar el ConstraintLayout genérico en el archivo XML por el personalizado que acabamos de crear. 
<com.your.package.CustomSwitchComponent
        android:id="@+id/custom_toggleable_root_layout"
        ... >
<!-- Child views -->
</com.your.package.CustomSwitchComponent>

Añadir una descripción del contenido 
Después de crear el componente personalizado, lo primero que vamos a hacer es crear una descripción del contenido ya que la accesibilidad de las vistas hijas se ha deshabilitado con importantForAccessibility="no". 
class CustomSwitchComponent(...) {
private lateinit var titleView: TextView
    private lateinit var subtitleView: TextView
    private lateinit var switchView: SwitchCompat
    override fun onFinishInflate() {
        super.onFinishInflate()
        titleView = findViewById(R.id.custom_toggleable_title)
        subtitleView = findViewById(R.id.custom_toggleable_subtitle)
        switchView = findViewById(R.id.custom_toggleable_switch)
        initAccessibilityConfiguration()
    }
    
    private fun initAccessibilityConfiguration() {
        contentDescription = "${titleView.text}. ${subtitleView.text}"
    }
}

Identificar el componente como Interruptor 
El siguiente paso es identificar nuestro componentepersonalizado como un Interruptor para que sea anunciado correctamente por Talkback. 
Para ello, necesitamos cambiar el atributo className en las clases AccessibilityEvent y AccessibilityNodeInfo cuando se inicializan. 
class CustomSwitchComponent(...) {
    ...

    override fun onInitializeAccessibilityEvent(event: AccessibilityEvent?) {
        super.onInitializeAccessibilityEvent(event)
        event?.className = android.widget.Switch::class.java.name
    }

    override fun onInitializeAccessibilityNodeInfo(info: AccessibilityNodeInfo?) {
        super.onInitializeAccessibilityNodeInfo(info)
        info?.className = android.widget.Switch::class.java.name
    }
}
Proporcionar el estado del Interruptor al componente 
Para proporcionar el estado del Interruptor a nuestro componente, existe la función setStateDescription que podemos utilizar de la siguiente manera: 
private fun initAccessibilityConfiguration() {
    ...
    ViewCompat.setStateDescription(this@CustomSwitchComponent, ViewCompat.getStateDescription(switchView))
}
El código anterior está bien, y podemos comprobar que Talkback efectivamente anuncia el estado del Interruptor en nuestro componente. 
Pero esto no acaba aquí... ¡El estado no se actualizará automáticamente en nuestro componente cuando cambiemos el estado del Interruptor!

Necesitamos que el componente gestione la actualización del estado del Interruptor de forma controlada. 

Esta es la razón por la que desactivamos el clic en el Interruptor anteriormente, ya que queremos que todos los cambios de estado en el Interruptor pasen a través de nuestro componente para que podamos actualizar el estado correctamente. 

Una forma sencilla de conseguirlo es creando funciones de utilidad pública para que el estado del Interruptor pueda cambiarse al mismo tiempo que lo actualizamos en nuestro componente. 

fun changeSwitchState(enable: Boolean) {
    switchView.isChecked = enable
    ViewCompat.setStateDescription(this@CustomSwitchComponent, ViewCompat.getStateDescription(switchView))
}

fun getSwitchState() = switchView.isChecked
Con estos métodos, podemos cambiar el estado del Interruptor a través de nuestro componente personalizado. 

val component: CustomSwitchComponent = findViewById(R.id.custom_toggleable_root_layout)
component.setOnClickListener {
    component.changeSwitchState(!component.getSwitchState())
}

Ahora dispone de un componente conmutable personalizado con una gran configuración de accesibilidad. Tiene buena pinta, ¿eh? 

Pero eso no es suficiente... vamos a mejorar aún más la accesibilidad en nuestro componente personalizado Switch. 
Mejorar el anuncio de clic de los componentes 
Si probamos el componente con Talkback veremos que anuncia "Doble toque para activar ". 

Esto no es del todo erróneo, pero si probamos Talkback con un Switch aislado, veremos que anuncia "Double-tap to conmutar", lo que da más contexto al usuario que simplemente "activar". 

Dado que Android no permite obtener este valor, no hay forma de obtener el valor ACTION_CLICK del componente Switch directamente, a diferencia de lo que ocurre con el valor stateDescription . 

Por lo tanto, para cambiar esta parte del anuncio lo hacemos de la siguiente manera: 
override fun onInitializeAccessibilityNodeInfo(info: AccessibilityNodeInfo?) {
    ...
    info?.addAction(
        AccessibilityNodeInfo.AccessibilityAction(
            AccessibilityNodeInfoCompat.ACTION_CLICK,
            "toggle" // Remember to use a stringRes reference instead of hardcoded text.
        )
    )
}

Con este código podemos especificar aún más el anuncio del clic en función del estado del interruptor. 
info?.addAction(
    AccessibilityNodeInfo.AccessibilityAction(
        AccessibilityNodeInfoCompat.ACTION_CLICK,
        if (switchView.isChecked) "turn off" else "turn on"
    )
)
Así que ahora, Talkback anunciará uno u otro texto en función del estado del interruptor: 
· El interruptor está en ON → "Doble toque para apagar" 
· El interruptor está apagado → "Doble toque para encender" 

Es una envoltura 
¡Enhorabuena! Has creado un componente Switch personalizado con una excelente configuración de accesibilidad que hará que tu aplicación sea más accesible para los usuarios. 
Veamos cómo queda nuestro componente con todas estas mejoras de accesibilidad. 
Puedes consultar el código completo de este blog en nuestro repositorio de fuente abierta: Catálogo de accesibilidad. 

Además, este componente se ha añadido a Mistica Android, la biblioteca de componentes de fuente abierta que tenemos en Telefónica. Puede obtener más información sobre la implementación en Mistica en la sección Listas. 

Cualquier sugerencia o mejora será bienvenida. Tus valiosas aportaciones ayudan a crear aplicaciones más accesibles para personas de todo el mundo. 

¡Gracias por leernos!