
Todos los desarrolladores de Android tenían que trabajar con estilos de todos modos. Alguien se siente seguro con ellos, alguien solo tiene un conocimiento superficial, que a menudo no les permite resolver el problema por sí mismos.
En previsión del lanzamiento del tema oscuro, se decidió actualizar en la memoria toda la información sobre temas y estilos en las aplicaciones de Android.
Lo que se discutirá:
- Considere los conceptos básicos de temas y estilos en las aplicaciones de Android, vea qué oportunidades nos brindan;
- Creemos un tema simple usando Componentes materiales y juguemos con redefinir estilos;
- Veamos cómo funciona el tema oscuro;
- Formular recomendaciones para trabajar con estilos.
Comencemos con lo básico
En su estructura, los temas y estilos tienen una estructura común:
<style name="MyStyleOrTheme"> <item name="key">value</item> </style>
Para crear, use la etiqueta de style
. Cada estilo tiene un nombre y almacena los parámetros key-value
.
Todo es bastante simple. Pero, ¿cuál es la diferencia entre tema y estilo?
La única diferencia es cómo los usamos.
Tema
Un tema es un conjunto de parámetros que se aplican a toda la aplicación, actividad o componente de vista. Contiene los colores básicos de la aplicación, estilos para representar todos los componentes de la aplicación y varias configuraciones.
Tema de ejemplo:
<style name="Theme.MyApp.Main" parent="Theme.MaterialComponents.Light.NoActionBar"> <item name="colorPrimary">@color/color_primary</item> <item name="colorPrimaryVariant">@color/color_primary_variant</item> <item name="colorSecondary">@color/color_secondary</item> <item name="colorOnPrimary">@color/color_on_primary</item> <item name="colorOnError">@color/color_on_error</item> <item name="textAppearanceHeadline1">@style/TextAppearance.MyTheme.Headline1</item> <item name="bottomSheetDialogTheme">@style/ThemeOverlay.MyTheme.BottomSheetDialog</item> <item name="chipStyle">@style/Widget.MaterialComponents.Chip.Action</item> <item name="textInputStyle">@style/Widget.MaterialComponents.TextInputLayout.FilledBox</item> <item name="android:windowTranslucentStatus">true</item> </style>
El tema ha redefinido los colores principales de la aplicación ( colorPrimary
, colorSecondary
), el estilo del texto ( textAppearanceHeadline1
) y algunos componentes estándar de la aplicación, así como la opción de una barra de estado transparente.
Para que el estilo se convierta en un tema real, es necesario heredar (hablaremos de la herencia un poco más adelante) de la implementación predeterminada del tema.
Estilo
Un estilo es un conjunto de parámetros para diseñar un solo componente de Vista.
Estilo de muestra para TextInputLayout :
<style name="Widget.MyApp.CustomTextInputLayout" parent="Widget.MaterialComponents.TextInputLayout.FilledBox"> <item name="boxBackgroundMode">outline</item> <item name="boxStrokeColor">@color/color_primary</item> <item name="shapeAppearanceOverlay">@style/MyShapeAppearanceOverlay</item> </style>
Atributo
Un atributo es una clave de estilo o tema. Estos son pequeños ladrillos con los que todo está construido:
colorPrimary colorSecondary colorOnError boxBackgroundMod boxStrokeColor shapeAppearanceOverlay ...
Todas estas claves son atributos estándar.
Podemos crear nuestros propios atributos:
<attr name="myFavoriteColor" format="color|reference" />
El atributo myFavoriteColor
apuntará a un color o enlace a un recurso de color.
En el formato, podemos especificar valores bastante estándar:
- color
- referencia
- cuerda
- enumeración
- fracción
- dimensión
- booleano
- banderas
- flotar
- entero
Por su naturaleza, un atributo es una interfaz . Debe implementarse en el tema:
<style name="Theme.MyApp.Main" parent="Theme.MaterialComponents.Light.NoActionBar"> <item name="myFavoriteColor">@color/color_favorite</item> </style>
Ahora podemos referirnos a eso. La estructura general de la apelación se ve así:

1 — , ; 2 — namespace ( Material Components Library); 3 — , (); 4 — .
Bueno, finalmente, cambiemos, por ejemplo, el color del texto del campo:
<androidx.appcompat.widget.AppCompatTextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="?attr/myFavoriteColor"/>
Gracias a los atributos, podemos agregar cualquier tipo de abstracción que cambie dentro del tema.
Heredar temas y estilos
Al igual que en OOP, podemos adoptar la funcionalidad de una implementación existente. Hay dos formas de hacer esto:
- Explícito (explícito)
- Implícito (implícitamente)
Para herencia explícita, especificamos el padre usando la palabra clave parent
:
<style name="SnackbarStyle" parent="Widget.MaterialComponents.Snackbar"> </style>
Para la herencia implícita, usamos dot-notation
para indicar el padre:
<style name="SnackbarStyle.Green"> </style>
No hay diferencia en el trabajo de estos enfoques.
Muy a menudo podemos encontrar estilos similares:
<style name="Widget.MyApp.Snackbar" parent="Widget.MaterialComponents.Snackbar"> </style>
Puede parecer que el estilo se crea por doble herencia. Este no es realmente el caso. La herencia múltiple está prohibida. En esta definición, la herencia explícita siempre gana .
Es decir, se creará un estilo con el nombre Widget.MyApp.Snackbar
, que es el descendiente de Widget.MaterialComponents.Snackbar
.
Superposición de temas
ThemeOverlay: estos son temas especiales "ligeros" que le permiten anular los atributos del tema principal para el componente View.
No vamos a ir lejos por un ejemplo, pero tomemos un caso de nuestra aplicación. Los diseñadores decidieron que necesitamos crear un campo de inicio de sesión estándar, que tendrá un color diferente del estilo principal.
Con el tema principal, el campo de entrada se ve así:

Se ve muy bien, pero los diseñadores insisten en que el campo esté en un estilo marrón.
Bien, ¿cómo podemos resolver este problema?
La solución correcta es usar ThemeOverlay .
Cree ThemeOverlay
y redefina el color principal del tema :
<style name="ThemeOverlay.MyApp.Login" parent="ThemeOverlay.MaterialComponents.TextInputEditText"> <item name="colorPrimary">@color/colorBrown</item> </style>
A continuación, lo especificamos usando la etiqueta especial android:theme
en nuestro TextInputLayout
:
<com.google.android.material.textfield.TextInputLayout android:theme="@style/ThemeOverlay.MyApp.Login" android:hint="Login" ... > <com.google.android.material.textfield.TextInputEditText ... /> </com.google.android.material.textfield.TextInputLayout>
Todo funciona como lo necesitamos.

Por supuesto, surge la pregunta: ¿cómo funciona bajo el capó?
Esta magia te permite producir ContextThemeWrapper
. Al crear una Vista en LayoutInflater
, se LayoutInflater
un contexto donde el tema actual se tomará como base y los parámetros que especificamos en nuestro tema Overlay se redefinirán en él.
../LayoutInflater.java final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME); final int themeResId = ta.getResourceId(0, 0); if (themeResId != 0) { context = new ContextThemeWrapper(context, themeResId); } ta.recycle();
Del mismo modo, podemos anular independientemente cualquier parámetro de tema en la aplicación.
La secuencia de aplicación de temas y estilos al componente Ver

La principal prioridad es el archivo de marcado. Si se define un parámetro en él, se ignorarán todos los parámetros similares.
<Button android:textColor="@color/colorRed" ... />
La siguiente prioridad es el estilo de Vista:
<Button style=“@Widget.MyApp.ButtonStyle" ... />
Lo siguiente usa estilos predefinidos para el componente:
<style name="Theme.MyApp.Main" parent="Theme..."> <item name=“materialButtonStyle”>@Widget.MyApp.ButtonStyle</item> </style>
Si no se encontraron parámetros, se utilizan los atributos del tema:
<style name="Theme.MyApp.Main" parent="Theme..."> <item name=“colorPrimary”>@colorPrimary</item> </style>
En general, esto es todo lo que necesita saber para comenzar a trabajar con temas. Ahora echemos un vistazo rápido a la biblioteca de diseño de Componentes de material actualizada.
Pueden los componentes materiales venir con nosotros
Material Components se introdujo en Google I / O 2018 y es un reemplazo de Design Support Library.
La biblioteca nos brinda la oportunidad de utilizar componentes actualizados de Material Design 2.0. Además, aparecieron muchas opciones de personalización interesantes. Todo esto le permite escribir aplicaciones brillantes y únicas.


Aquí hay algunos ejemplos de aplicaciones en el nuevo estilo: Owl , Reply , Crane .
Pasemos a practicar
Para crear un tema, debe heredar del tema base:
Theme.MaterialComponents Theme.MaterialComponents.NoActionBar Theme.MaterialComponents.Light Theme.MaterialComponents.Light.NoActionBar Theme.MaterialComponents.Light.DarkActionBar Theme.MaterialComponents.DayNight Theme.MaterialComponents.DayNight.NoActionBar Theme.MaterialComponents.DayNight.DarkActionBar
Todos ellos son muy similares a los temas de AppCompat
, pero tienen atributos y configuraciones adicionales.
Puede obtener más información sobre los nuevos atributos en material.io .
Si por alguna razón no puede cambiar a un nuevo tema en este momento, los temas de Bridge
lo harán. AppCompat
temas de AppCompat
y tienen todos los nuevos atributos de Componentes de material. Solo necesita agregar el postfix Bridge
y usar todas las características sin temor:
Theme.MaterialComponents.Light.Bridge
Y aquí está nuestro tema:
<style name="Theme.MyApp.Main" parent="Theme.MaterialComponents.Light.NoActionBar"> <item name="colorPrimary">@color/color_primary</item> <item name="colorPrimaryVariant">@color/color_primary_variant</item> <item name="colorSecondary">@color/color_secondary</item> <item name="colorSecondaryVariant">@color/color_secondary_variant</item> <style>
Los nombres de los colores primarios (colores de la marca) han cambiado:
colorPrimary — ( AppCompat); colorPrimaryVariant — ( colorPrimaryDark AppCompat); colorSecondary — ( colorAccent AppCompat); colorSecondaryVariant — .
Se puede encontrar más información sobre los colores en material.io .
Ya mencioné que el tema contiene estilos estándar para cada componente de Vista. Por ejemplo, para Snackbar
estilo se llamará snackbarStyle
, para checkbox
- checkboxStyle
y luego todo será similar. Un ejemplo pondrá todo en su lugar:

Crea tu propio estilo y aplícalo al tema:
<style name="Theme.MyApp.Main" parent="Theme.MaterialComponents.Light.NoActionBar"> <item name="snackbarStyle">@style/Widget.MyApp.SnackbarStyle</item> </style> <style name="Widget.MyApp.SnackbarStyle" parent="Widget.MaterialComponents.Snackbar"> </style>
Es importante comprender que cuando redefine un estilo en un tema, se aplicará a todas las vistas de este tipo en la aplicación (Actividad).
Si desea aplicar el estilo a una sola Vista específica, debe usar la etiqueta de style
en el archivo con marcado:
<com.google.android.material.button.MaterialButton style="@style/Widget.MyApp.SnackbarStyle" ... />
Una de las innovaciones que realmente me impresionó fue ShapeAppearance . ¡Le permite cambiar la forma de los componentes directamente en el sujeto!
Cada componente de vista pertenece a un determinado grupo:
forma Apariencia Componente pequeño
forma Apariencia Componente medio
forma Apariencia Componente grande
Como podemos entender por el nombre, en grupos de vistas de diferentes tamaños.

Verificación en la práctica:
<style name="Theme.MyApp.Main" parent="Theme.MaterialComponents.Light.NoActionBar"> <item name="shapeAppearanceSmallComponent">@style/Widget.MyApp.SmallShapeAppearance</item> </style> <style name="Widget.MyApp.SmallShapeAppearance" parent=“ShapeAppearance.MaterialComponents.SmallComponent”> <item name="cornerFamilyTopLeft">rounded</item> <item name="cornerFamilyBottomRight">cut</item> <item name="cornerSizeTopLeft">20dp</item> <item name="cornerSizeBottomRight">15dp</item> </style>
Creamos Widget.MyApp.SmallShapeAppearance
para los componentes "pequeños". Redondeamos la esquina superior izquierda en 20dp
y la esquina inferior derecha se cortó en 15dp
.
Obtuve este resultado:

Se ve interesante ¿Funcionará en la vida real? El tiempo lo dirá.
Al igual que con los estilos, solo podemos aplicar ShapeAppearance
a un componente de vista.
¿Qué hay sobre un tema oscuro?
El lanzamiento de Android Q tendrá lugar muy pronto, y con él vendrá el tema oscuro oficial.
Quizás una de las características más interesantes y espectaculares de la nueva versión de Android es la aplicación automática de un tema oscuro para toda la aplicación con una línea de código.
Suena genial, intentémoslo. Sugiero tomar un cliente de gitlab favorito de terrakok .
Permitir volver a pintar la aplicación (deshabilitada de manera predeterminada):
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar"> <item name="android:forceDarkAllowed">true</item> </style>
El android:forceDarkAllowed
está disponible con API 29 (Android Q).
Comenzamos, mira lo que pasó:

De acuerdo en que para una línea de código se ve muy bien.
Por supuesto, hay problemas: el BottomNavigationBar
fusiona con el fondo, el cargador permanece en blanco, la selección del código sufre y, al parecer, todo, al menos nada grave me ha sorprendido.
Estoy seguro de que pasar no tanto tiempo puede resolver los principales problemas. Por ejemplo, desactivar el modo oscuro automático para vistas individuales (sí, también es posible - android:forceDarkAllowed
está disponible para Ver en un archivo de marcado).
Debe recordarse que este modo está disponible solo para temas claros, si usa uno oscuro, entonces un tema oscuro forzado no funcionará.
Las recomendaciones de trabajo se pueden encontrar en la documentación y en material.io .
¿Y si queremos hacer todo por nuestra cuenta?
No importa cuán fácil sea usar un tema oscuro forzado, este modo carece de flexibilidad. De hecho, todo funciona de acuerdo con reglas predefinidas que pueden no ser adecuadas para nosotros y, lo que es más importante, para el cliente. Creo que esa decisión puede considerarse temporal, hasta que implementemos un tema oscuro.
En API 8 (Froyo), se -night
calificador de -night
, que hasta el día de hoy se utiliza para aplicar un tema oscuro. Le permite aplicar automáticamente el tema deseado según la hora del día.
En los temas de DayNight
, dicha implementación ya se usa, es suficiente para que heredemos de ellos.
Tratemos de escribir el nuestro:
../values/themes.xml <style name="Theme.DayNight.Base" parent="Theme.MaterialComponents.Light"/> <style name="Theme.MyApp.Main" parent="Theme.DayNight.Base> <!-- ... --> </style> ../values-night/themes.xml <style name="Theme.DayNight.Base" parent="Theme.MaterialComponents"/>
En el recurso habitual para el tema ( values/themes.xml
) heredamos del tema claro, en la "noche" ( values-night/themes.xml
) heredamos del tema oscuro.
Eso es todo. Tenemos una implementación de biblioteca de un tema oscuro. Ahora deberíamos apoyar recursos para dos temas.
Para cambiar entre temas mientras la aplicación se está ejecutando, puede usar AppCompatDelegate.setDefaultNightMode
, que toma los siguientes parámetros:
MODE_NIGHT_NO
- Tema ligero;MODE_NIGHT_YES
- Tema oscuro;MODE_NIGHT_AUTO_BATTERY
- Modo automático. El tema oscuro se enciende si el modo de ahorro de energía está activo;MODE_NIGHT_FOLLOW_SYSTEM
- Modo basado en la configuración del sistema.
¿Qué debemos considerar al trabajar con temas y estilos?
Como señalé, Google ha comenzado oficialmente a forzar un tema oscuro. Estoy seguro de que muchos clientes comenzaron a recibir preguntas: "¿Podemos agregar un tema oscuro?". Es bueno si hace todo bien desde el principio y le resulta fácil cambiar los colores claros a oscuros, mientras recibe una aplicación completamente repintada.
Desafortunadamente, este no es siempre el caso. Existen aplicaciones antiguas que requieren un esfuerzo considerable para realizar los cambios necesarios.
Intentemos formular recomendaciones para trabajar juntos con estilos:
1. El selector de color
Creo que cada desarrollador se enfrenta a una situación en la que aparece un color extraño en el nuevo diseño que aún no está definido en la paleta de aplicaciones. ¿Qué hacer en este caso?
La respuesta correcta es hablar con el diseñador e intentar desarrollar una paleta de colores. Ahora hay muchos programas (Zeplin, Sketch, etc.) que le permiten renderizar colores primarios y luego reutilizarlos.
Cuanto antes lo haga, menos dolores de cabeza tendrá en el futuro.
2. Deletrea los colores por sus nombres propios
En cada aplicación, hay un color que tiene muchas opciones de brillo. Puedes comenzar a inventar nombres para ellos:
<color name="green_tiny">...</color> <color name="green_light">...</color> <color name="green_dark">...</color>
De acuerdo, no se ve muy bien. La pregunta surge de inmediato: ¿qué color es más claro que el tiny
o el light
? ¿Y si tenemos una docena de opciones?
Es mejor seguir el concepto de Google y agregar el brillo apropiado a los nombres de los colores (Google llama a esta opción de color - colorVariant
):
<color name="material_green_300">...</color> <color name="material_green_700">...</color> <color name="material_green_900">...</color>
Con este enfoque, podemos tener cualquier cantidad de opciones de brillo de un color y no tenemos que encontrar nombres específicos, lo cual es realmente difícil.
3. Para abstraer de un color específico si cambia en diferentes temas
Como estamos escribiendo una aplicación en la que habrá al menos dos temas, no podemos permitirnos referirnos a un color específico si se implementa en los temas de diferentes maneras.
Veamos un ejemplo:

Vemos que en un tema claro, por ejemplo, la barra de herramientas está coloreada en púrpura y en la oscuridad es gris oscuro. ¿Cómo implementaríamos este comportamiento usando solo las capacidades de los temas?
Todo es bastante simple: crearemos un atributo y lo implementaremos en temas claros y oscuros con el color apropiado, como se describió anteriormente.
Google recomienda asociar nombres de atributos con semántica de uso.
4. No tengas miedo de crear archivos de recursos
Cuando se escriben muchos estilos, temas y atributos diferentes en styles.xml
, resulta difícil mantenerlos.
Es mejor agrupar todo en archivos separados:
themes.xml — Theme & ThemeOverlay styles.xml — Widget styles type.xml — TextAppearance, text size etc shape.xml — ShapeAppearance motion.xml — Animations styles system_ui.xml — Booleans, colors for UI control //may be other files
Una regla tan simple evitará los archivos de Dios y, por lo tanto, será más fácil mantener los estilos.
5. Reutilizar al máximo
¿Qué haremos si queremos redefinir un atributo que está disponible solo desde una versión específica de la API?
Podemos crear dos temas separados:
../values/themes.xml <style name="Theme.MyApp.Main" parent=”Theme.MaterialComponents.NoActionBar”> </style> ../values-v27/themes.xml <style name="Theme.MyApp.Main" parent=”Theme.MaterialComponents.NoActionBar”> <name="android:windowLightNavigationBar">...</item> </style>
¿Ahora tenemos un tema con todos los parámetros para cada versión de la API? Por supuesto que no! Haremos un tema básico donde los atributos básicos que están disponibles para todas las versiones de la API serán determinados y heredados de la misma en la versión deseada de la API:
../values/themes.xml <style name="Theme.MyApp.Base" parent=”Theme.MaterialComponents.DayNight.NoActionBar”> </style> <style name="Theme.MyApp.Main" parent=”Theme.MyApp.Base”/> ../values-v27/themes.xml <style name="Theme.MyApp.Base.V27" parent="Theme.MyApp.Base"> <name="android:windowLightNavigationBar">...</item> </style> <style name="Theme.MyApp.Main" parent=”Theme.MyApp.Base.V27”/>
Por este principio, se construyen todos los temas en la biblioteca estándar.
6. Use recursos vectoriales y tinte
Creo que no vale la pena decir por qué los recursos vectoriales son buenos. Todos ya lo saben (por si acaso, un enlace a la documentación ). Bueno, el teñido nos ayudará a colorearlos con los colores del tema.
Puede ver qué es el tinte y cómo trabajar con él en este ejemplo .
7.? Android: attr / ... vs? Attr / ...
Al acceder a los recursos, tenemos la oportunidad de utilizar tanto los atributos del sistema como los atributos de la biblioteca de Componentes del material. Es importante comprender que algunos atributos existen solo con una versión específica de la API. Como todos sabemos bien, acceder a un recurso inexistente conduce a un bloqueo (la pelusa, por supuesto, nos dirá si algo está mal, pero no siempre debe confiar en él)
android:background="?android:attr/selectableItemBackgroundBorderless" android:background="?attr/selectableItemBackgroundBorderless"
En el primer caso, accedemos al recurso del sistema, como lo indica android
. En el segundo caso, a un atributo de la biblioteca donde se implementa la compatibilidad con versiones anteriores.
Es mejor usar siempre la segunda opción.
8. Siempre especifique padre para el estilo
Puede haber parámetros en el estilo primario, sin los cuales el componente se representará incorrectamente, por lo que siempre debe especificar el primario.
<style name="Widget.MyApp.LoginInputLayout" parent="Widget.MaterialComponents.TextInputLayout.FilledBox"> <item name="errorTextColor">@color/colorError</item> </style>
9. Tema, estilo o ...?
Al crear sus propios temas y estilos, será excelente si especifica un prefijo que diga qué tipo de estilo es y para qué está definido. Tal denominación hará que sea muy fácil estructurar y extender estilos.
<style name="Theme.MyApp.Main" parent=”...”/> <style name="Widget.MyApp.LoginInputLayout" parent="..."/> <style name="Widget.MyApp.LoginInputLayout.Brown"/> <style name="ThemeOverlay.MyApp.Login" parent=”...”/>
10. Use TextAppearance
Será un buen tono para expandir los estilos básicos del texto y usarlos en todas partes.
Material Design: Typography , Typography Theming .
Conclusión
, — , . - . Material Components. . Sketch — Material Theme Editor . . , .
Material Components GitHub — Modular and customizable Material Design UI components for Android . . , — sample, .
Enlaces utiles: