์ด๋ฒ ํฌ์คํ ์ ์ง๋ ํฌ์คํ ์์ ๋งํ๋ค์ํผ ์ปค์คํ ํ ๋ง์ ๋ํ ๊ฐ๋จ ์ค๋ช ๊ณผ ์ฝ๋๋ฅผ ์๋ ค์ฃผ๋๋ก ํ๊ฒ ๋ค.
์ปค์คํ ํ ๋ง๋ฅผ ์ ๋ง๋ค์ด์ผ ํ๋์ง ๊ถ๊ธํ ์ฌ๋๋ค์ด ์์ ๊ฒ ๊ฐ์ ๊ฐ๋จํ ์ค๋ช ํ์๋ฉด
๋ํ ํ๋ก์ ํธ๋ฅผ ํ๋ค ๋ณด๋ฉด color๊ฐ ๋ค์ํด์ง ์ ์๊ธฐ์ ๊ธฐ๋ณธ์ ์ผ๋ก android studio์์ ์ ๊ณตํ๋ color palette๊ฐ ๋ถ์กฑํ ์ ์๋ค. ๊ทธ๋ฆฌ๊ณ ๋์์ด๋๊ฐ ์ ํ color๋ช ์ ๊ทธ๋๋ก ์ ์ฉํ๊ธฐ ํ๋ค๊ธฐ ๋๋ฌธ์ด๋ค.
์ด๋ด ๋์๋ custom theme๋ฅผ ๋ง๋๋ ๊ฒ์ ๊ณ ๋ คํด ๋ณผ ์ ์๋ค.
๊ทธ๋์ ํ์๋ ์ด๋ฒ ๋ํ ํ๋ก์ ํธ์ ๋๋นํด ์ปค์คํ ํ ๋ง๋ฅผ ์ ์ํด ๋ณด๊ธฐ๋ก ํ๋ค.
์ ๊ทธ๋ผ ์ด์ ์ฝ๋๋ก ํ์๊ฐ custom theme๋ฅผ ๋ง๋ค์๋ ๊ณผ์ ์ ํ์ธํด ๋ณด์.
์์ํ๊ธฐ ์ ์!
์ฒซ ๋ฒ์งธ ๋จ๊ณ๋ theme ์ํ๋ฅผ ํ์ธํ ์ ์๋ enum class๋ฅผ ์์ฑํด ์ค๋ค.
enum class Theme {
LIGHT,
DARK
}
1. color ์ ์ํ๊ธฐ
์์ ์ ํ๋ก์ ํธ design system์ ์ด์ฉํด color๋ฅผ ์ ์ํด ๋ณด์.
val Black = Color(0xFF000000)
val White = Color(0xFFFFFFFF)
val Transparent = Color(0x00000000)
val Primary10Light = Color(0xFF6F7AEC)
val Primary20Light = Color(0xFFEEF0FD)
val Primary30Light = Color(0xFFC5CAF7)
val Neutral10Light = Color(0xFF292E3D)
val Neutral20Light = Color(0xFF656B80)
val Neutral30Light = Color(0xFFBBBBCC)
val Neutral40Light = Color(0xFFE2E2EE)
val Neutral50Light = Color(0xFFF2F2F4)
val BackGroundLight = Color(0xFFF7F7F9)
val CardBackGroundLight = Color(0xFFFDFDFD)
val ErrorLight = Color(0xFFD84D4D)
val PositiveLight = Color(0xFF2E8546)
val SubGreenLight = Color(0xFF9BDA93)
val SubYellowLight = Color(0xFFEEE170)
val SubRedLight = Color(0xFFF3A199)
val Primary10Dark = Color(0xFF5966E9)
val Primary20Dark = Color(0xFF1D2136)
val Primary30Dark = Color(0xFF24295D)
val Neutral10Dark = Color(0xFFCDCDD5)
val Neutral20Dark = Color(0xFF818198)
val Neutral30Dark = Color(0xFF626274)
val Neutral40Dark = Color(0xFF292930)
val Neutral50Dark = Color(0xFF1E1E26)
val BackGroundDark = Color(0xFF1F1E2B)
val CardBackGroundDark = Color(0xFF17161F)
val ErrorDark = Color(0xFFFF7D7D)
val PositiveDark = Color(0xFF5EFF8B)
val SubGreenDark = Color(0xFF79CE6F)
val SubYellowDark = Color(0xFFE9D844)
val SubRedDark = Color(0xFFEE786D)
ํ์์ ๊ฒฝ์ฐ์๋ dark, light ๊ตฌ๋ถํด์ color๋ฅผ ์ ์ํ๋ค. dark theme๊ฐ ์๋ ๊ฒฝ์ฐ ์์ฑํ์ง ์์๋ ๋๊ณ , color์ ๊ตฌ์ฑ๋ ์ฌ๋ฌ๋ถ๋ค์ design system์ ์ด์ฉํด ์์ ๋กญ๊ฒ custom ํด๋ณด์๊ธธ..(ํ์์ ์ฝ๋๋ ์์์ด๋ค.)
๊ทธ๋ฆฌ๊ณ color ๊ฐ์ฒด๋ฅผ ์ ์ํ๋ color palette class์ color ๋ณต์ฌ update์ฉ ํจ์๋ฅผ ์ถ๊ฐํ๋ค.
์ดํ์ ๋ง๋ค typography์ ๋ค๋ฅธ ์ ์ copy์ update๊ฐ ์๋ค๋ ์ ์ด๋ค. ์ด ๋ ํจ์๋ฅผ ๋ง๋๋ ์ด์ ๋ ํ์์ theme๋ dark/light๋ก ๊ณ์ํด์ ๋ฐ๋๊ธฐ ๋๋ฌธ์ ๊ทธ ์ํ๋ฅผ ๋ณด์กดํ๊ธฐ ์ํด copy์ update ํจ์๋ฅผ ๋ง๋ค์ด ์ฃผ์๋ค.
class Colors(
primary10: Color,
primary20: Color,
primary30: Color,
neutral10: Color,
neutral20: Color,
neutral30: Color,
neutral40: Color,
neutral50: Color,
background: Color,
cardBackground: Color,
error: Color,
positive: Color,
subGreen: Color,
subYellow: Color,
subRed: Color
) {
var primary10 by mutableStateOf(primary10)
private set
var primary20 by mutableStateOf(primary20)
private set
var primary30 by mutableStateOf(primary30)
private set
var neutral10 by mutableStateOf(neutral10)
private set
var neutral20 by mutableStateOf(neutral20)
private set
var neutral30 by mutableStateOf(neutral30)
private set
var neutral40 by mutableStateOf(neutral40)
private set
var neutral50 by mutableStateOf(neutral50)
private set
var background by mutableStateOf(background)
private set
var cardBackground by mutableStateOf(cardBackground)
private set
var error by mutableStateOf(error)
private set
var positive by mutableStateOf(positive)
private set
var subGreen by mutableStateOf(subGreen)
private set
var subYellow by mutableStateOf(subYellow)
private set
var subRed by mutableStateOf(subRed)
private set
fun update(colors: Colors) {
this.primary10 = colors.primary10
this.primary20 = colors.primary20
this.primary30 = colors.primary30
this.neutral10 = colors.neutral10
this.neutral20 = colors.neutral20
this.neutral30 = colors.neutral30
this.neutral40 = colors.neutral40
this.neutral50 = colors.neutral50
this.background = colors.background
this.cardBackground = colors.cardBackground
this.subGreen = colors.subGreen
this.subYellow = colors.subYellow
this.subRed = colors.subRed
this.error = colors.error
this.positive = colors.positive
}
fun copy() = Colors(
primary10 = primary10,
primary20 = primary20,
primary30 = primary30,
neutral10 = neutral10,
neutral20 = neutral20,
neutral30 = neutral30,
neutral40 = neutral40,
neutral50 = neutral50,
background = background,
cardBackground = cardBackground,
error = error,
positive = positive,
subGreen = subGreen,
subYellow = subYellow,
subRed = subRed
)
}
2. typography ๋ง๋ค๊ธฐ
๊ทธ๋ค์์๋ font๋ฅผ ์ ์ฉํ typography๋ฅผ ๋ง๋ค์ด ์ฃผ๊ฒ ๋ค.
val suitFont = FontFamily(
Font(R.font.suit_bold, FontWeight.Bold),
Font(R.font.suit_medium, FontWeight.Medium),
Font(R.font.suit_semi_bold, FontWeight.SemiBold)
)
object Typography {
@Stable
val h1 = TextStyle(
fontFamily = suitFont,
fontSize = 48.sp,
fontWeight = FontWeight.Bold
)
@Stable
val h2 = TextStyle(
fontFamily = suitFont,
fontSize = 32.sp,
fontWeight = FontWeight.Bold
)
@Stable
val h3 = TextStyle(
fontFamily = suitFont,
fontSize = 24.sp,
fontWeight = FontWeight.Bold
)
@Stable
val h4 = TextStyle(
fontFamily = suitFont,
fontSize = 20.sp,
fontWeight = FontWeight.Bold
)
@Stable
val subTitle1 = TextStyle(
fontFamily = suitFont,
fontSize = 18.sp,
fontWeight = FontWeight.Bold
)
@Stable
val subTitle2 = TextStyle(
fontFamily = suitFont,
fontSize = 16.sp,
fontWeight = FontWeight.Bold
)
@Stable
val subTitle3 = TextStyle(
fontFamily = suitFont,
fontSize = 18.sp,
fontWeight = FontWeight.Medium
)
@Stable
val smallTitle = TextStyle(
fontFamily = suitFont,
fontSize = 14.sp,
fontWeight = FontWeight.SemiBold
)
@Stable
val body = TextStyle(
fontFamily = suitFont,
fontSize = 16.sp,
fontWeight = FontWeight.Medium,
lineHeight = 1.5f.sp
)
@Stable
val body2 = TextStyle(
fontFamily = suitFont,
fontSize = 14.sp,
fontWeight = FontWeight.Medium
)
@Stable
val caption = TextStyle(
fontFamily = suitFont,
fontSize = 12.sp,
fontWeight = FontWeight.Medium
)
}
internal val LocalTypography = staticCompositionLocalOf { Typography }
fontfamily์ font object๋ฅผ ์์ฑํ๊ณ localTypography๋ staticCompositionLocalOf์ ์ด์ฉํด ํน์ composable๊น์ง ์ ๊ทผ์ ํ์ฉํ ์ ์๋ค.(์ฌ์ค staticCompositionLocalOf์ ์ ๋๋ก ์ดํดํ์ง ๋ชปํด์ ์ ๋๋ก ์ค๋ช ์ ๋๋ฆด ์๊ฐ ์์ ๊ฒ ๊ฐ์์ ํ์๊ฐ ์ฐธ๊ณ ํ ๋ธ๋ก๊ทธ ์ฃผ์๋ฅผ ๋ณด๋ด์ฃผ๋๋ก ํ๊ฒ ๋ค.)
https://www.charlezz.com/?p=46403
3. theme ๋ง๋ค๊ธฐ
color์ typography๋ฅผ ๋ง๋ค์์ผ๋ ์ด์ ๋ ํ ๋ง๋ฅผ ๋ง๋ค์ด๋ณด์.
๋จผ์ ์ด์ ์ ๋ง๋ค์ด๋ color Palette๋ฅผ ์ด์ฉํด color๋ฅผ ๋ง๋ค์ด ์ค๋ค.
private val darkColors = Colors(
primary10 = Primary10Dark,
primary20 = Primary20Dark,
primary30 = Primary30Dark,
neutral10 = Neutral10Dark,
neutral20 = Neutral20Dark,
neutral30 = Neutral30Dark,
neutral40 = Neutral40Dark,
neutral50 = Neutral50Dark,
background = BackGroundDark,
cardBackground = CardBackGroundDark,
subGreen = SubGreenDark,
subYellow = SubYellowDark,
subRed = SubRedDark,
error = ErrorDark,
positive = PositiveDark
)
private val lightColors = Colors(
primary10 = Primary10Light,
primary20 = Primary20Light,
primary30 = Primary30Light,
neutral10 = Neutral10Light,
neutral20 = Neutral20Light,
neutral30 = Neutral30Light,
neutral40 = Neutral40Light,
neutral50 = Neutral50Light,
background = BackGroundLight,
cardBackground = CardBackGroundLight,
subGreen = SubGreenLight,
subYellow = SubYellowLight,
subRed = SubRedLight,
error = ErrorLight,
positive = PositiveLight
)
๋ํ typography ๋์ ๊ฐ์ด color ์ํ๋ฅผ ์ ์ฅํ๋ staticCompositionLocalOf ๋ณ์๋ฅผ ๋ง๋ค์ด ์ค๋ค.
private val LocalColorProvider = staticCompositionLocalOf { DotoriLightColors }
๋ค์์ผ๋ก ํ ๋ง๋ฅผ ์ ๊ณตํ๋ ํจ์๋ฅผ ๋ง๋ค์ด ๋ณด๊ฒ ๋ค.
@Composable
private fun ThemeLocalProvider(
colors: DotoriColors,
typography: DotoriTypography,
content: @Composable () -> Unit
) {
val colorPalette = remember { colors.copy() }
colorPalette.update(dotoriColors = colors)
CompositionLocalProvider(
LocalColorProvider provides colorPalette,
LocalTypography provides typography,
content = content
)
}
private val Theme.colors: Colors
get() = when(this) {
Theme.DARK -> DarkColors
Theme.LIGHT -> LightColors
}
color๋ light/dark๋ก ๋ฐ๋๊ธฐ ๋๋ฌธ์ color์ ์ํ๋ฅผ ์ ์ฅํ๊ณ colors๊ฐ ๋ฐ๋๋ฉด remember์์๋ ๋ณ๊ฒฝ๋๊ฒ ๋ง๋ค์๊ณ , compositionLocalProvider๋ก color์ typography๋ฅผ ์ ๊ณตํ๋ค.
colors ๋ณ์๋ theme ์ํ์ ๋ฐ๋ผ ์์์ ์ ์ํ color palette๋ฅผ ์ ์ฉํด ์ฃผ๋ ๋ถ๋ถ์ด๋ค.
๋ง์ง๋ง ๋จ๊ณ๋ ๊ฐ๋ฐ์๊ฐ ์ง์ ์ฌ์ฉํ theme๋ฅผ ์์ฑํด ์ฃผ๋ ๊ณผ์ ์ด๋ค.
object Theme {
val colors: Colors
@Composable
get() = LocalColorProvider.current
val typography: Typography
@Composable
get() = LocalTypography.current
var theme by mutableStateOf(Theme.LIGHT)
fun isSystemIsDarkTheme(): Boolean {
return dotoriTheme == Theme.DARK
}
}
@Composable
fun Theme(
theme: Theme = Theme.theme,
typography: Typography = Theme.typography,
content: @Composable () -> Unit
) {
ThemeLocalProvider(
colors = theme.colors,
typography = typography,
content = content
)
}
object์ theme๋ ์ฌ์ฉ์๊ฐ color์ typography๋ฅผ ์ ์ฉํ ๋ ์ฌ์ฉ๋ ๊ฒ์ด๋ค.
color์ typography์ ํ์ฌ ์ํ๋ฅผ ๊ฐ์ ธ์จ๋ค. ๋ํ ํ์ฌ ํ ๋ง๊ฐ dark theme์ธ์ง๋ ๊ฒ์ฌํ๋ ํจ์๋ ์์ฑํ๋ค.(์ด ํจ์๋ ์์ด๋ ๋๋ค. ํ์ํ ๋ถ๋ค๋ง ๋ง๋์๊ธธ)
composable function theme๋ android studio project๋ฅผ ์ฒ์ ์์ฑํ ๋ ์ฌ์ฉํ๋ AppNameTheme {}์ ๊ฐ์ ์ญํ ์ด๋ค.
theme์ typography param์ ์ ์ํด ๋์๊ธฐ์ ์ฌ์ฉํ ๋ ๊ตณ์ด param์ ์ ์ํ๋ผ๊ณ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ง ์์ ๊ฒ์ด๋ค.
4. ์ฌ์ฉํด ๋ณด๊ธฐ
์ด์ ํ ๋ง๋ฅผ ๋ง๋ค์์ผ๋ ์ฌ์ฉํด ๋ณด์.
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Theme {
// ui component
}
}
}
}
์ด์ ์ ์์ฑํ Theme composable function์ ์ด์ฉํด ํ ๋ง๋ฅผ ์ ์ฉํ ์ ์๋ค.
color์ typography๋ฅผ ์ ์ฉํ๋ ๋ฐฉ๋ฒ์ ๋ค์๊ณผ ๊ฐ๋ค.
Column(
modifier = Modifier
.fillMaxSize()
.background(Theme.colors.background) // color ์ฌ์ฉ
) {
Text(
modifier = Modifier.fillMaxWidth(),
text = "theme typography",
style = Theme.typography.body, // typographuy ์ฌ์ฉ
textAlign = TextAlign.Center,
color = DotoriTheme.colors.neutral10
)
}
์ด์ ์ ๋ง๋ theme object class๋ฅผ ์ด์ฉํด ์ ์ํ color์ typography๋ฅผ ์ฌ์ฉํ ์ ์๋ค.
์ ๋ฆฌ
- ํ ๋ง๋ฅผ ๋ง๋๋ ๊ฒ์ ์ฒ์์ด๋ผ ์ด๋ ค์ ๋ ์ ์ด ๋ง์๋๋ฐ, ๋ค๋ฅธ ๋ธ๋ก๊ทธ์ youtube ์์์ ์ฐธ๊ณ ํ์๋๋ฐ ๊ธฐ์ต์ด ๋์ง ์๋๋ค. ์ฃ์กํฉ๋๋ค :(
- custom theme๋ฅผ ์ ์ํ๋ฉด์ ๋ํ ํ๋ก์ ํธ์์ ์ธ๋ชจ๊ฐ ๋ง์ ๊ฒ์ด๋ผ๊ณ ์๊ฐํ๊ณ , ์์ ๋ณํ(dark/light)๊ณผ typography๊ฐ ๋ณ๊ฒฝ๋ ๋ชจ์ต์ ์ฒ์ ๋ดค์ ๋์๋ ์์ฒญ ๊ธฐ๋ปํ์๋ค.(์ด๋์ ๋ด๊ฐ ์ฝ๋ฉ์ ๋์ ์๊ฐ ์๋ค ใ ใ )
- ์์ง๋ ์ดํด๊ฐ ์์ ํ ๋์ง ์์์ ์ฝ๋์ ๋ถ๊ฐ ์ค๋ช ์ ํด์ฃผ์๊ฑฐ๋ ๋ ํจ์จ์ด ์ข์ ์ฝ๋๋ฅผ ์์๋ ๋ถ์ ๋๊ธ๋ก ์๋ ค์ฃผ์๋ฉด ์ข์ ๊ณต๋ถ๊ฐ ๋ ๊ฒ ๊ฐ์ต๋๋ค. ๊ฐ์ฌํฉ๋๋ค >:D
์ฐธ๊ณ ํ ๋ธ๋ก๊ทธ
https://velog.io/@vov3616/Compose-Custom-Theme-%EB%A7%8C%EB%93%A4%EA%B8%B0