El sistema de recursos: la carpeta res y sus calificadores
Por qué Android externaliza textos, colores y layouts fuera del código: la estructura de res, los calificadores de idioma, densidad, tamaño y modo oscuro, y el algoritmo exacto con el que el sistema elige el recurso correcto en tiempo de ejecución.
Android no te deja escribir un texto dentro del código y quedarte tranquilo. Los textos, los colores, las dimensiones, los iconos y hasta los layouts viven fuera, en la carpeta res/, identificados por nombre y agrupados en directorios con calificadores. Parece burocracia hasta que entiendes el problema que resuelve: la misma app corre en un teléfono barato en portugués con pantalla pequeña y en un plegable en árabe con modo oscuro y densidad altísima. Externalizar los recursos convierte esa explosión combinatoria en algo que el sistema resuelve por ti, en el instante exacto en que hace falta.
- Comprender la externalización de recursos como mecanismo de adaptación, no como convención de estilo.
- Conocer los tipos de recurso y la estructura de directorios de
res/. - Dominar los calificadores de idioma, densidad, tamaño, orientación y tema.
- Reproducir mentalmente el algoritmo con el que Android elige el recurso más adecuado.
Por qué los recursos viven fuera del código
Un recurso es un valor con nombre que el sistema puede sustituir según el contexto del dispositivo. Escribir el texto de un botón dentro de una función lo fija para siempre; declararlo como recurso lo convierte en una referencia que el sistema resuelve al vuelo, eligiendo la variante que corresponde al idioma, al tamaño de pantalla o al tema activo. Es inversión de control aplicada a los datos de presentación: tú declaras alternativas, el sistema decide.
La consecuencia arquitectónica es enorme. Traducir una app a doce idiomas no toca una sola línea de Kotlin. Soportar modo oscuro no exige condicionales. Servir un icono nítido en seis densidades distintas es cuestión de carpetas. Y cuando el usuario cambia el idioma del dispositivo con la app abierta, Android recrea la actividad y todos los recursos se resuelven de nuevo: la adaptación no es un evento que gestionas, es una propiedad del sistema.
La estructura básica agrupa por tipo. Cada tipo tiene sus reglas y su forma de compilarse.
res/
drawable/ imagenes vectoriales y mapas de bits
mipmap/ iconos de lanzador, con su propia regla de densidad
values/ strings, colores, dimensiones, estilos y temas
layout/ layouts XML de la era anterior a Compose
font/ familias tipograficas
raw/ archivos que se copian tal cual, con identificador
xml/ configuraciones arbitrarias en XML
menu/ definiciones de menu
Dentro de values/, los archivos no dan nombre a nada: puedes llamarlos como quieras porque lo que cuenta es el elemento raíz y el nombre de cada entrada. En el resto de carpetas ocurre lo contrario: el nombre del archivo, sin extensión, es el identificador del recurso, y por eso debe estar en minúsculas y sin guiones altos.
<resources>
<string name="saludo">Hola</string>
<string name="elementos">%1$d elementos</string>
<color name="primario">#FF6750A4</color>
<dimen name="margen_lateral">16dp</dimen>
</resources>
La distinción parece cosmética y no lo es. Los recursos de drawable/ de densidades que el dispositivo no usa pueden eliminarse al generar el paquete final para ese dispositivo; los de mipmap/ se conservan siempre. El motivo es que el lanzador a veces pide el icono de la app en una densidad superior a la de la pantalla, para dibujarlo ampliado en animaciones o en pantallas de ajustes. Si el icono viviera en drawable/, esa densidad extra podría haberse podado y el lanzador mostraría una imagen borrosa. Regla práctica: iconos de lanzador en mipmap/, absolutamente todo lo demás en drawable/.
Calificadores: declarar variantes sin condicionales
Un calificador es un sufijo en el nombre del directorio que restringe cuándo aplica su contenido. La carpeta sin sufijo es la variante por defecto, y cada carpeta con sufijo es una alternativa condicionada. El recurso se llama igual en todas: cambia la carpeta, nunca el identificador.
res/values/strings.xml por defecto
res/values-es/strings.xml espanol
res/values-es-rMX/strings.xml espanol de Mexico
res/values-night/colors.xml tema oscuro
res/values-sw600dp/dimens.xml ancho minimo de 600dp
res/drawable-hdpi/logo.png densidad alta
res/drawable-xxhdpi/logo.png densidad muy alta
res/layout-land/pantalla.xml orientacion apaisada
Se pueden encadenar varios en un mismo directorio, pero no en cualquier orden: la plataforma exige que aparezcan en el orden de precedencia oficial, separados por guiones. Un directorio para español en tablet y modo oscuro se escribe con el idioma primero, la anchura mínima después y el modo al final. Si te equivocas de orden, la herramienta de compilación no adivina: falla o ignora el directorio, y el recurso nunca se aplica.
Los calificadores más usados cubren cuatro ejes. El idioma y la región usan códigos estándar, con el prefijo de región marcado por una letra que evita ambigüedades. La densidad clasifica las pantallas en escalones —desde la baja hasta la extra extra extra alta—, y existe además un calificador que marca un recurso como independiente de la densidad, útil para vectores. El tamaño moderno se expresa con la anchura mínima disponible en unidades independientes de densidad, que es estable frente a rotaciones. Y el modo nocturno permite tener dos paletas completas sin una sola condición en el código.
Hay un orden de precedencia fijado por la plataforma: idioma y región pesan más que tamaño, el tamaño más que la orientación, y la densidad se aplica casi al final porque el sistema siempre puede escalar una imagen, aunque le cueste calidad. Ese orden no es negociable y explica por qué un recurso en español gana a uno de tablet cuando el dispositivo es una tablet en español.
Cómo elige Android el recurso correcto
El algoritmo es determinista y merece la pena memorizarlo, porque explica casi todos los desconciertos con recursos. Ante una petición, el sistema toma todos los directorios candidatos para ese tipo de recurso y los va filtrando calificador a calificador, siguiendo el orden de precedencia.
flowchart TD
A[Se pide un recurso por identificador] --> B[Descartar carpetas que contradicen la configuracion]
B --> C[Tomar el calificador de mayor precedencia presente]
C --> D{Alguna carpeta lo usa}
D -->|Si| E[Descartar las carpetas que no lo llevan]
D -->|No| F[Pasar al siguiente calificador]
E --> G{Queda mas de una carpeta}
F --> G
G -->|Si| C
G -->|No| H[Esa carpeta aporta el recurso final]Vale la pena recorrerlo con un caso concreto. Supón un dispositivo configurado en español de México, con pantalla de densidad muy alta, tema oscuro y una ventana de 700 unidades de ancho, que pide un color declarado en varias carpetas.
Candidatos iniciales
values/ por defecto
values-es/ coincide con el idioma
values-fr/ contradice el idioma
values-night/ coincide con el tema
values-es-night/ coincide con ambos
values-sw600dp/ coincide con el tamano
Paso 1 descartar contradicciones cae values-fr
Paso 2 idioma, maxima precedencia sobreviven values-es y values-es-night
Paso 3 tamano, ninguna lo declara no filtra
Paso 4 modo nocturno gana values-es-night
Observa que values-sw600dp cayó en el paso del idioma pese a describir correctamente el tamaño de la ventana. No es un fallo: la precedencia dice que hablar el idioma del usuario importa más que ajustarse a su pantalla, y una vez eliminada, esa carpeta ya no vuelve aunque en pasos posteriores fuese la mejor candidata. Quien no conoce esta poda temprana concluye que Android ignora sus recursos de tablet, cuando lo que ocurre es que los descartó tres pasos antes.
Dos consecuencias prácticas se derivan de este recorrido. La primera es que una carpeta cuyo calificador contradice el dispositivo queda eliminada de entrada y ya no vuelve: un recurso en values-fr/ jamás se usará en un dispositivo en italiano, aunque sea el único que defina esa clave. La segunda es que la carpeta por defecto debe estar completa. Si un texto solo existe en una variante calificada y el dispositivo no encaja con ninguna, la app falla al buscarlo. Por eso la disciplina correcta es: define todo en la carpeta por defecto y usa las variantes únicamente para sobrescribir.
Cualquier cambio en la configuración del dispositivo —idioma, orientación, densidad, tema claro u oscuro, tamaño de la ventana en modo multiventana— invalida los recursos ya resueltos. La respuesta por defecto de Android es destruir la actividad y volverla a crear para que todo se resuelva de nuevo contra la configuración nueva. No es un fallo ni un exceso: es la única forma de garantizar coherencia cuando la resolución depende del contexto. De ahí que el estado de tu app tenga que sobrevivir a la recreación por diseño, y de ahí que Compose, que lee los recursos en la composición, reaccione a estos cambios con naturalidad.
Densidad, tamaño y la ventana real
La densidad es el eje que más confusión genera porque mezcla dos magnitudes: los píxeles físicos y las unidades independientes de densidad. Una unidad independiente equivale a un píxel en una pantalla de densidad media, y el sistema la multiplica por un factor según el escalón real del dispositivo. Un margen declarado en esas unidades mide lo mismo en milímetros en cualquier teléfono; declarado en píxeles, se encoge en las pantallas buenas. Para textos existe además una unidad escalable que respeta el ajuste de tamaño de fuente del usuario, y usarla no es una recomendación estética sino un requisito de accesibilidad.
Para las imágenes, la elección moderna es sencilla: usa vectores siempre que puedas y reserva los mapas de bits para fotografías. Un vector es un único archivo que se rasteriza a la densidad exacta que haga falta, elimina la necesidad de mantener cinco copias del mismo icono y reduce el tamaño del paquete de forma notable.
El eje de tamaño merece un matiz que cambió con los plegables y el modo multiventana: el calificador de anchura mínima describe la ventana asignada a tu app, no la pantalla física. Una app en media pantalla de una tablet recibe una configuración de teléfono, y eso es correcto —lo que importa para el diseño es el espacio del que dispones, no el cristal que tienes delante—.
Los calificadores antiguos de tamaño, los que clasificaban las pantallas en cuatro categorías cualitativas, están obsoletos por esta misma razón: describían el dispositivo y no la ventana, y con las ventanas redimensionables dejaron de significar nada estable. La forma moderna de razonar sobre esto son las clases de tamaño de ventana, que agrupan la anchura y la altura disponibles en tres tramos —compacto, medio y expandido— y que Compose expone como estado observable. Los calificadores de directorio siguen sirviendo para valores concretos como márgenes o número de columnas; las clases de tamaño sirven para decidir la estructura de la interfaz, que es una decisión de código y no de recurso.
Idioma y región
Un código de dos o tres letras, y opcionalmente la región marcada con una letra previa. Es el calificador de mayor precedencia de todos.
Modo nocturno
Dos paletas completas sin una sola condición en el código. El sistema alterna según la preferencia del usuario o la programación horaria.
Anchura mínima
Expresada en unidades independientes de densidad. Describe la ventana disponible, no el hardware, y es estable ante rotaciones.
Densidad
Escalones desde baja hasta extra extra extra alta, más un valor que marca un recurso como independiente de la densidad.
Lo que parece una carpeta de archivos es en realidad una tabla de despacho. Cuando escribes una referencia a un recurso, no estás nombrando un archivo: estás nombrando una decisión que el sistema tomará más tarde, con información que tú no tenías cuando compilabas. Ese aplazamiento es la respuesta de Android al problema que define la plataforma —que el hardware, el idioma, el tamaño de ventana y las preferencias del usuario son desconocidos en tiempo de compilación y además pueden cambiar mientras la app está viva—. La alternativa sería resolverlo en el código con condicionales, y esa alternativa se probó en otras plataformas: produce funciones llenas de ramas que nadie recuerda actualizar, textos incrustados que impiden traducir sin recompilar, y apps que se ven bien en el móvil del programador y mal en el resto del mundo. La externalización invierte la responsabilidad: tú declaras el espacio de variantes posibles, el sistema selecciona un punto de ese espacio, y lo hace con un algoritmo público, estable y auditable. El precio es real —hay que aprender la tabla de precedencia y aceptar que un cambio de configuración recree tu actividad—, pero a cambio recibes algo que ninguna cantidad de condicionales te daría: adaptación correcta a configuraciones que no existían cuando publicaste la app. Cuando entiendes esto, dejas de ver res/ como un depósito de archivos y empiezas a verlo como lo que es, la declaración del rango de mundos en los que tu app sabe existir.
- Declara un texto en la carpeta por defecto y sobrescríbelo en una variante de idioma; cambia el idioma del dispositivo y comprueba el cambio sin tocar código.
- Crea una paleta alternativa para modo nocturno y alterna el tema del sistema con la app abierta.
- Define una dimensión distinta para una anchura mínima de 600 unidades y ejecuta la app en modo multiventana estrechando la ventana hasta cruzar el umbral.
- Borra a propósito una clave de la carpeta por defecto dejándola solo en una variante, ejecuta en un dispositivo que no encaje y observa el fallo exacto.
- Sustituye un icono de mapa de bits en cinco densidades por un único vector y compara el tamaño del paquete resultante.