프로그래밍/Android

[안드로이드] Jetpack Compose 테마와 스타일 정의하기

Lou Park 2021. 9. 7. 00:15

Jetpack Compose를 시작하기 위한 강의는 구글에서 제공한다.

레이아웃을 어떻게 만들지는 한 번만 따라해도 어느정도 각이 나온다. 어떻게 디자인하는지 알려주는 강의는 너무 많다. 하지만 중요한건 Integration! 앞으로 블로그에서 다룰 Jetpack Compose 관련 주제가 바로 Integration이 될 것이다. 어떻게 내 프로젝트에 통합시킬 수 있을지!


Theming

그 첫번째로 테마(Theme)와 스타일에 관해서 알아보려고한다.

Jetpack Compose에서 아주 쉽게 Material Design을 적용시킬 수 있다고는 하지만, 디자이너들은(93% 확률로 아이폰을 이용중) 우리에게 Material Design을 던져주지 않는다. 앱 고유의 스타일을 어떻게 확립하고 적용시킬 수 있는지 알아보자.

 

Colors

기존에는 앱에서 자주쓰는 색상들을 res > colors.xml 에 저장해왔다. 이제는 Colors.kt 같은 kotlin 파일에 저장한다.

github에서 높은 Star를 받은 Jetpack Compose 프로젝트 몇개를 보니 패키지명 > ui > Color.kt 혹은 패키지명 > ui > theme > Color.kt 정도의 경로로 정리하고 있다.

 

내용은 대충 이렇다.

import androidx.compose.ui.graphics.Color

val louBlue = Color(0xFF407FFC)
val purple500 = Color(0xFF6200EE)
val purple700 = Color(0xFF3700B3)
val teal200 = Color(0xFF03DAC5)

 

 

Theme

마찬가지로 theme.xml에 저장하고는 했었던 내용들을 Theme.kt에 작성할 수 있다. darkColorslightColors는 각각 다크테마와 라이트테마 기준으로 만들어지며, 값을 주지 않으면 기본 값을 채운다. 반대로 Colors는 테마를 처음부터 재정의하기 때문에 모든 parameter를 다 채워주어야한다.

 

여기에는 당연하게도 아까 Colors.kt에 정의했던 색깔들을 불러올 수 있다.

import androidx.compose.material.Colors
import androidx.compose.material.darkColors
import androidx.compose.material.lightColors
import androidx.compose.ui.graphics.Color

private val DarkColorPalette = darkColors(
    primary = louBlue,
    primaryVariant = louBlue,
    secondary = teal200,
    background = Color.Black,
    surface = Color.DarkGray
)

private val LightColorPalette = lightColors(
    primary = louBlue,
    primaryVariant = louBlue,
    secondary = louBlue,
    background = Color.White
)

private val customColorPalette = Colors(
    primary = purple500,
    primaryVariant = purple500,
    secondary = purple700,
    secondaryVariant = purple700,
    background = Color.White,
    surface = Color.White,
    error = Color.White,
    onPrimary = Color.Black,
    onSecondary = Color.Black,
    onBackground = Color.Black,
    onSurface = Color.Black,
    onError = Color.White,
    isLight = true
)

Theme.kt를 작성했다면, 이 모든 테마를 Composable 함수로 래핑하여 적용가능한 형태로 만들어야한다. 똑같이 Theme.kt 파일에다 작성해주면 된다. @Composable은 Annotation processor처럼 보일 수 있지만 사실은 suspend 키워드 처럼 함수의 타입을 정의하는 language keyword다. 이 사실을 숙지하면 @Composable () -> Unit 같은 코드가 자연스럽게 보이기 시작할 것 이다.

 

나는 LouAppTheme라는 테마를 정의했고, 시스템이 다크모드냐 아니냐에 따라 colors를 정의하고 MaterialTheme을 만들어서 해당 컬러팔레트를 인자로 넘겨주었다.

@Composable
fun LouAppTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    content: @Composable () -> Unit
) {
    val colors = if (darkTheme) DarkColorPalette else LightColorPalette
    MaterialTheme(
        colors = colors,
        content = content
    )
}

MaterialTheme은 사실 이렇게 생긴 Composable 함수다. 이걸보면 "shape나 typography도 같이 정의해서 넣을 수 있구나

이 글의 어딘가에 쉐이프랑 폰트 정의를 얘기할꺼구나~"를 직감할 수 있다.

@Composable
fun MaterialTheme(
    colors: Colors = MaterialTheme.colors,
    typography: Typography = MaterialTheme.typography,
    shapes: Shapes = MaterialTheme.shapes,
    content: @Composable () -> Unit
) {
    ...
}

나를 포함하여 추가적인 궁금증이 드시는 분들을 위해... 방금 정의한 Theme은 이렇게 사용할 수 있다.

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            LouAppTheme {
                // TODO Composable...
                // MaterialTheme.colors.primary 로 접근가능
            }
        }
    }
}

 

 

Typography

올 것이 왔군? Typography.kt 파일에 타이포 그래피를 정의할 수 있다. 커스텀 폰트를 사용하기 위해서 앱의 res > font에 사용할 font를 넣는 방식은 같다. 나는 샘플로 Noto sans를 다운받아서 앱에 적용하려한다.

val NotoFontFamily = FontFamily(
    Font(R.font.noto_sans_cjk_kr_regular),
    Font(R.font.noto_sans_cjk_kr_medium, FontWeight.Medium),
    Font(R.font.noto_sans_cjk_kr_bold, FontWeight.Bold)
)

Typography는 이렇게 정의할 수 있다. defaultFontFamily는 따로 FontFamily가 없다면 생략가능하다.

val typography = Typography(
    defaultFontFamily = NotoFontFamily,
    h1 = TextStyle(
        fontWeight = FontWeight.Light,
        fontSize = 96.sp,
        letterSpacing = 1.2.sp
    )
)

앱의 각 구성요소마다 이렇게 텍스트 스타일을 미리 지정해둘 수 있다. 정의 가능한 요소들은 아래 사진과 같다.

 

Typography parameters

Shape

윈도우 UI의 타일 디자인처럼 모든 버튼이 각져있는 앱은 드물다. (미안해 마소...!!ㅋㅋㅋ) 다들 저마다의 Radius를가진 둥글둥글한 버튼들이나, 카드 컴포넌트를 이용한다.

 

(플레이오 앱 UI중 일부) 둥글둥글한 모서리가 여기저기 있다.

 

원래는 Shape들을 drawables 아래에 xml로 만들어서 배경으로 넣어주고...했는데 Compose에는 Shape라는 파라미터를 정의해주면 알아서 둥글게 처리된다. 통일감있는 둥글기 처리를 위해 이 Shape의 Radius값들도 테마로서 정의할 수 있다. 이제 대충 Shape.kt 정도에 저장하는게 custom임을 아셨으리...

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

아까 잠깐 MaterialTheme에서 보았던 것 처럼 테마에서 파라미터로 넘겨서 정의하고, Composable 함수에서 MaterialTheme.shapes.large 이렇게 접근해서 적용시킬 수 있다.


참고자료

https://medium.com/androiddevelopers/under-the-hood-of-jetpack-compose-part-2-of-2-37b2c20c6cdd

https://infinum.com/the-capsized-eight/jetpack-compose-framework

https://github.com/vipulasri/JetInstagram