앱이 커짐에 따라 디자인 시스템의 필요성을 느끼게 되었다. 흔히 Toss Design System이란 말을 많이 들었는데, 이 부분이 생각의 출발점이었다. Design에 왜 System이란 말을 붙였을까? 그것은 아무래도 Design을 체계적으로 구축 및 System화하고 개발자는 기존 구축된 시스템을 단순히 사용함으로써 비즈니스 로직에 더욱 집중할 수 있기 때문이 아닐까 한다.
이에 맞춰 Compose를 사용하다보면 MatherialTheme()
라는 메서드를 볼 수 있다. 해당 메서드는 하위에 ColorScheme
, Shapes
, Typography
를 주입받을 수 있게 설계되어 있는걸 볼 수 있다.
package androidx.compose.material3
@Suppress("DEPRECATION_ERROR")
@Composable
fun MaterialTheme(
colorScheme: ColorScheme = MaterialTheme.colorScheme,
shapes: Shapes = MaterialTheme.shapes,
typography: Typography = MaterialTheme.typography,
content: @Composable () -> Unit
)
처음에는 이게 필요한 이유를 잘 몰랐지만, Design System이란 무엇인걸까 란 생각을 무의식적으로 계속 하다보니 이들을 통해 앱의 디자인을 일관적으로 꾸밀 수 있는 유용한 툴이란 것을 알게되었다. 이런 툴들은 아래와 같은 참조 방식으로 사용되곤 한다.
val materialShape = MaterialTheme.shapes
val materialColorScheme = MaterialTheme.colorScheme
val materialTypo = MaterialTheme.typography
또한 이들을 각각 참조하려할 때, MaterialTheme
에 주입한 각각의 객체 상태를 조회할 수 있다.
[Shapes 참조]
[ColorShapes 참조]
[Typography 참조]
또한 Google사가 제공하는 권장 아키텍처 앱인 Now In Android
에서도 MaterialTheme
객체에 아래와 같은 값을 주입시킴으로써 Design System을 적극 활용하고 있단것도 알 수 있다.
@Composable
fun NiaTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
androidTheme: Boolean = false,
disableDynamicTheming: Boolean = true,
content: @Composable () -> Unit,
) {
// Color scheme
val colorScheme = when {
androidTheme -> if (darkTheme) DarkAndroidColorScheme else LightAndroidColorScheme
!disableDynamicTheming && supportsDynamicTheming() -> {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
}
else -> if (darkTheme) DarkDefaultColorScheme else LightDefaultColorScheme
}
// Gradient colors
val emptyGradientColors = GradientColors(container = colorScheme.surfaceColorAtElevation(2.dp))
val defaultGradientColors = GradientColors(
top = colorScheme.inverseOnSurface,
bottom = colorScheme.primaryContainer,
container = colorScheme.surface,
)
val gradientColors = when {
androidTheme -> if (darkTheme) DarkAndroidGradientColors else LightAndroidGradientColors
!disableDynamicTheming && supportsDynamicTheming() -> emptyGradientColors
else -> defaultGradientColors
}
// Background theme
val defaultBackgroundTheme = BackgroundTheme(
color = colorScheme.surface,
tonalElevation = 2.dp,
)
val backgroundTheme = when {
androidTheme -> if (darkTheme) DarkAndroidBackgroundTheme else LightAndroidBackgroundTheme
else -> defaultBackgroundTheme
}
val tintTheme = when {
androidTheme -> TintTheme()
!disableDynamicTheming && supportsDynamicTheming() -> TintTheme(colorScheme.primary)
else -> TintTheme()
}
// Composition locals
CompositionLocalProvider(
LocalGradientColors provides gradientColors,
LocalBackgroundTheme provides backgroundTheme,
LocalTintTheme provides tintTheme,
) {
MaterialTheme(
colorScheme = colorScheme,
typography = NiaTypography,
content = content,
)
}
}
그리고 이에 잘 맞게도, Now In Android
에서는 동적 테마 변경 기능까지 제공한다. 해당 기능을 통해 3가지의 테마를 바꿀 수 있다. 그렇다면 이제 MaterialTheme
객체의 상태값을 설정하고자 할 때, 각각의 프로퍼티들이 어떤 의미가 있는지만 알면 된다. 그렇다면 앱 구축 시, Design System을 구축하고 그에 맞게 잘 활용할 수 있을 것이다.
추가로 Material Theme Builder를 사용해보면 알겠지만, Design System이 적용된 앱이 참 예쁘게 느껴지기도 한다.
참고 : https://m3.material.io/styles/color/roles
ColorScheme
내부에 정의된 프로퍼티들이 어떤 역할을 하는지 알기 위한 좋은 방법은
1. 안드로이드 공식 홈페이지
2. Material Design공식 홈페이지
가 있다. 해당 자료를 통해 Colorscheme
프로퍼티의 역할을 이해할 수 있다. 또한 색상을 보다보면 특정 키워드들(ex. Surface
, Primary
, Secondary
, Tertiary
, Container
, On
, Variant
)이 있다. 우선적으로 이 키워드들의 의미를 이해하면 ColorScheme를 좀 더 쉽게 이해할 수 있을거라 본다. (참고 : Material Color General Concepts)
Surface
: 배경의 크고 약한 강조를 위해 사용되는 색상이다.Priamry
: 강한 강조가 필요할 때 또는 앱 전체에서 가장 빈번하게 사용되는 컴포넌트에 사용되는 색상이다. (ex. FloatingActionButton 또는 Button 또는 활성 상태)Secondary
:Primary
색깔보다 덜 강조하려할 때 사용되는 색상이다. (ex. filter chip)Tertiary
:Primary
와Secondary
색상의 대비 강조 효과를 주고자할 때 사용되는 색상이다. 또는 InputField에도 쓰일 수 있다Container
: 버튼 등과 같이 내부 UI요소들을 채우기 위한 색상으로 사용된다. 이들은 텍스트 또는 아이콘 색상으로 사용되면 안된다.On
:OnPrimary
,OnSecondary
등과 같이 특정 색상의 접두사로, 해당 접두사의 부모 색상들(Primary
,Secondary
..)들 위쪽에 사용되는 아이콘과 텍스트 색상이다.Variant
:OnSurfaceVariant
,OutlineVariant
등과 같이 특정 색상의 접미사로, 해당 접미사의 부모 색상들(OnSurface
,OutlineVariant
)과 대비하여 약한 강조를 할때 사용되는 색상이다.
앱 내에선 글자에 다양한 크기, 폰트, 강조 효과 등을 적용한다. 이를 가능하게 하는게 바로 Typography이다. MatherialTheme
에선 아래와 같이 글자 크기를 설정할 수 있다.
class Typography(
val displayLarge: TextStyle = TypographyTokens.DisplayLarge,
val displayMedium: TextStyle = TypographyTokens.DisplayMedium,
val displaySmall: TextStyle = TypographyTokens.DisplaySmall,
val headlineLarge: TextStyle = TypographyTokens.HeadlineLarge,
val headlineMedium: TextStyle = TypographyTokens.HeadlineMedium,
val headlineSmall: TextStyle = TypographyTokens.HeadlineSmall,
val titleLarge: TextStyle = TypographyTokens.TitleLarge,
val titleMedium: TextStyle = TypographyTokens.TitleMedium,
val titleSmall: TextStyle = TypographyTokens.TitleSmall,
val bodyLarge: TextStyle = TypographyTokens.BodyLarge,
val bodyMedium: TextStyle = TypographyTokens.BodyMedium,
val bodySmall: TextStyle = TypographyTokens.BodySmall,
val labelLarge: TextStyle = TypographyTokens.LabelLarge,
val labelMedium: TextStyle = TypographyTokens.LabelMedium,
val labelSmall: TextStyle = TypographyTokens.LabelSmall,
)
위와 같이 각각의 경우에 해당하는 TextStyle
을 정의하고 꺼내쓰면 된다. 하지만 디자인 시스템이 체계화되어있지 않다면 이를 사용하는게 현실적으로 가능할까란 생각이 들기도 한다.
다만, Now In Android
에선 이를 사용하고 있다. 아무래도 구글팀이고, 안드로이드 권장 아키텍처 샘플앱인만큼 작업이 되어있는게 아닐까 한다.
[Google사, Now In Android]
internal val NiaTypography = Typography( displayLarge = TextStyle( fontWeight = FontWeight.Normal, fontSize = 57.sp, lineHeight = 64.sp, letterSpacing = (-0.25).sp, ), displayMedium = TextStyle( fontWeight = FontWeight.Normal, fontSize = 45.sp, lineHeight = 52.sp, letterSpacing = 0.sp, ), displaySmall = TextStyle( fontWeight = FontWeight.Normal, fontSize = 36.sp, lineHeight = 44.sp, letterSpacing = 0.sp, ), headlineLarge = TextStyle( fontWeight = FontWeight.Normal, fontSize = 32.sp, lineHeight = 40.sp, letterSpacing = 0.sp, ), headlineMedium = TextStyle( fontWeight = FontWeight.Normal, fontSize = 28.sp, lineHeight = 36.sp, letterSpacing = 0.sp, ), headlineSmall = TextStyle( fontWeight = FontWeight.Normal, fontSize = 24.sp, lineHeight = 32.sp, letterSpacing = 0.sp, lineHeightStyle = LineHeightStyle( alignment = Alignment.Bottom, trim = Trim.None, ), ), titleLarge = TextStyle( fontWeight = FontWeight.Bold, fontSize = 22.sp, lineHeight = 28.sp, letterSpacing = 0.sp, lineHeightStyle = LineHeightStyle( alignment = Alignment.Bottom, trim = Trim.LastLineBottom, ), ), titleMedium = TextStyle( fontWeight = FontWeight.Bold, fontSize = 18.sp, lineHeight = 24.sp, letterSpacing = 0.1.sp, ), titleSmall = TextStyle( fontWeight = FontWeight.Medium, fontSize = 14.sp, lineHeight = 20.sp, letterSpacing = 0.1.sp, ), // Default text style bodyLarge = TextStyle( fontWeight = FontWeight.Normal, fontSize = 16.sp, lineHeight = 24.sp, letterSpacing = 0.5.sp, lineHeightStyle = LineHeightStyle( alignment = Alignment.Center, trim = Trim.None, ), ), bodyMedium = TextStyle( fontWeight = FontWeight.Normal, fontSize = 14.sp, lineHeight = 20.sp, letterSpacing = 0.25.sp, ), bodySmall = TextStyle( fontWeight = FontWeight.Normal, fontSize = 12.sp, lineHeight = 16.sp, letterSpacing = 0.4.sp, ), // Used for Button labelLarge = TextStyle( fontWeight = FontWeight.Medium, fontSize = 14.sp, lineHeight = 20.sp, letterSpacing = 0.1.sp, lineHeightStyle = LineHeightStyle( alignment = Alignment.Center, trim = Trim.LastLineBottom, ), ), // Used for Navigation items labelMedium = TextStyle( fontWeight = FontWeight.Medium, fontSize = 12.sp, lineHeight = 16.sp, letterSpacing = 0.5.sp, lineHeightStyle = LineHeightStyle( alignment = Alignment.Center, trim = Trim.LastLineBottom, ), ), // Used for Tag labelSmall = TextStyle( fontWeight = FontWeight.Medium, fontSize = 10.sp, lineHeight = 14.sp, letterSpacing = 0.sp, lineHeightStyle = LineHeightStyle( alignment = Alignment.Center, trim = Trim.LastLineBottom, ), ), )
앱 내에서 각종 UI Component에 여러 모양새를 넣을 수 있다. 예를 들면, 꼭지점 부분을 둥글게 한다던지, 아니면 아예 동그랗게 만들다든지, 테두리를 적용한다든지 등이 있다. 이러한 부분을 상위 MatherialTheme
에 적용하고 공통적으로 꺼내쓰고자할 때 사용한다. 위와 비슷한 부분이 많기에 자세한 설명은 생략한다.
Material Design System이란게 생각보다 복잡하단 것을 알게되었다. 그러기 위해선 함께 협업하는 디자이너분의 실력이나 책임감이 필요하며, 그에 맞게 Material Design System에 맞게 디자인을 설계해주실 필요가 있어보인다.
개발자가 스스로 이를 적용하기엔 단순 'Material Builder'같은 곳에서 다운받아와 적용하는게 최선이지 않을까 생각한다.
현재 진행하는 사이드 프로젝트가 있는데, Design System이 적용되어 있지 않을까하고 해당 주제로 약간의 공부를 해보았다. 근데 크게 적용되어 있는지는 모르겠고, 단순 내가 적용할 수 있는 부분이라도 조금 찾아서 진행하는게 나아보인다.
Toss에는 TDS라는 디자인 시스템이 자체적으로 존재한다. 그러다보니 이들의 디자인 시스템은 얼마나 고도화되어있고 체계적일지 궁금하기도 하다. 아무튼 오늘 공부 끝.