2 분 소요

안드로이드에서는 Material Design을 사용하여 컬러를 적용하여 라이트, 다크모드를 지원하는 앱을 만들 수 있게 해주고 있습니다.

하지만 Material의 지정된 테마가 한정적이고, 디자이너가 만든 색상의 네임을 사용해야 하는 경우에는 사용자 지정 테마를 만들어야 합니다. 이번에 Compose에서 사용자 지정 테마를 적용하는 방법을 하나씩 살펴보도록 하겠습니다!

📍 Compose Theme

안드로이드 스튜디오에서 기본적으로 Compose 프로젝트를 만들게 되면 Color, Theme, Type의 기본 값들이 생성되어 있습니다.

1. Color

앱에서 사용할 라이트,다크모드에 사용할 색상들을 지정하고 있습니다.

val Purple80 = Color(0xFFD0BCFF)
val PurpleGrey80 = Color(0xFFCCC2DC)
val Pink80 = Color(0xFFEFB8C8)

val Purple40 = Color(0xFF6650a4)
val PurpleGrey40 = Color(0xFF625b71)
val Pink40 = Color(0xFF7D5260)

2. Typography

// Set of Material typography styles to start with
val Typography = Typography(
    bodyLarge = TextStyle(
        fontFamily = FontFamily.Default,
        fontWeight = FontWeight.Normal,
        fontSize = 16.sp,
        lineHeight = 24.sp,
        letterSpacing = 0.5.sp
    )
    ...
)

3. Theme

private val DarkColorScheme = darkColorScheme(
    primary = Purple80,
    secondary = PurpleGrey80,
    tertiary = Pink80
    ...
)

private val LightColorScheme = lightColorScheme(
    primary = Purple40,
    secondary = PurpleGrey40,
    tertiary = Pink40
    ...
)

Theme.kt 에서는 라이트, 다크모드에서 사용할 ColorScheme가 지정되어 있습니다. 이 메소드에서는 parameter를 따로 채워주지 않으면 기본 값이 설정되게 됩니다.

시스템의 모드에 따라 colors를 정의하고 MaterialTheme에 해당 인자들을 넘겨줘 사용하고 있다는 것을 볼 수 있습니다.

@Composable
fun CustomTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    dynamicColor: Boolean = true,
    content: @Composable () -> Unit
) {
    val colorScheme = when {
        dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
            val context = LocalContext.current
            if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
        }

        darkTheme -> DarkColorScheme
        else -> LightColorScheme
    }
    .
    .
    MaterialTheme(
        colorScheme = colorScheme,
        typography = Typography,
        content = content
    )
}

Theme 만들어보기

위에서 봤던 컬러, 폰트등을 사용자 지정 테마로 만들어서 사용해 보도록 하겠습니다.

먼저 Material에서 lightColorScheme, darkColorScheme을 사용했던 것과 같이 커스텀 ColorScheme를 만들어야 합니다.

Color들이 시스템 모드에 맞게 변경된다면 Recomposition이 일어나야하므로 지정한 color들을 State로 감싸주는 작업이 필요합니다.

사용자 Color

val primary = Color(0xFFB4BDE9)
val primaryDark = Color(0xFF6E81E4)

val background = Color(0xFFE2E2E2)
val backgroundDark = Color(0xFF3D3D3D)


class CustomColorScheme(
    primary: Color,
    background: Color,
    .
    .
) {
    var primary by mutableStateOf(primary)
        private set
    var background by mutableStateOf(background)
        private set
    .
    .
}

val customLightColorScheme: RickColorScheme by lazy {
    RickColorScheme(
        primary = primary,
        background = background,
        .
        .
    )
}

val customDarkColorScheme: RickColorScheme by lazy {
    RickColorScheme(
        primary = primaryDark,
        background = backgroundDark,
        .
        .
    )
}

사용자 Typography

private val pretendard = FontFamily(
    Font(R.font.pretendard_regular, FontWeight.Normal),
    Font(R.font.pretendard_medium, FontWeight.Medium),
    Font(R.font.pretendard_semi_bold, FontWeight.SemiBold)
    .
    .
)

// FontFamily가 FontWeight에 따라 선택된다.
val title01 = TextStyle(
    fontSize = 18.sp,
    fontWeight = FontWeight.Bold,
    fontFamily = pretendard
)

val title02 = TextStyle(
    fontSize = 14.sp,
    fontWeight = FontWeight.SemiBold,
    fontFamily = pretendard
)
.
.
data class CustomTypography(
    val title01: TextStyle = title01,
    val title02: TextStyle = title02,
)

만들어준 ColorScheme 및 Typography들은 CompositionLocalProvider 를 통해 제공할 수 있습니다.

@Composable
fun CustomTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    content: @Composable () -> Unit
) {

    val typography = CustomTypography()
    val currentColor = remember {
        if (darkTheme) {
            customDarkColorScheme
        } else {
            customLightColorScheme
        }
    }

    CompositionLocalProvider(
        LocalColorScheme provides currentColor
        LocalTypography provides typography
    ) {
        ProvideTextStyle(typography.title01, content = content)
    }
}

val LocalColorScheme = staticCompositionLocalOf { customLightColorScheme }
val LocalTypography = staticCompositionLocalOf { CustomTypography() }

테마 값 사용하기

CompositionLocalProvider 로 주입된 color나 typography는 LocalColorScheme.current 를 통해 최신 값을 가져올 수 있습니다.

이를 object 로 감싸 더 쉽게 사용할 수 있도록 만들어 줍니다.

object CustomTheme {
    val colors: CustomColorScheme
        @Composable
        @ReadOnlyComposable
        get() = LocalColorScheme.current

    val typography: CustomTypography
        @Composable
        @ReadOnlyComposable
        get() = LocalTypography.current
}

@ReadOnlyComposable : 읽기에 최적화되게 만들어주는 어노테이션

간단에게 테스트를 위해 color값을 설정해 보면 시스템 모드에 따라 색상이 설정되는 것을 볼 수 있습니다 👋🏼

Column(
    modifier = modifier
        .fillMaxSize()
        .background(color = CustomTheme.colors.background)
) {
    ...
}


References

맨 위로 이동 ↑

댓글남기기