Por qué la accesibilidad en Kotlin es un superpoder, no una casilla de verificación
\n\nImagina: escribiste una aplicación genial en Kotlin. Hermoso Material Design, animaciones fluidas, streams reactivos. Estás orgulloso del código. Pero luego resulta que una persona con baja visión no puede presionar el botón «Comprar», porque TalkBack lo lee como «botón sin etiquetar». O un usuario con temblor en las manos falla al tocar un pequeño interruptor.
\n\nLa accesibilidad no es una burocracia aburrida. Se trata de que tu código en Kotlin funcione para todos. Según la OMS, más de mil millones de personas tienen alguna discapacidad. Ignorarlos significa perder audiencia y, francamente, ser un mal desarrollador.
\n\nEn este artículo, veremos cómo escribir código accesible en Kotlin sin dolor. Sin toneladas de código repetitivo. Con ejemplos vivos que puedes copiar y pegar ahora mismo.
\n\nPrincipios básicos: ContentDescription es tu mejor amigo
\n\nEl fallo más común en accesibilidad es la falta de descripción para los elementos de la interfaz. El sistema TalkBack (lector de pantalla en Android) no sabe leer imágenes o iconos. Necesita texto.
\n\nEn Kotlin, esto se hace mediante el atributo contentDescription. Pero no solo «Botón», sino con sentido.
Mal ejemplo (no hagas esto)
\nimageView.contentDescription = "Botón"\n\nBuen ejemplo
\nbinding.fabAddTask.contentDescription = "Agregar nueva tarea"\n\nSi el icono es decorativo (solo adorno) — establece contentDescription = null para que TalkBack lo ignore.
// Icono decorativo — no distraemos al usuario\nbinding.decorativeIcon.contentDescription = null\n\nConsejo: Usa recursos de cadena (strings.xml) para soportar localización. No hardcodees texto en español en los archivos Kotlin.
Marcado semántico: le decimos al sistema qué es importante
\n\nLas View estándar (Button, TextView) funcionan bastante bien, pero los componentes personalizados a menudo rompen la accesibilidad. En Kotlin podemos definir explícitamente la semántica.
Añadimos un rol al elemento
\nbinding.customView.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_YES\nbinding.customView.accessibilityDelegate = object : View.AccessibilityDelegate() {\n override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfo) {\n super.onInitializeAccessibilityNodeInfo(host, info)\n info.text = "Valor actual: ${viewModel.value}"\n info.contentDescription = "Control deslizante de volumen"\n }\n}\n\nEste código le dice al sistema: «Oye, esto no es solo un cuadrado, ¡es un control deslizante!». TalkBack entenderá que el elemento es interactivo y podrá interactuar correctamente.
\n\nAgrupación de elementos
\nSi tienes una tarjeta de producto con imagen, precio y nombre — combínalos en un solo foco. De lo contrario, el usuario escuchará tres elementos por separado.
\n\nbinding.cardProduct.apply {\n isFocusable = true\n contentDescription = "Producto: ${product.name}, precio: ${product.price} pesos"\n}\n\nAhora TalkBack leerá toda la información de un solo clic.
\n\nNavegación por acción: teclado y D-pad
\n\nNo todos los usuarios tocan la pantalla con el dedo. Algunos usan teclado (mediante OTG) o D-pad (control remoto). Tu aplicación debe poder manejarse sin contacto táctil.
\n\nEn Kotlin, esto se resuelve con focusable y el orden de foco (nextFocusDown, nextFocusForward).
binding.buttonNext.nextFocusForwardId = R.id.buttonConfirm\nbinding.buttonConfirm.nextFocusDownId = R.id.buttonCancel\n\nVerifica que todos los elementos interactivos reciban foco. Un error común es un TextView con ClickableSpan (enlace en el texto). Por defecto no recibe foco. Lo corregimos:
binding.textWithLink.movementMethod = LinkMovement