글꼴 작업

이 페이지에서는 Compose 앱에서 글꼴을 설정하는 방법을 설명합니다.

글꼴 설정

TextfontFamily 매개변수를 사용하면 컴포저블에 사용되는 글꼴을 설정할 수 있습니다. 기본적으로 Serif, Sans Serif, 고정폭 및 필기체 글꼴 모음이 포함됩니다.

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

단어

fontFamily 속성을 사용하여 res/font 폴더에 정의된 맞춤 글꼴 및 서체로 작업할 수 있습니다.

개발 환경 내 res > font 폴더의 그래픽 표현

이 예에서는 이러한 글꼴 파일을 기반으로 Font 함수를 사용하여 fontFamily를 정의하는 방법을 보여줍니다.

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)
)

fontFamilyText 컴포저블에 전달할 수 있습니다. fontFamily에는 다양한 가중치가 포함될 수 있으므로 fontWeight를 수동으로 설정하여 텍스트에 적합한 가중치를 선택할 수 있습니다.

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)
}

단어

앱 전체에서 서체를 설정하는 방법을 알아보려면 Compose의 맞춤 디자인 시스템을 참고하세요.

다운로드 가능한 글꼴

Compose 1.2.0부터 Compose 앱의 다운로드 가능한 글꼴 API를 사용하여 Google 글꼴을 비동기식으로 다운로드하고 앱에서 사용할 수 있습니다.

맞춤 제공업체에서 제공하는 다운로드 가능한 글꼴은 현재 지원되지 않습니다.

프로그래매틱 방식으로 다운로드 가능한 글꼴 사용

앱 내에서 프로그래매틱 방식으로 글꼴을 다운로드하려면 다음 단계를 따르세요.

  1. 종속 항목을 추가합니다.

    Groovy

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

    Kotlin

    dependencies {
        ...
        implementation("androidx.compose.ui:ui-text-google-fonts:1.7.5")
    }
  2. Google Fonts의 사용자 인증 정보로 GoogleFont.Provider를 초기화합니다.
    val provider = GoogleFont.Provider(
        providerAuthority = "com.google.android.gms.fonts",
        providerPackage = "com.google.android.gms",
        certificates = R.array.com_google_android_gms_fonts_certs
    )
    제공업체가 수신하는 매개변수는 다음과 같습니다.
    • Google Fonts 글꼴 제공업체 권한
    • 제공업체의 ID를 확인하기 위한 글꼴 제공업체 패키지
    • 제공업체의 ID를 확인하는 인증서의 해시 세트 목록 Google Fonts 제공업체에 필요한 해시는 Jetchat 샘플 앱의 font_certs.xml 파일에서 확인할 수 있습니다.
  3. 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)
    )
    FontWeight, FontStyle을 사용하면 글꼴 및 두께 등 글꼴의 다른 매개변수를 쿼리할 수 있습니다.
    // ...
     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. Text 구성 가능한 함수에서 FontFamily가 사용되도록 구성합니다.

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

FontFamily를 사용하도록 서체를 정의할 수도 있습니다.

val MyTypography = Typography(
    bodyMedium = TextStyle(
        fontFamily = fontFamily, fontWeight = FontWeight.Normal, fontSize = 12.sp/*...*/
    ),
    bodyLarge = TextStyle(
        fontFamily = fontFamily,
        fontWeight = FontWeight.Bold,
        letterSpacing = 2.sp,
        /*...*/
    ),
    headlineMedium = TextStyle(
        fontFamily = fontFamily, fontWeight = FontWeight.SemiBold/*...*/
    ),
    /*...*/
)

다음으로 서체를 앱의 테마로 설정합니다.

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

Material3과 함께 Compose에서 다운로드 가능한 글꼴을 구현하는 앱의 예는 Jetchat 샘플 앱을 참고하세요.

대체 글꼴 추가

글꼴이 제대로 다운로드되지 않는 경우를 대비해 글꼴의 대체 체인을 결정할 수 있습니다. 예를 들어 다운로드 가능한 글꼴이 다음과 같이 정의되어 있다고 가정해 보겠습니다.

// ...
 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)
)

두 가중치의 글꼴 기본값을 다음과 같이 정의할 수 있습니다.

// ...
 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)
)

올바른 가져오기를 추가했는지 확인하세요.

FontFamily를 이렇게 정의하면 가중치당 하나씩 두 개의 체인이 포함된 FontFamily가 생성됩니다. 로드 메커니즘은 먼저 온라인 글꼴을 결정하려고 시도한 다음, 로컬 R.font 리소스 폴더에 있는 글꼴을 결정하려고 합니다.

구현 디버깅

글꼴이 올바르게 다운로드되고 있는지 확인하려면 디버그 코루틴 핸들러를 정의하세요. 핸들은 글꼴이 비동기적으로 로드되지 않는 경우 실행할 작업을 제공합니다.

먼저 CoroutineExceptionHandler를 만듭니다.

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

리졸버가 새 핸들러를 사용하도록 createFontFamilyResolver 메서드에 전달합니다.

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

제공업체의 isAvailableOnDevice API를 사용하여 제공업체를 사용할 수 있는지, 인증서가 올바르게 구성되었는지 테스트할 수도 있습니다. 이렇게 하려면 제공업체가 잘못 구성되었을 때 false를 반환하는 isAvailableOnDevice 메서드를 호출하면 됩니다.

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

주의사항

Google Fonts가 Android에서 새 글꼴을 제공하려면 몇 개월 정도 걸립니다. 글꼴이 fonts.google.com에 추가되는 시점과 다운로드 가능한 글꼴 API를 통해 (뷰 시스템 또는 Compose에서) 제공되는 시점 사이에 차이가 있습니다. IllegalStateException를 사용하면 새로 추가된 글꼴이 앱에 로드되지 않을 수 있습니다. 개발자가 다른 유형의 글꼴 로드 오류보다 이 오류를 식별할 수 있도록 Compose에 예외에 관한 설명 메시지를 추가했습니다. 변경사항은 여기에서 확인하세요. 문제가 발견되면 Issue Tracker를 사용하여 신고하세요.

가변 글꼴 사용

가변 글꼴은 하나의 글꼴 파일에 여러 스타일을 포함할 수 있는 글꼴 형식입니다. 가변 글꼴을 사용하면 축 (또는 매개변수)을 수정하여 원하는 스타일을 생성할 수 있습니다. 이러한 축은 두께, 너비, 기울기, 기울임꼴과 같은 표준이거나 가변 글꼴마다 다른 맞춤일 수 있습니다.

축 값이 다른 동일한 가변 글꼴의 5가지 구성
그림 1. 서로 다른 축 값으로 맞춤설정된 동일한 가변 글꼴을 사용하는 텍스트

일반 글꼴 파일 대신 가변 글꼴을 사용하면 글꼴 파일 여러 개가 아닌 하나만 사용할 수 있습니다.

가변 글꼴에 관한 자세한 내용은 Google Fonts 지식, 사용 가능한 가변 글꼴의 전체 카탈로그, 각 글꼴에 지원되는 축 를 참고하세요.

이 문서에서는 Compose 앱에서 가변 글꼴을 구현하는 방법을 보여줍니다.

가변 글꼴 로드

  1. 사용하려는 가변 글꼴 (예: Roboto Flex)을 다운로드하여 앱의 app/res/font 폴더에 배치합니다.추가하는 ttf 파일은 글꼴의 가변 글꼴 버전이며 글꼴 파일의 이름은 모두 소문자이고 특수문자를 포함하지 않습니다.

  2. 가변 글꼴을 로드하려면 res/font/ 디렉터리에 있는 글꼴을 사용하여 FontFamily를 정의합니다.

    // 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),
                )
            )
        )

    FontVariation API를 사용하면 weight, width, slant와 같은 표준 글꼴 축을 구성할 수 있습니다. 이는 모든 가변 글꼴에서 사용할 수 있는 표준 축입니다. 글꼴이 사용되는 위치에 따라 다양한 글꼴 구성을 만들 수 있습니다.

  3. 변수 글꼴은 Android 버전 O 이상에서만 사용할 수 있으므로 가드레일을 추가하고 적절한 대체를 구성합니다.

    // 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. 더 쉽게 재사용할 수 있도록 설정을 상수 집합으로 추출하고 글꼴 설정을 다음 상수로 바꿉니다.

    // 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. FontFamily를 사용하도록 Material Design 3 서체를 구성합니다.

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

    이 샘플은 기본 글꼴 설정과 권장 사용 사례가 다른 displayLarge Material 3 서체를 사용합니다. 예를 들어 displayLarge는 화면에서 가장 큰 텍스트이므로 짧고 중요한 텍스트에 사용해야 합니다.

    Material 3을 사용하면 TextStylefontFamily의 기본값을 변경하여 서체를 맞춤설정할 수 있습니다. 위 스니펫에서는 TextStyle 인스턴스를 구성하여 각 글꼴 모음의 글꼴 설정을 맞춤설정합니다.

  6. 서체를 정의했으므로 이제 M3 MaterialTheme에 전달합니다.

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

  7. 마지막으로 Text 컴포저블을 사용하고 정의된 서체 스타일 중 하나인 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
                    )
                }
            }
        }
    }

    Text 컴포저블은 Material 테마의 스타일을 통해 구성되며 서로 다른 가변 글꼴 구성을 포함합니다. MaterialTheme.typography를 사용하여 M3 MaterialTheme 컴포저블에 제공된 서체를 검색할 수 있습니다.

모두 다른 글꼴 구성을 보여주는 세 가지 텍스트
그림 2. 세 가지 구성으로 적용된 가변 글꼴

맞춤 축 사용

글꼴에 맞춤 축이 있을 수도 있습니다. 글꼴 파일 자체에서 정의됩니다. 예를 들어 Roboto Flex 글꼴에는 소문자 어센더의 높이를 조정하는 어센더 높이 ("YTAS") 축이 있고, 카운터 너비 ("XTRA")는 각 문자의 너비를 조정합니다.

FontVariation 설정을 사용하여 이러한 축의 값을 변경할 수 있습니다.

글꼴에 대해 구성할 수 있는 맞춤 축에 관한 자세한 내용은 각 글꼴의 지원되는 축 표를 참고하세요.

  1. 맞춤 축을 사용하려면 맞춤 ascenderHeight 축과 counterWidth 축에 대한 함수를 정의합니다.

    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())
    }

    이러한 함수는 다음을 실행합니다.

    • 허용 가능한 값에 대한 가이드라인을 정의합니다. 가변 글꼴 카탈로그에서 볼 수 있듯이 ascenderHeight (YTAS)의 최솟값은 649f이고 최댓값은 854f입니다.
    • 구성을 글꼴에 추가할 준비가 되도록 글꼴 설정을 반환합니다. FontVariation.Setting() 메서드에서 축 이름 (YTAS, XTRA)은 하드코딩되고 값을 매개변수로 사용합니다.
  2. 글꼴 구성과 함께 축을 사용하여 로드된 각 Font에 추가 매개변수를 전달합니다.

    @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
    }

    이제 소문자 어센더의 높이가 증가하고 다른 텍스트가 더 넓어졌습니다.

맞춤 축이 설정된 가변 글꼴의 다양한 구성을 보여주는 세 가지 텍스트. 일부 텍스트에서는 소문자 어센더가 더 높고 이전보다 폭이 넓어졌습니다.
그림 3. 가변 글꼴에 설정된 맞춤 축을 보여주는 텍스트

추가 리소스

자세한 내용은 다음의 가변 글꼴에 관한 블로그 게시물을 참고하세요.