[Compose] Theme

KSang·2024년 5월 1일
1

TIL

목록 보기
94/101

Theme

일관성 있는 UI를 위한 Compose에서 theme관리하는 방법

Color

data class MyColors(
    val material: ColorScheme,
    val tertiary: Color = material.primary,
    val onPrimaryAlt: Color = material.onPrimary,
    val success: Color = Color.Green,
    val checked: Color = Color.White,
    val unchecked: Color = Color.White,
    val checkmark: Color = material.primary,
    val disabledSecondary: Color = material.secondary.copy(alpha = 0.5f),
    val textFiledBackground: Color = Color.LightGray,
    val textFiledBackgroundVariant: Color = Color.DarkGray,
    val launcherScreenBackground: Color = material.primary,
    val progressItemColor: Color = Color.Black
) {
    val primary: Color get() = material.primary
    val primaryContainer: Color get() = material.primaryContainer
    val secondary: Color get() = material.secondary
    val secondaryContainer: Color get() = material.secondaryContainer
    val background: Color get() = material.background
    val surface: Color get() = material.surface
    val error: Color get() = material.error
    val onPrimary: Color get() = material.onPrimary
    val onSecondary: Color get() = material.onSecondary
    val onBackground: Color get() = material.onBackground
    val onSurface: Color get() = material.onSurface
    val onSurfaceVariant: Color get() = material.onSurfaceVariant
    val onError: Color get() = material.onError
}

앱에 사용할 커스텀 컬러 data class 를 만들어준다.

colorScheme을 지정해주고 사용하는 메소드들을 정의 해준다.

이제 Light모드와 Dark모드를 구별해서 사용 할 수 있는 ColorSet를 만들어준다.

val Red400 = Color(0xFFFF5258)
val Red700 = Color(0xFFEC0000)
val Red800 = Color(0xFFAF0000)
...

sealed class ColorSet {
    open lateinit var LightColors: MyColors
    open lateinit var DarkColors: MyColors

    data object Red: ColorSet() {
        override var LightColors = MyColors(
            material = lightColorScheme(
                primary = Red700,
                onPrimary = Red800,
                secondary = Purple900,
                onSecondary = Purple700,
                surface = White,
                onSurface = Black,
                background = White,
                onBackground = Black,
                error = Red400
            ),
            success = Green400,
            disabledSecondary = Grey200,
            textFiledBackground = Grey200
        )
        override var DarkColors = MyColors(
            material = darkColorScheme (
                primary = Purple900,
                onPrimary = Red800,
                secondary = Purple900,
                onSecondary = Purple700,
                surface = White,
                onSurface = Black,
                background = White,
                onBackground = Black,
                error = Red400,
            )
        )
    }
}

ColorSet을 설정한뒤 Theme.kt의 AppTheme컴포저블에서 설정해준다.

private val LocalColors = staticCompositionLocalOf { ColorSet.Red.LightColors }

@Composable
fun MovieAppTheme(
    myColors: ColorSet = ColorSet.Red,
    darkTheme: Boolean = isSystemInDarkTheme(),
    content: @Composable () -> Unit,
) {
    val colors =
        if (darkTheme) myColors.DarkColors else myColors.LightColors

    val view = LocalView.current
    if (!view.isInEditMode) {
        SideEffect {
            val window = (view.context as Activity).window
            window.statusBarColor = colors.primary.toArgb()
            WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme
        }
    }

    CompositionLocalProvider(LocalColors provides colors) {
        MaterialTheme(
            colorScheme = colors.material,
            content = content,
        )
    }
}

여기서 CompositionLocalProvider를 사용해서 하위 컴포넌트로 필요한 데이터를 효율적으로 내려준다.

CompositionLocal을 사용하면 필요한 데이터만 상위에서 하위로 내려줌으로 불필요한 리컴포지션을 방지해준다.

MaterialTheme에선 color뿐만아니라 typography,shapes또한 설정이 가능하다.

Typography

Type.kt

private val spoqaHanSansNeo = FontFamily(
    Font(R.font.spoqa_han_sans_neo_bold, FontWeight.Bold),
    Font(R.font.spoqa_han_sans_neo_regular, FontWeight.Normal),
    Font(R.font.spoqa_han_sans_neo_thin, FontWeight.Thin)
)

val Typography = Typography(
    displayLarge = TextStyle(
        fontFamily = spoqaHanSansNeo,
        fontWeight = FontWeight.Bold,
        fontSize = 57.sp,
        lineHeight = 64.sp
    ),
    displayMedium = TextStyle(
        fontFamily = spoqaHanSansNeo,
        fontWeight = FontWeight.Bold,
        fontSize = 32.sp,
        lineHeight = 40.sp
    ),
    ...

theme에서 사용할 폰트들을 설정해줄 수 있다.

    Text(
        text = titleName,
        modifier = Modifier.padding(Padding.large),
        style = MaterialTheme.typography.headlineSmall
    )

theme에 추가했다면 컴포저블의 style에서 타이포그래피를 설정해 스타일을 지정해 줄수 있다.

material3에선 style설정을 안할시 Text에선 bodyLarge를 기본으로 따라 가는것 같다.

기본 타이포 그래피에서 설정하는 값들이 부족하다, 좀더 세분화 해서 사용하고 싶다 할 수 있는데, 확장 프로퍼티를 통해 표현가능하다.

val Typography.displayLarge60: TextStyle
    @Composable get() = displayLarge.copy(
        fontSize = 60.sp
    )
    
    Text(
        text = titleName,
        modifier = Modifier.padding(Padding.large),
        style = MaterialTheme.typography.displayLarge60
    )

Typography의 설정을 변경하지 않고도 필요한 부분을 수정할 수 있어 관심사가 분리된다.

시스템을 유연하고 확장성있게 관리 할 수 있다.

결합도 또한 낮아지는데, Typography에 직접적인 영향을 끼치지 않기 때문에 결합도가 낮아 안정성이 늘어난다.

Shape

shape은 card등의 corner radius를 관리한다.

val Shapes = Shapes(
    small = RoundedCornerShape(4.dp),
    medium = RoundedCornerShape(8.dp),
    large = RoundedCornerShape(12.dp),
)

그냥 이거선언하고 테마에 넣으면 기본 radius가 조절된다.

padding과 size

padding같은경우 object를 이용해 관리할 수 있다.

object Padding {
    val none = 0.dp
    val xsmall = 2.dp
    val small = 4.dp
    val medium = 8.dp
    val large = 12.dp
    val xlarge = 16.dp
    val extra = 24.dp
    val xextra = 32.dp
}

직접적인 수 8.dp같은건 잘 안쓰인다고 한다.

그럼 modifier등에 설정하는 크기는 컴포저블마다 다를텐데 어떻게 할까?


private val CARD_WIDTH = 150.dp
private val CARD_HEIGHT = 200.dp
private val ICON_SIZE = 12.dp

@Composable
fun MovieItem() {
    Column(
        modifier = Modifier
            .width(CARD_WIDTH)
            .padding(Padding.large)
    ) {
        Poster(
            modifier = Modifier
                .width(CARD_WIDTH)
        )

각각 컴포저블을 구성하는 파일에 들어가 private하게 설정해준다.

0개의 댓글