[Compose] Design System 구축하기 - 1
Custom Design System Module를 구축하기 전에 Compose 내 테마 분석을 먼저 진행해보려고 합니다. Jetpack Compose의 테마는 여러 개의 하위 수준 구성과 관련 API로 이루어져 있습니다. 이러한 요소는 MaterialT
moondev03.tistory.com
이전 글에 이어서 이번에는 Compose의 맞춤 디자인 시스템에 대해 작성하려고 합니다.
Material Design은 구글이 추천하는 안드로이드 디자인 시스템이며, Jetpack Compose는 Material Design의 구현을 제공합니다. 하지만, 이 시스템을 반드시 사용해야 하는 것은 아닙니다.
Material은 완전히 공개된 API를 기반으로 만들어졌기 때문에, 유사한 방식으로 자체 디자인 시스템을 만들 수 있습니다. 다음은 기본 Material 시스템을 커스터마이즈 하거나 대체하는 몇 가지 접근 방식입니다:
- MaterialTheme 확장 - 추가 테마 설정 값 사용
- 하나 이상의 Material 시스템(Colors, Typography 또는 Shapes)을 맞춤 구현으로 바꾸고 나머지는 유지
- 완전한 맞춤 디자인 시스템을 구현하여 MaterialTheme 대체
또한, 맞춤 디자인과 함께 Material 구성요소를 계속 사용할 수도 있습니다.
Material Theme 확장
Compose의 Material 라이브러리는 Material Theming을 면밀하게 모델링하므로, Material 가이드라인을 간단하고 유형 안전성 있게 따를 수 있습니다. 그러나 색상, 서체, 도형 등을 추가하여 확장할 수 있습니다.
확장 속성 추가하기
MaterialTheme에 커스텀 속성을 추가하는 가장 간단한 방법입니다.
예를 들어, 추가 색상이나 텍스트 스타일을 정의할 수 있습니다.
// MaterialTheme.colorScheme.snackbarAction 사용
val ColorScheme.snackbarAction: Color
@Composable
get() = if (isSystemInDarkTheme()) Red300 else Red700
// MaterialTheme.typography.textFieldInput 사용
val Typography.textFieldInput: TextStyle
get() = TextStyle(/* ... */)
// MaterialTheme.shapes.card 사용
val Shapes.card: Shape
get() = RoundedCornerShape(size = 20.dp)
이 방법은 MaterialTheme 사용 API와 일관성을 유지할 수 있지만,
단순한 테마 설정 값 추가나 서로 다른 테마의 동일한 값에만 권장됩니다.
기존 요소 래핑하기
새 속성을 추가하는 대신, 기존 Material 색상 등을 유지하면서 추가적인 속성을 래핑하는 확장 테마를 정의할 수 있습니다.
예를 들어, 기존 Material 색상과 함께 두 가지 색상(caution, onCaution)을 추가하는 방법은 다음과 같습니다.
@Immutable
data class ExtendedColors(
val caution: Color,
val onCaution: Color
)
val LocalExtendedColors = staticCompositionLocalOf {
ExtendedColors(
caution = Color.Unspecified,
onCaution = Color.Unspecified
)
}
@Composable
fun ExtendedTheme(
/* 필요한 테마 설정 */
content: @Composable () -> Unit
) {
val extendedColors = ExtendedColors(
caution = Color(0xFFFFCC02),
onCaution = Color(0xFF2C2D30)
)
CompositionLocalProvider(LocalExtendedColors provides extendedColors) {
MaterialTheme(
/* colors = ..., typography = ..., shapes = ... */
content = content
)
}
}
// 사용 예: ExtendedTheme.colors.caution
object ExtendedTheme {
val colors: ExtendedColors
@Composable
get() = LocalExtendedColors.current
}
이 방법은 MaterialTheme과 비슷하게 ExtendedThemes를 중첩하여 여러 테마를 지원할 수 있습니다.
Material 구성요소 사용
Material Theming을 확장할 때 기존 MaterialTheme 값은 유지되고 Material 구성요소는 계속 적합한 기본값을 가집니다. 확장된 값을 사용하려면 직접 래핑 하여 적용합니다.
@Composable
fun ExtendedButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
content: @Composable RowScope.() -> Unit
) {
Button(
colors = ButtonDefaults.buttonColors(
containerColor = ExtendedTheme.colors.caution,
contentColor = ExtendedTheme.colors.onCaution
/* 나머지 색상은 MaterialTheme의 값을 사용 */
),
onClick = onClick,
modifier = modifier,
content = content
)
}
@Composable
fun ExtendedApp() {
ExtendedTheme {
/*...*/
ExtendedButton(onClick = { /* ... */ }) {
/* ... */
}
}
}
Material 하위 시스템 교체
MaterialTheme을 확장하는 대신, 하나 이상의 시스템(Colors, Typography, Shapes)을 맞춤 구현으로 대체하고 나머지는 유지할 수 있습니다.
예를 들어, 색상은 그대로 두고 Typography와 Shapes 시스템을 대체하는 경우입니다.
@Immutable
data class ReplacementTypography(
val body: TextStyle,
val title: TextStyle
)
@Immutable
data class ReplacementShapes(
val component: Shape,
val surface: Shape
)
val LocalReplacementTypography = staticCompositionLocalOf {
ReplacementTypography(
body = TextStyle.Default,
title = TextStyle.Default
)
}
val LocalReplacementShapes = staticCompositionLocalOf {
ReplacementShapes(
component = RoundedCornerShape(ZeroCornerSize),
surface = RoundedCornerShape(ZeroCornerSize)
)
}
@Composable
fun ReplacementTheme(
/* 필요한 테마 설정 */
content: @Composable () -> Unit
) {
val replacementTypography = ReplacementTypography(
body = TextStyle(fontSize = 16.sp),
title = TextStyle(fontSize = 32.sp)
)
val replacementShapes = ReplacementShapes(
component = RoundedCornerShape(percent = 50),
surface = RoundedCornerShape(size = 40.dp)
)
CompositionLocalProvider(
LocalReplacementTypography provides replacementTypography,
LocalReplacementShapes provides replacementShapes
) {
MaterialTheme(
/* colors = ... */
content = content
)
}
}
// 사용 예: ReplacementTheme.typography.body
object ReplacementTheme {
val typography: ReplacementTypography
@Composable
get() = LocalReplacementTypography.current
val shapes: ReplacementShapes
@Composable
get() = LocalReplacementShapes.current
}
Material 구성요소 사용
하나 이상의 MaterialTheme 시스템이 대체되었을 때, Material 구성요소를 그대로 사용하면 원치 않는 Material 색상, 유형, 도형 값이 생길 수 있습니다.
이 경우, 구성요소에 맞춤 값을 사용하려면 값을 직접 래핑하고 필요한 매개변수를 설정합니다.
일부 값은 Material Composable에서 매개변수로 노출되지 않을 수 있어, ProvideTextStyle 같은 래핑이 필요할 수 있습니다.
@Composable
fun ReplacementButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
content: @Composable RowScope.() -> Unit
) {
Button(
shape = ReplacementTheme.shapes.component,
onClick = onClick,
modifier = modifier,
content = {
ProvideTextStyle(
value = ReplacementTheme.typography.body
) {
content()
}
}
)
}
@Composable
fun ReplacementApp() {
ReplacementTheme {
/*...*/
ReplacementButton(onClick = { /* ... */ }) {
/* ... */
}
}
}
완전한 맞춤 디자인 시스템 구현
Material Theming을 완전히 맞춤화된 디자인 시스템으로 대체할 수 있습니다.
MaterialTheme은 색상, 서체, 도형, 텍스트 선택 색상, 리플 및 리플 테마를 제공하므로, 맞춤 테마에서 시스템의 일부를 대체하거나 구성 요소의 시스템을 처리해야 합니다.
디자인 시스템은 Material의 개념에 국한되지 않으며, 기존 시스템을 수정하고 새로운 클래스와 유형으로 완전히 새로운 시스템을 도입할 수 있습니다.
다음은 Gradient(List<Color>)가 포함된 맞춤 색상 시스템을 모델링하고, Typography와 새 고도 시스템을 포함하며, MaterialTheme에서 제공하는 다른 시스템을 제외하는 예제입니다.
@Immutable
data class CustomColors(
val content: Color,
val component: Color,
val background: List<Color>
)
@Immutable
data class CustomTypography(
val body: TextStyle,
val title: TextStyle
)
@Immutable
data class CustomElevation(
val default: Dp,
val pressed: Dp
)
val LocalCustomColors = staticCompositionLocalOf {
CustomColors(
content = Color.Unspecified,
component = Color.Unspecified,
background = emptyList()
)
}
val LocalCustomTypography = staticCompositionLocalOf {
CustomTypography(
body = TextStyle.Default,
title = TextStyle.Default
)
}
val LocalCustomElevation = staticCompositionLocalOf {
CustomElevation(
default = Dp.Unspecified,
pressed = Dp.Unspecified
)
}
@Composable
fun CustomTheme(
/* 필요한 테마 설정 */
content: @Composable () -> Unit
) {
val customColors = CustomColors(
content = Color(0xFFDD0D3C),
component = Color(0xFFC20029),
background = listOf(Color.White, Color(0xFFF8BBD0))
)
val customTypography = CustomTypography(
body = TextStyle(fontSize = 16.sp),
title = TextStyle(fontSize = 32.sp)
)
val customElevation = CustomElevation(
default = 4.dp,
pressed = 8.dp
)
CompositionLocalProvider(
LocalCustomColors provides customColors,
LocalCustomTypography provides customTypography,
LocalCustomElevation provides customElevation,
content = content
)
}
object CustomTheme {
val colors: CustomColors
@Composable
get() = LocalCustomColors.current
val typography: CustomTypography
@Composable
get() = LocalCustomTypography.current
val elevation: CustomElevation
@Composable
get() = LocalCustomElevation.current
}
Material 구성요소 사용
MaterialTheme이 없을 때 Material 구성요소를 그대로 사용하면 원치 않는 Material 색상, 유형, 도형 값과 표시 동작이 발생합니다.
구성요소에서 맞춤 값을 사용하려면 자체 컴포저블로 래핑 합니다.
관련 시스템에 대한 값을 직접 설정하고 기타를 포함하는 컴포저블에 매개변수로 사용합니다.
사용자 정의 테마에서 설정한 값에 액세스하는 것이 좋습니다.
또는 테마가 Color, TextStyle, Shape 또는 기타 시스템을 제공하지 않는 경우 하드코딩할 수 있습니다.
@Composable
fun CustomButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
content: @Composable RowScope.() -> Unit
) {
Button(
colors = ButtonDefaults.buttonColors(
containerColor = CustomTheme.colors.component,
contentColor = CustomTheme.colors.content,
disabledContainerColor = CustomTheme.colors.content
.copy(alpha = 0.12f)
.compositeOver(CustomTheme.colors.component),
disabledContentColor = CustomTheme.colors.content
.copy(alpha = 0.38f)
),
shape = ButtonShape,
elevation = ButtonDefaults.elevatedButtonElevation(
defaultElevation = CustomTheme.elevation.default,
pressedElevation = CustomTheme.elevation.pressed
/* disabledElevation = 0.dp */
),
onClick = onClick,
modifier = modifier,
content = {
ProvideTextStyle(
value = CustomTheme.typography.body
) {
content()
}
}
)
}
val ButtonShape = RoundedCornerShape(percent = 50)
버튼은 내부적으로 RememberRipple()을 사용하여 잔물결 표시를 제공합니다.
기존 구성 요소를 래핑하는 다른 사용자 정의 구성 요소를 구현할 때 소스 코드를 확인하는 것이 좋습니다.
이어서 다음 편에서는 간단한 Custom Design System Module를 구축하는 예제를 작성해보겠습니다.
[Compose] Design System 구축하기 - 3
[Compose] Design System 구축하기 - 2[Compose] Design System 구축하기 - 1Custom Design System Module를 구축하기 전에 Compose 내 테마 분석을 먼저 진행해보려고 합니다. Jetpack Compose의 테마는 여러 개의 하위 수준
moondev03.tistory.com
Reference
Compose의 맞춤 디자인 시스템 | Jetpack Compose | Android Developers
이 페이지는 Cloud Translation API를 통해 번역되었습니다. Compose의 맞춤 디자인 시스템 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. Material은 권장되는 디자인
developer.android.com
'[Android - Compose] > Compose Theme' 카테고리의 다른 글
[Compose] Design System 구축하기 - 3 (0) | 2024.09.14 |
---|---|
[Compose] Design System 구축하기 - 1 (0) | 2024.09.14 |