Diseñar interfaces complejas en Android ya no tiene por qué ser un dolor de cabeza. Gracias a ConstraintLayout y a las nuevas herramientas de Android Studio, es posible montar pantallas muy ricas en contenido manteniendo una jerarquía de vistas plana, sin esas pirámides infinitas de LinearLayout y RelativeLayout anidados que penalizan el rendimiento y complican el mantenimiento.
En paralelo, el ecosistema ha evolucionado hacia Jetpack Compose, que propone otra manera de construir UIs, pero la realidad es que en muchos proyectos conviven ambas aproximaciones. Entender bien cómo sacarle todo el partido a ConstraintLayout (y a su editor visual), cómo funciona el sistema de restricciones, cadenas, márgenes, tamaños y hasta animaciones con ConstraintSet, es clave para construir interfaces complejas sin morir en el intento.
¿Qué es ConstraintLayout y por qué simplifica las interfaces complejas?
ConstraintLayout es un ViewGroup flexible que organiza y dimensiona vistas hijas utilizando un sistema de restricciones (constraints) en vez de anidamiento de layouts. Está disponible desde API 9 y fue diseñado para trabajar codo con codo con el editor visual de Android Studio.
Su filosofía es muy clara: con un solo contenedor puedes posicionar elementos en relación a otros, al padre o a guías invisibles, evitando anidar múltiples LinearLayout, RelativeLayout, FrameLayout, etc. De este modo:
- Reducimos la profundidad de la jerarquía de vistas, lo que mejora el rendimiento del renderizado y simplifica el debugging.
- Ganas en flexibilidad para adaptar la interfaz a diferentes tamaños y orientaciones de pantalla.
- Se integra perfectamente con el nuevo editor de layouts (modo Design y Blueprint) de Android Studio, permitiéndote trabajar casi todo con arrastrar y soltar.
Conceptualmente se parece a RelativeLayout, pero es bastante más avanzado: permite cadenas de vistas, proporciones, guías, barreras, sesgo de restricciones, animaciones de fotogramas clave, etc. Y todo ello accesible tanto por XML como desde el inspector de vistas del editor.
Cómo añadir ConstraintLayout a tu proyecto y crear nuevos layouts
Para empezar a usar este contenedor, primero debes incluir la librería de ConstraintLayout como dependencia en tu proyecto. Los pasos básicos son:
- Comprueba que en tu archivo settings.gradle tienes declarado el repositorio de Google (maven.google.com), que es desde donde se descarga la librería.
- En el build.gradle del módulo (normalmente app), añade la dependencia de ConstraintLayout con la última versión disponible. El patrón es algo como:
implementation "androidx.constraintlayout:constraintlayout:VERSIÓN" - Haz clic en Sync Project with Gradle Files para que Android Studio descargue y enlace la librería.
Con esto ya puedes usar el widget androidx.constraintlayout.widget.ConstraintLayout como raíz (o contenedor intermedio) de tus diseños.
Para crear un layout nuevo basado en ConstraintLayout con Android Studio, puedes:
- En la ventana Project, pulsa en la carpeta de tu módulo y selecciona File > New > XML > Layout XML.
- Introduce un nombre para el archivo (por ejemplo, activity_main.xml).
- En el campo Root Tag, escribe
androidx.constraintlayout.widget.ConstraintLayoutpara que se cree como contenedor raíz. - Finaliza con Finish y ya tendrás una plantilla con un ConstraintLayout listo para usar.
Si ya tienes diseños hechos con otros contenedores (como LinearLayout), puedes convertirlos a ConstraintLayout directamente:
- Abre el XML en el editor y ve a la pestaña Design.
- En el árbol de componentes, haz clic derecho sobre el layout raíz (por ejemplo, LinearLayout).
- Selecciona Convert LinearLayout to ConstraintLayout y Android Studio migrará la estructura conservando las vistas hijas.
El editor de layouts renovado: blueprint, propiedades y productividad
Con Android Studio se introdujo un editor de layouts completamente renovado pensado para sacarle partido a ConstraintLayout sin vivir pegado al XML. Dos ideas clave:
- El modo blueprint muestra una especie de “radiografía” del layout, con las relaciones entre vistas, restricciones, márgenes y alineaciones. Es especialmente útil para revisar interfaces complejas donde, a simple vista, es difícil entender cómo se sujeta todo.
- El panel de propiedades simplificado agrupa solo los atributos más comunes de cada vista (tamaño, texto, márgenes, constraints…), ocultando de inicio lo menos frecuente. Desde ahí puedes cambiar al modo avanzado si necesitas afinar más.
Además, el editor incorpora un inspector de vistas específico para ConstraintLayout. Este panel incluye:
- Control de constraints horizontales y verticales con sus tipos (Wrap content, Fixed, Match constraints).
- Deslizadores de sesgo (bias) horizontal y vertical para mover una vista entre dos restricciones opuestas.
- Botones para crear o borrar sujeciones (anchors) y cadenas sin tocar XML.
- Edición rápida de márgenes y tamaños.
Todo el sistema está pensado para que, si quieres, puedas montar pantallas enteras arrastrando componentes desde la Palette, ajustando restricciones visualmente y sin escribir ni una línea de XML, aunque siempre puedes refinar a mano el código cuando lo necesites.
Restricciones básicas: anclajes horizontales y verticales
La regla fundamental de ConstraintLayout es que toda vista debe tener al menos una restricción horizontal y otra vertical. Si no, el resultado en tiempo de ejecución no será el esperado.
Cuando arrastras una vista al editor, esta se queda donde la sueltas incluso sin constraints. Eso es solo una ayuda para que puedas diseñar con más comodidad; si ejecutas la app y la vista no tiene restricciones en un eje, se dibuja en la posición (esquina superior izquierda) o pegada a los límites superiores del layout padre.
Para trabajar con restricciones, cada vista muestra:
- Cuatro “puntos” circulares (handles) en cada lado, para crear constraints.
- Cuadrados en las esquinas para redimensionar la vista manualmente. Esto fija un tamaño exacto en el XML.
Crear una restricción es tan sencillo como:
- Arrastrar un handle hasta un posible punto de anclaje (borde de otra vista, borde del contenedor o una guía). El editor mostrará posibles conexiones y superposiciones en azul.
- O usar los botones de Create a connection en la sección Layout del panel Attributes.
Al establecer una restricción, por defecto el editor aplica un margen estándar entre las vistas. Ese margen puede ser el que tengas configurado en el botón Default Margin de la barra de herramientas (típicamente 8dp o 16dp) o el que edites después en Attributes.
Conviene tener en mente algunas reglas clave:
- Siempre necesitas al menos una constraint horizontal y otra vertical por vista, aunque lo normal es que acabes usando más.
- Solo puedes conectar planos compatibles: borde izquierdo/derecho con otro borde vertical, borde superior/inferior con otro borde horizontal, y líneas de base (baseline) solo con otras baselines.
- Cada handle solo admite una restricción, aunque varias vistas pueden compartir el mismo punto de anclaje.
Para borrar una restricción tienes varias opciones:
- Hacer clic directamente sobre la línea de la constraint y pulsar en Delete.
- Mantener Control (o Command en macOS) mientras haces clic sobre un ancla; se mostrará en rojo indicando que puedes eliminarlo.
- Desde el panel Attributes, pulsando sobre el icono del ancla correspondiente.
Si añades restricciones opuestas en una dimensión (por ejemplo, izquierda y derecha), verás que las líneas se dibujan como un resorte enrollado. Esto indica fuerzas opuestas; si el tamaño de la vista es Fixed o Wrap content, se centrará entre ambas restricciones por defecto. Para que se estire ocupando todo el espacio, debes usar el modo de tamaño Match constraints (0dp).
Sesgo de restricciones (bias): control fino de la posición
Cuando una vista está anclada por ambos lados en un eje (por ejemplo, izquierda y derecha), y su ancho/alto está en Fixed o Wrap content, el sistema la coloca en el centro justo, con un bias del 50%.
El sesgo permite desplazar la vista hacia un lado sin romper las restricciones. Puedes ajustarlo de dos formas:
- Moviendo el control deslizante de bias en el inspector de vistas para el eje correspondiente.
- Arrastrando directamente la vista en el lienzo; verás cómo varía el porcentaje.
Esto es muy práctico para interfaces donde quieres que un elemento no esté exactamente centrado, pero tampoco pegado a uno de los bordes, y no quieres jugar con márgenes asimétricos.
Modos de tamaño: Fixed, Wrap Content y Match Constraints
El inspector de vistas, en la parte superior del panel Attributes, muestra unos iconos para controlar cómo se calcula la anchura y la altura de cada vista. Estos modos son:
- Fixed: indicas una dimensión específica, ya sea escribiendo el valor (dp) o redimensionando manualmente la vista en el editor. Es útil para elementos muy concretos, pero abusar de tamaños fijos limita la capacidad de adaptación.
- Wrap content: la vista solo ocupa el espacio necesario para mostrar su contenido, ni más ni menos. Muy habitual en textos, iconos, etc.
- Match constraints (0dp): la vista se expande lo máximo posible para ajustarse a las restricciones de cada lado, teniendo en cuenta sus márgenes. Es la opción más flexible y la que deberías usar si quieres diseños realmente responsivos.
Es importante remarcar que en ConstraintLayout no debes usar match_parent para las dimensiones de las vistas hijas. En su lugar se usa match constraints (0dp) junto con las restricciones adecuadas.
Cuando solo tienes una restricción en un eje y el tamaño está en match constraints, la vista suele comportarse como wrap content, a menos que la configures de forma más avanzada (por ejemplo, en cadenas ponderadas).
Tamaños proporcionales (aspect ratio)
ConstraintLayout permite definir que una dimensión sea proporcional a la otra, algo muy útil para tarjetas, vídeo 16:9, avatares cuadrados, etc. Para ello:
- Asegúrate de que al menos una dimensión (ancho o alto) esté en Match constraints (0dp).
- Activa la opción Toggle Aspect Ratio Constraint en el inspector.
- Introduce la proporción en formato width:height, por ejemplo
16:9o1:1.
Si tanto ancho como alto son 0dp (match constraints) puedes elegir en qué dimensión se basa la proporción. El editor lo indica dibujando una línea continua en el lado dependiente. Por ejemplo, puedes decidir que el ancho sea una proporción de la altura; entonces el tamaño total dependerá de cómo calcules la altura (por restricciones al padre, a otras vistas, etc.).
Márgenes coherentes con Material Design
Para mantener interfaces consistentes, Android Studio ofrece un botón Margin en la barra de herramientas que define el margen por defecto de cada nueva restricción que crees. Todos los valores que propone son múltiplos de 8dp, siguiendo las recomendaciones de la cuadrícula de Material Design.
En cada constraint puedes afinar el margen de forma independiente: solo tienes que hacer clic sobre el número que aparece junto a la línea de restricción en el inspector y ajustar el valor o vincularlo a un recurso @dimen. Trabajar con recursos de dimensión te permite cambiar coherentemente márgenes en toda la app desde un único sitio.
Cadenas (chains): control avanzado de grupos lineales
Una de las armas más potentes de ConstraintLayout son las cadenas (chains), que son grupos de vistas enlazadas entre sí mediante restricciones bidireccionales.
Pueden ser horizontales o verticales, y permiten definir cómo se distribuyen el espacio y la posición de varias vistas en grupo. Los estilos principales son:
- Spread: las vistas se reparten uniformemente en el espacio disponible una vez descontados los márgenes. Es el estilo por defecto.
- Spread inside: la primera y la última vista se pegan a las restricciones de los extremos, mientras que las intermedias se distribuyen de forma uniforme.
- Ponderada (weighted): cuando la cadena es spread o spread inside, si una o varias vistas tienen ancho/alto en Match constraints (0dp), éstas rellenan el espacio restante en función de sus pesos
layout_constraintHorizontal_weightolayout_constraintVertical_weight. Funciona de forma similar alayout_weighten LinearLayout. - Packed: las vistas se agrupan en un bloque, respetando sus márgenes, y puedes desplazar el conjunto variando el sesgo de la vista de cabecera (izquierda/derecha o arriba/abajo).
La vista de cabecera de la cadena (primera a la izquierda en horizontales LTR, o la superior en verticales) es la que define el estilo en el XML mediante atributos como:
app:layout_constraintHorizontal_chainStyle="spread"app:layout_constraintHorizontal_chainStyle="packed"app:layout_constraintVertical_chainStyle="spread_inside"
Crear una cadena desde el editor es muy sencillo:
- Selecciona todas las vistas que quieres incluir.
- Haz clic derecho y ve a Chains.
- Elige Create Horizontal Chain o Create Vertical Chain según el caso.
Ten en cuenta varios detalles importantes:
- Una vista puede estar a la vez en una cadena horizontal y otra vertical, lo que te permite construir cuadrículas flexibles.
- Para que una cadena funcione bien, sus extremos deben estar restringidos a algo estable en el mismo eje (por ejemplo, al padre o a otras vistas fijas).
- Las cadenas no se encargan por sí mismas de la alineación perpendicular (por ejemplo, en una cadena horizontal, la alineación vertical de cada vista). Para ello debes añadir constraints adicionales (top, bottom, baseline, etc.).
Guías, barreras y línea de base
Además de las restricciones entre vistas, ConstraintLayout incorpora elementos auxiliares para organizar el diseño sin que sean visibles para el usuario:
- Guías (Guidelines): líneas invisibles, verticales u horizontales, a las que puedes anclar vistas. Pueden definirse como porcentaje del tamaño del padre o como posición fija. Son muy útiles para repetir alineaciones a lo largo de la pantalla.
- Barreras (Barriers): se crean a partir de un conjunto de vistas y representan un borde dinámico que se sitúa en función del tamaño de esas vistas. Es ideal cuando no sabes de antemano qué vista será más grande (por ejemplo, por textos variables) pero quieres que otra vista nunca las solape.
- Constraints de línea de base (baseline): permiten alinear el texto de dos TextView por su base, incluso aunque tengan tamaños de fuente distintos. Esto ofrece una apariencia mucho más pulida en diseños con múltiples etiquetas.
La creación de estos elementos se hace desde la paleta del editor de layouts, y luego se manejan igual que cualquier otro ancla de ConstraintLayout.
Creación automática de restricciones: Infer Constraints y Autoconnect
Cuando estás prototipando pantallas, puede ser un poco pesado ir añadiendo constraints a cada vista a mano. Para acelerar, el editor ofrece dos herramientas:
- Infer Constraints: analiza la posición actual de las vistas en el lienzo y genera automáticamente el conjunto de restricciones que mejor se adapta a esa disposición. Es ideal cuando primero colocas todo “a ojo” y luego quieres que el editor genere una base de constraints sobre la que tú afinas.
- Autoconnect to Parent: cuando está activado, cada vista que arrastras al diseño crea de forma automática dos o más restricciones respecto al padre, si el editor considera que tiene sentido. No crea constraints hacia otras vistas, solo hacia el layout superior.
Autoconnect viene deshabilitado por defecto, y puedes activarlo desde la barra de herramientas del editor. Aun así, conviene no confiarse demasiado: estas herramientas son un punto de partida, pero casi siempre tendrás que revisar y ajustar manualmente para que el diseño responda bien en todas las pantallas.
Inspector de layouts y herramientas de depuración visual
Para entender qué está pasando realmente en una pantalla compleja, Android Studio incluye un inspector de layouts avanzado. Desde el monitor de Android, puedes abrir el Layout Inspector y:
- Ver la jerarquía completa de vistas de una Activity en ejecución.
- Examinar atributos concretos (tamaños, márgenes, constraints, padding, etc.).
- Comprobar cómo se distribuye el espacio en tiempo real, lo que es muy útil con cadenas, biases y tamaños dinámicos.
Este inspector es especialmente potente para layouts basados en ConstraintLayout, ya que te permite ver claramente cómo se resuelven las restricciones, qué vistas se están expandiendo, qué cadenas están activas, etc. La única pega es que solo funciona con apps en modo debug; no puedes, por ejemplo, inspeccionar directamente la interfaz de otras aplicaciones instaladas.
Animaciones con ConstraintSet y fotogramas clave
Otro de los puntos fuertes de ConstraintLayout es que permite animar de forma sencilla cambios de posición y tamaño de las vistas usando la clase ConstraintSet junto con TransitionManager.
Un ConstraintSet es básicamente una representación en memoria de todas las restricciones, márgenes y padding de las vistas hijas de un ConstraintLayout. El flujo de trabajo típico para crear una animación es:
- Definir dos archivos de layout XML distintos (por ejemplo, keyframe_one.xml y keyframe_two.xml) que actúan como fotograma inicial y final. En el primero defines también colores y tamaños de texto finales, ya que ConstraintSet solo anima posición y tamaño.
- Usar uno de esos layouts como contenido actual de la Activity (por ejemplo,
setContentView(R.layout.keyframe_one)). - Cuando quieras lanzar la animación, crear un ConstraintSet, cargarle el segundo layout con
constraintSet.load(context, R.layout.keyframe_two)y aplicar la transición conTransitionManager.beginDelayedTransition()seguido deconstraintSet.applyTo(constraintLayout).
De esta forma, el sistema genera una animación interpolando las diferencias entre el estado actual y el definido en el segundo ConstraintSet. Ten en cuenta que solo se animan cambios de tamaño y posición; si quieres animar colores, opacidades u otras propiedades, tendrás que recurrir a otros mecanismos (animators, MotionLayout, etc.).
Si trabajas con Android Studio 3.6 o superior puedes apoyarte en View Binding para reemplazar las llamadas a findViewById() y ganar seguridad de tipos al manipular vistas en código. Esto encaja muy bien con el uso de ConstraintLayout y ConstraintSet.
Comparando Layouts clásicos: FrameLayout, LinearLayout y RelativeLayout
Aunque ConstraintLayout se lleva muchas miradas, sigue siendo importante entender los layouts clásicos y cuándo usarlos, porque te los sigues encontrando en infinidad de proyectos.
Un ViewGroup como FrameLayout, LinearLayout o RelativeLayout es un contenedor que organiza sus vistas hijas según ciertas reglas. Sobre ellos se apoyan los componentes básicos:
- View: la clase base de la que hereda todo lo visible. Es la opción más “en bruto”; si heredas directamente de View, tú te encargas de dibujar y manejar toda la interacción.
- TextView, EditText, Button, ImageView, etc.: son vistas especializadas que ya implementan la parte visual y la interacción para texto, campos de formulario, botones, imágenes, etc.
Entre los ViewGroup más típicos tenemos:
- FrameLayout: contenedor muy simple que apila vistas una encima de otra. La única forma de posicionarlas es con layout_gravity, que indica cómo se pega cada hijo al contenedor (bottom, center_horizontal, etc.). Ideal para overlays, fragment containers, pequeños paneles…
- LinearLayout: organiza las vistas en fila o columna según su orientación. Muy cómodo para formularios y listas sencillas donde cada elemento va uno debajo del otro (vertical) o uno al lado del otro (horizontal).
- RelativeLayout: te permite establecer relaciones entre vistas (a la izquierda de, debajo de, centrado en, etc.), pero acababa generando XML muy enrevesado con muchas dependencias cruzadas, motivo por el que ConstraintLayout lo ha ido sustituyendo.
La transición natural en proyectos existentes es sustituir combinaciones de LinearLayout + RelativeLayout por un único ConstraintLayout que reduzca la jerarquía y permita interfaces más ricas sin un coste excesivo de mantenimiento.
Primeros pasos con Jetpack Compose: Column, Row y Box
Mientras ConstraintLayout resuelve el problema de las interfaces complejas en vistas clásicas, Jetpack Compose propone un enfoque declarativo. Aun así, las ideas de organización del layout son muy similares: Column, Row y Box funcionan como los nuevos “layouts” básicos.
Algunos ejemplos muy sencillos:
- Column: distribuye elementos en vertical, como si fuera un LinearLayout vertical:
Column(Modifier.background(Color.White)) { Text("Nombre de la cosa") Text("Información y tal") } - Row: organiza elementos en horizontal; puedes controlar la disposición con horizontalArrangement y verticalAlignment. Es el equivalente a un LinearLayout horizontal pero mucho más flexible.
- Box: permite apilar elementos unos sobre otros (como FrameLayout) y alinearlos dentro del contenedor mediante
Modifier.align(Alignment...).
Con solo estos tres componentes, y combinando sus parámetros de alineación, puedes reproducir la mayoría de patrones típicos de layouts clásicos, y construir tarjetas, listas, cabeceras, overlays, etc.
El papel del Modifier en Compose y buenas prácticas
En Compose, la pieza clave para personalizar vistas es la clase Modifier. A través de ella defines posición, tamaño, fondo, comportamiento (clickable, scroll, listeners…), sombras, clipping y muchas cosas más.
Algunas ideas esenciales:
- Modifier funciona en cadena: el orden de las llamadas importa.
.background().padding()no es lo mismo que.padding().background(). - Sustituye conceptos como margin por padding. No existen márgenes como en el sistema clásico; el efecto se consigue con padding más organización de Row/Column.
- Es la forma estándar de inyectar comportamiento externo en un Composable: todas las nuevas composables deberían aceptar un parámetro
modifier: Modifier = Modifierpara que quien las use pueda ajustar su tamaño, padding, clic, etc., sin tocar su implementación interna.
Los modificadores se agrupan más o menos en:
- Posición y tamaño:
width(),height(),size(),fillMaxWidth(),fillMaxSize()… - Funcionalidad y comportamiento:
clickable(),verticalScroll(),horizontalScroll()… - Apariencia:
background(),padding(),offset(),shadow(),clip(),border()… - Listeners:
onFocusChanged(),onKeyEvent(),pointerInput()…
Un consejo práctico muy típico: si colocas background antes de padding, el color cubrirá solo el contenido interior; si lo pones después, pintas también la zona de padding. Esta clase de detalles marcan la diferencia en el resultado final.
Componentización, tamaños intrínsecos y tipografía en Compose
Compose anima a trocear la UI en pequeñas funciones reutilizables. Por ejemplo, puedes crear una tarjeta de perfil separando la imagen y el contenido textual en composables distintos (ImageProfile, ContentProfile, etc.) y una función principal que los agrupe.
Para controlar mejor los tamaños, especialmente en filas con contenido variable, puedes combinar fillMaxSize() en hijos con height(intrinsicSize = IntrinsicSize.Max) en el padre Row, de forma que tome como altura mínima la del hijo más alto, y luego distribuir el contenido con Arrangement.SpaceEvenly o similares.
Respecto al texto, tienes a tu disposición:
- Ajustar la escala no lineal de fuentes y propiedades directas: fontSize, fontStyle, fontWeight, textAlign, color…
- Objetos TextStyle reutilizables para agrupar estilos (tipografía, sombreado, familia, etc.).
- Medidas en sp para fuentes, igual que usas dp para tamaños de vista.
La recomendación es centralizar estilos de texto y colores en archivos comunes (por ejemplo, en la carpeta theme), igual que en vistas clásicas se centralizan en styles.xml y colors.xml, para evitar duplicidades y facilitar cambios globales.

Interactividad y gestión de clics en vistas clásicas
Volviendo al mundo “clásico”, una vez tienes la interfaz montada con ConstraintLayout, el siguiente paso es hacerla interactiva. Un patrón habitual consiste en agrupar en una lista todas las vistas clicables y asignarles el mismo listener.
Por ejemplo, puedes tener varios TextView y botones cuyos fondos cambian de color al pulsarlos. En tu Activity:
- Creas una función, por ejemplo pintarView(view: View), que decide el color según el id de la vista (usando una expresión
whenen Kotlin). - En clicView(), declaras una lista con todas las vistas interesadas (TextView, botones, el propio ConstraintLayout raíz, etc.).
- Recorres la lista y asignas a cada elemento un setOnClickListener que llama a
pintarView(it).
En la función de pintado puedes utilizar setBackgroundColor() con constantes de Color o setBackgroundResource() si quieres cambiar a un recurso de color o imagen definido en colors.xml o en drawables.
Este enfoque compacta la lógica y evita tener un listener distinto para cada widget, algo que se vuelve inmanejable cuando la pantalla crece en complejidad.
También es buena práctica identificar el ConstraintLayout raíz con un id, de forma que puedas cambiar el color de fondo general cuando el usuario pulse “fuera” de los elementos, dando feedback visual de que el fondo también reacciona.
Estilos, recursos y texto de ayuda para el usuario
Un diseño potente no solo depende de la disposición de las vistas, también de cómo gestionas estilos, tipografías y textos para que la interfaz sea coherente y fácil de entender.
Algunas prácticas recomendadas:
- Define estilos en values/styles.xml con atributos como
android:background,android:textSize,android:textColor,android:fontFamily… y aplícalos a múltiples TextView para evitar copiar y pegar propiedades. - Centraliza dimensiones comunes (márgenes, tamaños de fuente, etc.) en values/dimens.xml y utilízalas con
@dimen/...en lugar de valores mágicos. - Para fuentes personalizadas, usa res/font y establece
android:fontFamily="@font/roboto"u otras familias; esto mejora la consistencia visual. - Cuando aún no tengas todas las constraints definidas, pueden aparecer atributos con el espacio de nombres tools: (como
tools:layout_editor_absoluteY), que solo se aplican en tiempo de diseño y no afectan en ejecución. Son una ayuda visual mientras completas el layout.
Además, conviene añadir textos de ayuda dentro de la propia pantalla (por ejemplo, un “Cómo jugar” y una descripción) para guiar al usuario. Estos textos se gestionan como recursos @string/... y se colocan en la interfaz con TextView alineados mediante constraints y, si hace falta, alineación de baseline para que se vean perfectamente ajustados.
En conjunto, todo este ecosistema de ConstraintLayout, editor visual, estilos, recursos, modifiers en Compose y herramientas de inspección te permite construir interfaces de usuario en Android muy complejas con bastante control y poco sufrimiento. Comparte la información para que otros sepan de la novedad.

