FontFamily
커스텀 폰트를 사용하려면 FontFamily를 만들어 Text를 사용할 때마다 fontFamily 속성을 설정해 주어야 하는 번거로움이 있다. 이를 해결하기 위해 CompositionLocalProvider를 사용해 Default FontFamily를 설정하여 Text 사용 시 별도로 fontFamily를 설정하지 않아도 되는 방법을 소개한다.
CompositionLocalProvider
@Composable
@OptIn(InternalComposeApi::class)
fun CompositionLocalProvider(value: ProvidedValue<*>, content: @Composable () -> Unit) {
currentComposer.startProvider(value)
content()
currentComposer.endProvider()
}
"일반적으로 Compose에서 데이터는 각 구성 가능한 함수의 매개변수로 UI 트리를 통해 아래로 흐릅니다. 따라서 컴포저블의 종속 항목이 명시적으로 됩니다. 그러나 이 방법은 색상이나 유형 스타일과 같이 매우 자주 널리 사용되는 데이터의 경우에는 번거로울 수 있습니다."
라고 설명한다. 요약하자면 자주 사용하는 데이터는 매번 명시적으로 컴포저블에 전달하기 번거롭기 때문에 CompositionLocalProvider를 사용해 암시적으로 사용할 수 있다라는 말이다.
MaterialTheme
@Composable
fun MaterialTheme(
colorScheme: ColorScheme = MaterialTheme.colorScheme,
shapes: Shapes = MaterialTheme.shapes,
typography: Typography = MaterialTheme.typography,
content: @Composable () -> Unit
) {
val rippleIndication = androidx.compose.material.ripple.rememberRipple()
val selectionColors = rememberTextSelectionColors(colorScheme)
CompositionLocalProvider(
LocalColorScheme provides colorScheme,
LocalIndication provides rippleIndication,
androidx.compose.material.ripple.LocalRippleTheme provides MaterialRippleTheme,
LocalShapes provides shapes,
LocalTextSelectionColors provides selectionColors,
LocalTypography provides typography,
) {
ProvideTextStyle(value = typography.bodyLarge, content = content)
}
}
CompositionLocalProvider를 사용하는 널리 알려진 예시로 MaterialTheme가 있다.
MaterialTheme는 색상, 서체, 도형을 설정 후 하위 부분에서 세 가지의 인스턴트를 제공하는 객체로 MaterialTheme 코드를 확인해보면 CompositionLocalProvider를 사용해 colorScheme, rippleIndication, shapes, typograpy 등 자주 사용되는 데이터를 설정하고 있다.
따라서 fontFamily도 자주 사용한다면 같은 방법으로 설정할 수 있는 것이다.
ProvideTextStyle
@Composable
fun ProvideTextStyle(value: TextStyle, content: @Composable () -> Unit) {
val mergedStyle = LocalTextStyle.current.merge(value)
CompositionLocalProvider(LocalTextStyle provides mergedStyle, content = content)
}
직접 CompositionLocalProvider를 사용해 LocalTextStyle을 설정해도 되지만 인라인 함수가 아닌 textStyle을 설정하는 함수가 존재한다. 따라서 이 함수를 사용해서 LocalTextStyle을 설정하는 방법을 설명한다.
설정 방법
// Theme.kt
@Composable
fun AppTheme(
content: @Composable () -> Unit
) {
MaterialTheme(
typography = typography // 하단 설명 참고
) {
ProvideTextStyle(
value = TextStyle(
fontFamily = pretendard
)
) {
content()
}
}
}
// MainActivity.kt
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
ReReminderTheme { MainNavigation() }
}
}
}
Theme.kt에서 theme를 설정할 때 ProvideTextStyle 함수를 호출해 기본 fontFamily를 설정해준다. pretendard는 예시일 뿐 본인이 사용할 fontFamily를 입력해주면 된다. 이 후 메인 액티비티에서 theme를 적용시킨다.
하지만 Button이나 AppBar 등 일부 컴포넌트 내부에서 Text를 사용할 시 fontFamily가 적용이 안되는 경우가 있다. 그 이유는 어떤 컴포넌트 내부에서는 ProvideTextStyle 함수를 새롭게 호출해 fontFamily를 설정하는데 이때 MaterialTheme.typography의 값을 사용해 설정하기 때문이다.
이를 해결하는 방법은 MaterialTheme.typography에도 fontFamily를 적용시키면 된다. 이 때 약간의 문제가 있는데 Material3의 Typography 자체는 참고해서 사용할 수 있지만 내부에서 값을 설정하는 토큰은 internal로 되어있어 참고해서 사용할 수 없다. 그래서 토큰 값을 직접 베껴서 똑같이 설정했으므로 알아서 수정해서 사용하도록 한다.
val DefaultTextStyle = TextStyle.Default.copy(
fontFamily = pretendard,
fontWeight = FontWeight.Normal,
lineHeightStyle = LineHeightStyle(
alignment = LineHeightStyle.Alignment.Center,
trim = LineHeightStyle.Trim.None
)
)
val typography = Typography(
bodyLarge = DefaultTextStyle.copy(
fontSize = 16.sp,
lineHeight = 24.sp,
letterSpacing = 0.5.sp
),
bodyMedium = DefaultTextStyle.copy(
fontSize = 14.sp,
lineHeight = 20.sp,
letterSpacing = 0.2.sp
),
bodySmall = DefaultTextStyle.copy(
fontSize = 12.sp,
lineHeight = 16.sp,
letterSpacing = 0.4.sp
),
displayLarge = DefaultTextStyle.copy(
fontSize = 57.sp,
lineHeight = 64.sp,
letterSpacing = (-0.2).sp
),
displayMedium = DefaultTextStyle.copy(
fontSize = 45.sp,
lineHeight = 52.sp,
letterSpacing = 0.0.sp
),
displaySmall = DefaultTextStyle.copy(
fontSize = 36.sp,
lineHeight = 44.sp,
letterSpacing = 0.0.sp
),
headlineLarge = DefaultTextStyle.copy(
fontSize = 32.sp,
lineHeight = 40.sp,
letterSpacing = 0.0.sp
),
headlineMedium = DefaultTextStyle.copy(
fontSize = 24.sp,
lineHeight = 32.sp,
letterSpacing = 0.0.sp
),
headlineSmall = DefaultTextStyle.copy(
fontSize = 36.sp,
lineHeight = 44.sp,
letterSpacing = 0.0.sp
),
labelLarge = DefaultTextStyle.copy(
fontWeight = FontWeight.Medium,
fontSize = 14.sp,
lineHeight = 20.sp,
letterSpacing = 0.1.sp
),
labelMedium = DefaultTextStyle.copy(
fontWeight = FontWeight.Medium,
fontSize = 12.sp,
lineHeight = 16.sp,
letterSpacing = 0.5.sp
),
labelSmall = DefaultTextStyle.copy(
fontWeight = FontWeight.Medium,
fontSize = 11.sp,
lineHeight = 16.sp,
letterSpacing = 0.5.sp
),
titleLarge = DefaultTextStyle.copy(
fontWeight = FontWeight.Medium,
fontSize = 22.sp,
lineHeight = 28.sp,
letterSpacing = 0.0.sp
),
titleMedium = DefaultTextStyle.copy(
fontSize = 16.sp,
lineHeight = 24.sp,
letterSpacing = 0.2.sp
),
titleSmall = DefaultTextStyle.copy(
fontWeight = FontWeight.Medium,
fontSize = 14.sp,
lineHeight = 20.sp,
letterSpacing = 0.1.sp
)
)
* 이렇게 해도 fontFamily가 적용되지 않는 BasicTextField 같은 컴포넌트가 있는데 내부 코드를 확인해보면 직접 입력된 textStyle이 없다면 컨스트럭터를 통해 fontFamily를 null로 설정하기 때문이다. 따라서 위의 코드를 사용했을 때 적용되지 않는 컴포넌트는 어쩔 수 없이 내부 코드를 확인 후 textStyle에 fontFamily를 직접 설정해 주어야 한다.
Reference
https://developer.android.com/develop/ui/compose/compositionlocal?hl=ko
CompositionLocal을 사용한 로컬 범위 지정 데이터 | Jetpack Compose | Android Developers
이 페이지는 Cloud Translation API를 통해 번역되었습니다. CompositionLocal을 사용한 로컬 범위 지정 데이터 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. Composition
developer.android.com