Cómo trabajar con fuentes

En esta página, se describe cómo configurar fuentes en tu app de Compose.

Establecer fuente

Text tiene un parámetro fontFamily para permitir la configuración de la fuente usada en el elemento componible. Según la configuración predeterminada, se incluyen las familias de fuentes serif, Sans Serif, monoespaciales y cursivas:

@Composable
fun DifferentFonts() {
    Column {
        Text("Hello World", fontFamily = FontFamily.Serif)
        Text("Hello World", fontFamily = FontFamily.SansSerif)
    }
}

Aparece la frase

Puedes usar el atributo fontFamily para trabajar con fuentes personalizadas y tipos de letra definidos en la carpeta res/font:

Representación gráfica de la carpeta res > font en el entorno de desarrollo

En este ejemplo, se muestra cómo definirías una fontFamily según esos archivos de fuente y con la función Font:

val firaSansFamily = FontFamily(
    Font(R.font.firasans_light, FontWeight.Light),
    Font(R.font.firasans_regular, FontWeight.Normal),
    Font(R.font.firasans_italic, FontWeight.Normal, FontStyle.Italic),
    Font(R.font.firasans_medium, FontWeight.Medium),
    Font(R.font.firasans_bold, FontWeight.Bold)
)

Puedes pasar este fontFamily a tu elemento Text componible. Debido a que una fontFamily puede incluir diferentes pesos, puedes configurar fontWeight de forma manual para seleccionar el grosor correcto para tu texto:

Column {
    Text(text = "text", fontFamily = firaSansFamily, fontWeight = FontWeight.Light)
    Text(text = "text", fontFamily = firaSansFamily, fontWeight = FontWeight.Normal)
    Text(
        text = "text",
        fontFamily = firaSansFamily,
        fontWeight = FontWeight.Normal,
        fontStyle = FontStyle.Italic
    )
    Text(text = "text", fontFamily = firaSansFamily, fontWeight = FontWeight.Medium)
    Text(text = "text", fontFamily = firaSansFamily, fontWeight = FontWeight.Bold)
}

Aparece la frase

Si deseas aprender a configurar la tipografía en toda tu app, consulta Sistemas de diseño personalizado en Compose.

Fuentes descargables

A partir de Compose 1.2.0, puedes usar la API de fuentes descargables en tu app de Compose para descargar fuentes de Google de forma asíncrona y usarlas en tu app.

Actualmente, no se admiten las fuentes descargables que proporcionan los proveedores personalizados.

Cómo usar fuentes descargables de manera programática

Para descargar una fuente de manera programática desde tu app, sigue estos pasos:

  1. Agrega la dependencia:

    Groovy

    dependencies {
        ...
        implementation "androidx.compose.ui:ui-text-google-fonts:1.6.1"
    }
    

    Kotlin

    dependencies {
        ...
        implementation("androidx.compose.ui:ui-text-google-fonts:1.6.1")
    }
  2. Inicializa GoogleFont.Provider con las credenciales de Google Fonts:
    val provider = GoogleFont.Provider(
        providerAuthority = "com.google.android.gms.fonts",
        providerPackage = "com.google.android.gms",
        certificates = R.array.com_google_android_gms_fonts_certs
    )
    Los parámetros que recibe el proveedor son los siguientes:
    • La autoridad del proveedor de fuentes de Google Fonts
    • El paquete del proveedor de fuentes para verificar su identidad
    • Una lista de conjuntos de hashes para los certificados a fin de verificar la identidad del proveedor Puedes encontrar los hashes necesarios para el proveedor de Google Fonts en el archivo font_certs.xml de la app de ejemplo de Jetchat.
  3. Define un objeto FontFamily:
    // ...
     import androidx.compose.ui.text.googlefonts.GoogleFont
     import androidx.compose.ui.text.font.FontFamily
     import androidx.compose.ui.text.googlefonts.Font
     // ...
    
    val fontName = GoogleFont("Lobster Two")
    
    val fontFamily = FontFamily(
        Font(googleFont = fontName, fontProvider = provider)
    )
    Puedes buscar otros parámetros para la fuente, como el grosor y el estilo, con FontWeight y FontStyle, respectivamente:
    // ...
     import androidx.compose.ui.text.googlefonts.GoogleFont
     import androidx.compose.ui.text.font.FontFamily
     import androidx.compose.ui.text.googlefonts.Font
     // ...
    
    val fontName = GoogleFont("Lobster Two")
    
    val fontFamily = FontFamily(
        Font(
            googleFont = fontName,
            fontProvider = provider,
            weight = FontWeight.Bold,
            style = FontStyle.Italic
        )
    )
  4. Configura el FontFamily que se usará en la función de componibilidad Text:

Text(
    fontFamily = fontFamily, text = "Hello World!"
)

También puedes definir la tipografía para usar tu FontFamily:

val MyTypography = Typography(
    labelMedium = TextStyle(
        fontFamily = fontFamily, fontWeight = FontWeight.Normal, fontSize = 12.sp/*...*/
    ),
    labelLarge = TextStyle(
        fontFamily = fontFamily,
        fontWeight = FontWeight.Bold,
        letterSpacing = 2.sp,
        /*...*/
    ),
    displayMedium = TextStyle(
        fontFamily = fontFamily, fontWeight = FontWeight.SemiBold/*...*/
    ),
    /*...*/
)

A continuación, establece la tipografía en el tema de tu app:

MyAppTheme(
    typography = MyTypography
)/*...*/

Para ver un ejemplo de una app que implementa fuentes descargables en Compose junto con Material3, consulta la app de ejemplo de Jetchat.

Cómo agregar fuentes de resguardo

Puedes determinar una cadena de resguardo para tu fuente en caso de que esta no se descargue correctamente. Por ejemplo, si tu fuente descargable se define de la siguiente manera:

// ...
 import androidx.compose.ui.text.googlefonts.Font
 // ...

val fontName = GoogleFont("Lobster Two")

val fontFamily = FontFamily(
    Font(googleFont = fontName, fontProvider = provider),
    Font(googleFont = fontName, fontProvider = provider, weight = FontWeight.Bold)
)

Puedes definir los valores predeterminados de la fuente para ambos grosores, de la siguiente manera:

// ...
 import androidx.compose.ui.text.font.Font
 import androidx.compose.ui.text.googlefonts.Font
 // ...

val fontName = GoogleFont("Lobster Two")

val fontFamily = FontFamily(
    Font(googleFont = fontName, fontProvider = provider),
    Font(resId = R.font.my_font_regular),
    Font(googleFont = fontName, fontProvider = provider, weight = FontWeight.Bold),
    Font(resId = R.font.my_font_regular_bold, weight = FontWeight.Bold)
)

Asegúrate de agregar las importaciones correctas.

Cuando defines FontFamily de esta manera, se crea una FontFamily que contiene dos cadenas, una por cada grosor. El mecanismo de carga intentará resolver la fuente en línea primero y, luego, la fuente ubicada en la carpeta de recursos R.font local.

Depura tu implementación

Para ayudarte a verificar si la fuente se descarga correctamente, puedes definir un controlador de corrutinas de depuración. Tu controlador proporciona el comportamiento de lo que se debe hacer en caso de que la fuente no se cargue de forma asíncrona.

Para comenzar, crea un CoroutineExceptionHandler:

val handler = CoroutineExceptionHandler { _, throwable ->
    // process the Throwable
    Log.e(TAG, "There has been an issue: ", throwable)
}

Pásalo al método createFontFamilyResolver para que el agente de resolución use el controlador nuevo:

CompositionLocalProvider(
    LocalFontFamilyResolver provides createFontFamilyResolver(LocalContext.current, handler)
) {
    Column {
        Text(
            text = "Hello World!", style = MaterialTheme.typography.bodyMedium
        )
    }
}

También puedes usar la API de isAvailableOnDevice del proveedor para probar si está disponible y si los certificados están configurados de forma correcta. Para ello, puedes llamar al método isAvailableOnDevice, que muestra el valor "false" si el proveedor se configuró de forma incorrecta.

val context = LocalContext.current
LaunchedEffect(Unit) {
    if (provider.isAvailableOnDevice(context)) {
        Log.d(TAG, "Success!")
    }
}

Advertencias

Google Fonts tarda varios meses en habilitar las nuevas fuentes en Android. Existe un intervalo de tiempo entre el momento en que se agrega una fuente en fonts.google.com y el momento en que está disponible a través de la API de fuentes descargables (ya sea en el sistema de View o en Compose). Es posible que las fuentes agregadas recientemente no se carguen en tu app con una IllegalStateException. Para ayudar a los desarrolladores a identificar este error sobre otros tipos de errores de carga de fuentes, agregamos mensajes descriptivos para la excepción en Compose con los cambios aquí. Si encuentras algún problema, infórmalo con la Herramienta de seguimiento de errores.

Cómo usar fuentes variables

Una fuente variable es un formato de fuente que permite que un archivo de fuente incluya diferentes estilos. Con las fuentes variables, puedes modificar los ejes (o parámetros) para generar el estilo que prefieras. Estos ejes pueden ser estándar, como el grosor, el ancho, la inclinación y la cursiva, o bien personalizados, que difieren según las fuentes variables.

Cinco configuraciones de la misma fuente variable con diferentes valores de eje.
Figura 1: Texto con la misma fuente variable personalizada con diferentes valores de eje

El uso de fuentes variables en lugar de archivos de fuentes normales te permite tener solo un archivo de fuente en lugar de varios.

Para obtener más información general sobre las fuentes variables, consulta Google Fonts Knowledge, el catálogo completo de las fuentes variables disponibles y una tabla de los ejes compatibles con cada fuente.

En este documento, se muestra cómo implementar una fuente variable en tu app de Compose.

Cómo cargar una fuente variable

  1. Descarga la fuente variable que deseas usar (por ejemplo, Roboto Flex) y colócala en la carpeta app/res/font de tu app. Asegúrate de que el archivottf que agregas es la versión de fuente variable de la fuente y que el nombre de tu archivo de fuente está completamente en minúsculas y no contiene caracteres especiales.

  2. Para cargar una fuente variable, define un FontFamily con la fuente ubicada en el directorio res/font/:

    // In Typography.kt
    @OptIn(ExperimentalTextApi::class)
    val displayLargeFontFamily =
        FontFamily(
            Font(
                R.font.robotoflex_variable,
                variationSettings = FontVariation.Settings(
                    FontVariation.weight(950),
                    FontVariation.width(30f),
                    FontVariation.slant(-6f),
                )
            )
        )

    La API de FontVariation te permite configurar ejes de fuente estándar, como grosor, ancho e inclinación. Estos son ejes estándar que están disponibles con cualquier fuente variable. Puedes crear diferentes configuraciones de la fuente según dónde se usará.

  3. Las fuentes variables solo están disponibles para Android O y versiones posteriores, por lo que debes agregar una barrera de seguridad y configurar un resguardo adecuado:

    // In Typography.kt
    val default = FontFamily(
        /*
        * This can be any font that makes sense
        */
        Font(
            R.font.robotoflex_static_regular
        )
    )
    @OptIn(ExperimentalTextApi::class)
    val displayLargeFontFamily = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        FontFamily(
            Font(
                R.font.robotoflex_variable,
                variationSettings = FontVariation.Settings(
                    FontVariation.weight(950),
                    FontVariation.width(30f),
                    FontVariation.slant(-6f),
                )
            )
        )
    } else {
        default
    }

  4. Extrae la configuración en un conjunto de constantes para facilitar la reutilización y reemplaza la configuración de la fuente con estas constantes:

    // VariableFontDimension.kt
    object DisplayLargeVFConfig {
        const val WEIGHT = 950
        const val WIDTH = 30f
        const val SLANT = -6f
        const val ASCENDER_HEIGHT = 800f
        const val COUNTER_WIDTH = 500
    }
    
    @OptIn(ExperimentalTextApi::class)
    val displayLargeFontFamily = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        FontFamily(
            Font(
                R.font.robotoflex_variable,
                variationSettings = FontVariation.Settings(
                    FontVariation.weight(DisplayLargeVFConfig.WEIGHT),
                    FontVariation.width(DisplayLargeVFConfig.WIDTH),
                    FontVariation.slant(DisplayLargeVFConfig.SLANT),
                )
            )
        )
    } else {
        default
    }

  5. Configura la tipografía de Material Design 3 para usar FontFamily:

    // Type.kt
    val Typography = Typography(
        displayLarge = TextStyle(
            fontFamily = displayLargeFontFamily,
            fontSize = 50.sp,
            lineHeight = 64.sp,
            letterSpacing = 0.sp,
            /***/
        )
    )

    En este ejemplo, se usa la tipografía de Material 3 displayLarge, que tiene diferentes configuraciones predeterminadas y usos recomendados. Por ejemplo, debes usar displayLarge para texto corto y crítico, ya que es el texto más grande de la pantalla.

    Con Material 3, puedes cambiar los valores predeterminados de TextStyle y fontFamily para personalizar la tipografía. En el fragmento anterior, se pueden configurar instancias de TextStyle para personalizar la configuración de fuentes de cada familia.

  6. Ahora que definiste la tipografía, pásala al MaterialTheme de M3:

    MaterialTheme(
        colorScheme = MaterialTheme.colorScheme,
        typography = Typography,
        content = content
    )

  7. Por último, usa un elemento Text componible y especifica el estilo en uno de los estilos de tipografía definidos, MaterialTheme.typography.displayLarge:

    @Composable
    @Preview
    fun CardDetails() {
        MyCustomTheme {
            Card(
                shape = RoundedCornerShape(8.dp),
                elevation = CardDefaults.cardElevation(defaultElevation = 4.dp),
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(16.dp)
            ) {
                Column(
                    modifier = Modifier.padding(16.dp)
                ) {
                    Text(
                        text = "Compose",
                        style = MaterialTheme.typography.displayLarge,
                        modifier = Modifier.padding(bottom = 8.dp),
                        maxLines = 1
                    )
                    Text(
                        text = "Beautiful UIs on Android",
                        style = MaterialTheme.typography.headlineMedium,
                        modifier = Modifier.padding(bottom = 8.dp),
                        maxLines = 2
                    )
                    Text(
                        text = "Jetpack Compose is Android’s recommended modern toolkit for building native UI. It simplifies and accelerates UI development on Android. Quickly bring your app to life with less code, powerful tools, and intuitive Kotlin APIs.",
                        style = MaterialTheme.typography.bodyLarge,
                        modifier = Modifier.padding(bottom = 8.dp),
                        maxLines = 3
                    )
                }
            }
        }
    }

    Cada elemento Text componible se configura con el estilo de su tema de Material y contiene una configuración de fuente variable diferente. Puedes usar MaterialTheme.typography para recuperar la tipografía proporcionada al elemento componible MaterialTheme de M3.

Tres textos diferentes, todos con diferentes configuraciones de fuente.
Figura 2: Fuente variable aplicada en tres configuraciones diferentes.

Usar ejes personalizados

Las fuentes también pueden tener ejes personalizados. Estas se definen dentro del propio archivo de fuente. Por ejemplo, la fuente Roboto Flex tiene el eje de altura ascendente ("YTAS"), que ajusta la altura de los ascendentes en minúscula, mientras que el ancho del contador ("XTRA") ajusta el ancho de cada letra.

Puedes cambiar el valor de estos ejes con la configuración de FontVariation.

Si deseas obtener más información sobre los ejes personalizados que puedes configurar para una fuente, consulta la tabla de ejes compatibles para cada fuente.

  1. Si deseas usar ejes personalizados, define funciones para los ejes ascenderHeight y counterWidth personalizados:

    fun ascenderHeight(ascenderHeight: Float): FontVariation.Setting {
        require(ascenderHeight in 649f..854f) { "'Ascender Height' must be in 649f..854f" }
        return FontVariation.Setting("YTAS", ascenderHeight)
    }
    
    fun counterWidth(counterWidth: Int): FontVariation.Setting {
        require(counterWidth in 323..603) { "'Counter width' must be in 323..603" }
        return FontVariation.Setting("XTRA", counterWidth.toFloat())
    }

    Estas funciones hacen lo siguiente:

    • Definir protecciones para los valores que pueden aceptar Como puedes ver en el catálogo de fuentes variables, ascenderHeight (YTAS) tiene un valor mínimo de 649f y un máximo de 854f.
    • Muestra el parámetro de configuración de la fuente, de modo que la configuración esté lista para agregarse a la fuente. En el método FontVariation.Setting(), el nombre del eje (YTAS, XTRA) está codificado y toma el valor como parámetro.
  2. Usa los ejes con la configuración de la fuente, pasa parámetros adicionales a cada Font que se cargue:

    @OptIn(ExperimentalTextApi::class)
    val displayLargeFontFamily = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        FontFamily(
            Font(
                R.font.robotoflex_variable,
                variationSettings = FontVariation.Settings(
                    FontVariation.weight(DisplayLargeVFConfig.WEIGHT),
                    FontVariation.width(DisplayLargeVFConfig.WIDTH),
                    FontVariation.slant(DisplayLargeVFConfig.SLANT),
                    ascenderHeight(DisplayLargeVFConfig.ASCENDER_HEIGHT),
                    counterWidth(DisplayLargeVFConfig.COUNTER_WIDTH)
                )
            )
        )
    } else {
        default
    }

    Observa que la altura de los ascendentes en minúscula ahora aumenta y el resto del texto es más ancho:

Tres textos diferentes que muestran distintas configuraciones de fuentes variables, con ejes personalizados establecidos; algunos tienen ascendentes más altos en minúscula y son más anchos que antes.
Figura 3: Texto que muestra ejes personalizados establecidos en fuentes variables.

Recursos adicionales

Para obtener más información, consulta la siguiente entrada de blog sobre las fuentes variables: