¿Buscas nuestro logo?
Aquí te dejamos una copia, pero si necesitas más opciones o quieres conocer más, visita nuestra área de marca.
Conoce nuestra marca.¿Buscas nuestro logo?
Aquí te dejamos una copia, pero si necesitas más opciones o quieres conocer más, visita nuestra área de marca.
Conoce nuestra marca.dev
Miguel Román 08/04/2024 Cargando comentarios…
Ya está aquí una nueva entrega donde ampliaremos lo que ya sabemos sobre el uso de Material Theme en Compose. Si todavía no has tenido tiempo de familiarizarte con ello, te invitamos a pasarte por el post Themes en Compose: MaterialTheme.
Ahora que ya has empezado a hacer tus primeras pruebas con Material Theme, probablemente te hayas topado con un escollo común a la hora de implementar aplicaciones “del mundo real”: los diseñadores tienen su propio sistema de organización y por mucho que haya adaptado sus variables de color o tipografía al patrón de Material, hay valores extra que no tienen cabida.
A continuación, estudiaremos cómo podemos añadir nuevos valores a la definición y así ampliar sus funciones.
Lo primero que haremos será aprender cómo añadir una definición extra.
Para ampliar las colecciones de colores, tipografía o shapes que presenta Material Theme de forma rápida y consistente con las APIs de uso de la librería, podemos optar por crear extensiones para cada una de ellas. Por ejemplo, si queremos añadir un nuevo valor a los colores que ofrece Material Theme lo definiríamos como una extensión de la clase:
val ColorScheme.extraColor: Color
@Composable
get() = if (isSystemInDarkTheme()) Color.Red else Color.Blue
De esta forma podemos invocar de manera normal a nuestro nuevo color de la siguiente manera:
MaterialTheme.colorScheme.extraColor
También podemos añadir tipografías o formas (shapes) siguiendo esta misma línea:
val Typography.extraTextStyle: TextStyle
@Composable
get() = TextStyle(color = Color.Red)
// Uso
MaterialTheme.typography.extraTextStyle
val Shapes.extraShape: Shape
@Composable
get() = RoundedCornerShape(size = 20.dp)
// Uso
MaterialTheme.shapes.extraShape
Aunque es una forma completamente lícita de extender los valores de Material Theme, realmente es poco efectiva. En la siguiente sección veremos una mejor forma de enfocarlo.
Hay una segunda aproximación que nos permite añadir más valores de una forma más organizada y elegante consistente en definir nuestros propios esquemas de colores o tipografías y añadirlos como extensión de Material Theme. Así, podremos acceder a dichos valores de forma consistente con el resto de la API de Material.
En primer lugar, tendremos que crear una clase análoga a la que queremos ampliar, en nuestro ejemplo será ColorScheme. Como la propia clase indica en sus comentarios, contiene todos los parámetros con nombres de colores utilizados en un Material Theme. Nuestra clase CustomColorScheme define todos los nuevos nombres de colores que queramos utilizar en nuestra app.
Por simplicidad, en el ejemplo se han incluido solo tres con nombres genéricos extraColorN:
@Immutable
class CustomColorScheme(
extraColor1: Color,
extraColor2: Color,
extraColor3: Color,
) {
var extraColor1 by mutableStateOf(extraColor1, structuralEqualityPolicy())
internal set
var extraColor2 by mutableStateOf(extraColor2, structuralEqualityPolicy())
internal set
var extraColor3 by mutableStateOf(extraColor3, structuralEqualityPolicy())
internal set
/** Returns a copy of this CustomColorScheme, optionally overriding some of the values. */
fun copy(
extraColor1: Color = this.extraColor1,
extraColor2: Color = this.extraColor2,
extraColor3: Color = this.extraColor3,
): CustomColorScheme = CustomColorScheme(extraColor1, extraColor2, extraColor3)
override fun toString(): String {
return "CustomColorScheme(" +
"extraColor1=$extraColor1" +
"extraColor2=$extraColor2" +
"extraColor3=$extraColor3" +
")"
}
}
En términos generales, hemos redefinido las mismas funciones que utiliza Material en su ColorScheme para asegurar la coherencia con el resto del sistema. Para ello, hemos creado una clase sellada que define los nombres adicionales de recursos que queramos declarar. A esto solo habría que añadir un par de detalles.
En primer lugar, definir las funciones en el mismo fichero CustomColorScheme que definen los valores en función de si estamos en light o dark mode y su ProvidableCompositionLocal para acceder a los valores por defecto:
/**
* Creates a light custom color scheme with default colors for the extended colors
* @return A CustomColorScheme instance representing the light color scheme.
*/
fun lightCustomColorScheme(
extraColor1: Color = CustomColors.darkNavy,
extraColor2: Color = CustomColors.yellow20,
extraColor3: Color = CustomColors.black40,
): CustomColorScheme =
CustomColorScheme(
extraColor1 = extraColor1,
extraColor2 = extraColor2,
extraColor3 = extraColor3
)
/**
* Creates a dark custom color scheme with default colors for the extended colors
* @return A CustomColorScheme instance representing the dark color scheme.
*/
fun darkCustomColorScheme(
extraColor1: Color = CustomColors.darkNavy,
extraColor2: Color = CustomColors.yellow20,
extraColor3: Color = CustomColors.white,
): CustomColorScheme =
CustomColorScheme(
extraColor1 = extraColor1,
extraColor2 = extraColor2,
extraColor3 = extraColor3
)
/**
* A composition local containing the current custom color scheme.
*/
val LocalCustomColorScheme = staticCompositionLocalOf { lightCustomColorScheme() }
En el caso de querer ampliar las tipografías, procederíamos de forma muy similar: Crearíamos nuestra clase CustomTypography y su ProvidableCompositionLocal siguiendo el patrón de la clase Typography.
// Custom fonts (if necessary)
private val montserratNormal = Font(R.font.montserrat_regular_ttf, FontWeight.Normal)
private val montserratBold = Font(R.font.montserrat_bold_ttf, FontWeight.Bold)
// Declare the FontFamily
val montserratFontFamily = FontFamily(
montserratNormal,
montserratBold
)
@Immutable
class CustomTypography(
val customBase: TextStyle = TextStyle(
fontFamily = montserratFontFamily,
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
),
val customBold: TextStyle = customBase.copy(
fontWeight = FontWeight.Bold,
letterSpacing = 1.5.sp,
lineHeight = 112.sp,
fontSize = 96.sp
),
val customSmall: TextStyle = customBase.copy(
fontSize = 12.sp
),
) {
fun copy(
customBase: TextStyle = this.customBase,
customBold: TextStyle = this.customBold,
customSmall: TextStyle = this.customSmall,
): CustomTypography =
CustomTypography(
customBase = customBase,
customBold = customBold,
customSmall = customSmall
)
override fun equals(other: Any?): Boolean {...}
override fun hashCode(): Int {...}
override fun toString(): String {...}
}
val LocalCustomTypography = staticCompositionLocalOf { CustomTypography() }
Y, como último paso, solo deberíamos hacer accesibles dichos valores desde los Themes. Para ello tenemos dos opciones (ambas en nuestro fichero Theme).
val MaterialTheme.customColorScheme: CustomColorScheme
@Composable
@ReadOnlyComposable
get() = LocalCustomColorScheme.current
val MaterialTheme.customTypography: CustomTypography
@Composable
@ReadOnlyComposable
get() = LocalCustomTypography.current
@Composable
fun CustomTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
customTypography: CustomTypography = CustomTypography(),
content: @Composable () -> Unit,
) {
val colorScheme = when (darkTheme) {
true -> DarkColorScheme
false -> LightColorScheme
}
val customColorScheme = when (darkTheme) {
true -> lightCustomColorScheme()
false -> darkCustomColorScheme() }
CompositionLocalProvider(
LocalCustomColorScheme provides customColorScheme,
LocalCustomTypography provides customTypography
) {
MaterialTheme(
colorScheme = colorScheme,
content = content
)
}
}
De esta forma, podremos acceder a los nuevos colores de la siguiente manera:
MaterialTheme.customColorScheme.extraColor1
MaterialTheme.customTypography.extraColor1
object CustomTheme {
val colors: CustomColorScheme
@Composable
get() = LocalCustomColorScheme.current
val typography: CustomTypography
@Composable
get() = LocalCustomTypography.current
}
// Uso
CustomTheme.colors.extraColor1
CustomTheme.typography.customSmall
En este post, hemos visto cómo añadir más valores de forma individual o en grupo y acoplarnos a la API de Material Theme, haciendo nuestros temas mucho más flexibles y adaptables a las formas de trabajar de nuestros equipos de diseño.
Aun así, si esto se quedase corto o no quisiéramos utilizar en absoluto la API de Material, siempre podemos crear nuestro propio tema desde cero y utilizarlo con nuestros componentes personalizados. Este cambio resulta más radical y habría que adaptar todo el sistema de componentes a esta forma, por lo que lo dejaremos para más adelante.
Espero que os haya sido de ayuda y recuerda: ¡Keep Calm and Debug!
Los comentarios serán moderados. Serán visibles si aportan un argumento constructivo. Si no estás de acuerdo con algún punto, por favor, muestra tus opiniones de manera educada.
Cuéntanos qué te parece.