안드로이드 Jetpack Compose! 구글 Codelabs을 통해 알아본다.

안드로이드 Jetpack Compose! 구글 Codelabs을 통해 알아본다.



개인 광고 영역

이 글은 구글 Codelabs Jetpack Compose basics를 기반으로 작성하는 기본 글이다.

Jetpack Compose basics - Codelabs 링크

Android Jetpack Compose는 최신 Android Studio 4.2 Preview에서 사용해볼 수 있다.

필자도 처음 학습하는 Compose이니 구글에서 제공하는 Codelabs을 통해 이 글을 정리해보았다.

Devfest 2020 GDG Korea Android에서 진행한 코드랩 자료


이 글에서 알아볼 내용

  • Jetpack Compose를 알아본다.
  • Android Studio 4.2 Preview를 통해 Compose를 다루어본다.
  • Jetpack Compose basics Codelabs을 익힌다.


Jetpack Compose 영상을 추가해요


Jetpack Compose

Jetpack Compose - 링크는 기본 UI를 구축하기 위한 Android의 최신 도구이다. Kotlin API를 활용해 더 적은 코드로 UI를 작성 가능하다고 한다.

구글 공식 사이트에서는 아래 4가지를 장점을 소개하고 있다.

  • Less code

간단한 코드로 더 많은 작업을 할 수 있고, 낮은 버그율 및 유지 관리가 쉽다.

  • Intuitive

UI 관련 코드를 작성하면 나머지 처리를 Compose가 처리하며, 변경 상태에 대한 UI 갱신을 자동으로 처리해 준다.

  • Accelerate Development

기존 코드와 호환이 가능하여, 원하는 대로 작업 가능하고 실시간 미리 보기를 Android Studio를 통해 지원한다.

  • Powerful

Android 플랫폼 API에 직접 액세스하고, 머트리얼 디자인, Night 모드, 애니메이션 등을 기본적으로 지원한다.

사이트에 정리된 내용으로 충분히 좋아 보인다. 이 글을 통해 기존 xml을 어느 정도 대체 가능한지 알 수 있어 보인다. 아직은 한참 개발 중인 알파버전이라 추후에 얼마나 강력해질지 기대된다.


Android Studio 4.2에서 생성해 주는 코드

Android Studio 4.2부터 새로운 프로젝트 선택 시 새로운 레이아웃에 Empty Compose Activity가 추가되어 Compose 샘플 코드를 볼 수 있다.

Android Studio 4.2의 새로운 프로젝트를 선택해 Empty Compose Activity 프로젝트를 생성한다.

sample_01

새로운 프로젝트를 생성하면 기존과 다르게 레이아웃이 없고, 코드로 만들어진 Compose 샘플이 나온다.

MainActivity.kt를 열면 아래와 같은 Compose 코드가 나온다. 이 부분을 부분부분 살펴보도록 한다.

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyApplicationTheme {
                // A surface container using the 'background' color from the theme
                Surface(color = MaterialTheme.colors.background) {
                    Greeting("Android")
                }
            }
        }
    }
}

@Composable
fun Greeting(name: String) {
    Text(text = "Hello $name!")
}

@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
    MyApplicationTheme {
        Greeting("Android")
    }
}

컴포즈는 크게 3 가지로 나뉜다.

  • 위젯을 포함하는 Composable 함수
  • Preview를 하기 위한 Preview Composable 함수
  • setContent 람다 표현식으로 실제 화면에 노출하는 코드


TextView를 만드는 부분

화면에 노출할 TextView의 구성은 아래와 같이 작성되어 있다.

이 함수는 새로운 이름이 들어오면 Hello 로 시작하는 Text가 만들어진다. 어노테이션으로 @Compose가 추가되어 있다. Compose 어노테이션이 붙으면 이는 Compose에서 만들어질 UI를 말한다.

@Composable
fun Greeting(name: String) {
    Text(text = "Hello $name!")
}


Preview 하는 부분

프리뷰를 위한 코드이다. 이는 xml에서 정의했던 tools에 해당한다고 보면 되겠다. 실제 apk를 설치하고 동작하는 화면에서는 이에 대한 영향을 받지 않기 때문이다.

Preview에 Greeting을 하나 더 추가해 엉망으로 만들었다. 하지만 프리뷰에서 만 사용될 뿐 실제 앱에는 영향 미치지 않는다.

sample_02

아래 코드에서 사용하는 어노테이션은

  • @Preview : Preview 할 함수에 이 어노테이션을 사용한다.
  • @Composable : Preview라고 하더라도 Composable 임을 나타내야 하기에 필요하다.

Preview에는 일반적인 변수 파라메터가 추가될 수 없다. 추가할 때는 정해진 룰이 있어 아래와 같이 오류가 발생한다. 오류 내용 상 @PreviewParameter를 추가로 사용해야 한다.

Composable functions with non-default parameters are not supported in Preview unless they are annotated with @PreviewParameter.

@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
    MyApplicationTheme {
        Greeting("Android")
        Greeting("Test")
    }
}

@PreviewParameter를 사용하는 방법은 다른 블로그 글을 링크한다.

Sample Data in Compose Previews - 링크 영문


화면을 구성하는 코드들

위에는 @Composable를 이용해 View를 만들고, 이를 테스트하기 위한 @Preview를 살펴보았다. 그럼 실제 안드로이드 화면에 노출하기 위한 코드를 살펴보자.

override fun onCreate(savedInstanceState: Bundle?) {
    // 생략
    setContent {
        MyApplicationTheme {
            // A surface container using the 'background' color from the theme
            Surface(color = MaterialTheme.colors.background) {
                Greeting("Android")
            }
        }
    }
    // 생략
}

컴퍼즈 UI를 구성하는 람다 표현식은 아래와 같다.

  • setContent는 필수이다. xml을 사용하거나, 커스텀 뷰를 사용할 때는 setContentView()을 사용했었는데 이와 동일하다. 람다 표현식으로 만들어진 setContent에 Compose UI를 구성해야 한다.
  • MyApplicationTheme는 Theme 정보를 정의한다. 기본 샘플에서는 Theme.kt에 정의되어 있는 MaterialTheme를 사용하고 있음을 알 수 있다. 이 함수는 Codelabs의 설명에 나오니 그때 자세히 알아본다.
  • Surface는 Greeting을 감싸는 뷰에 해당한다. 여기서는 크기를 정하지 않고, background 색상을 정의하고 있다. 역시 람다 표현식이다.
  • Greeting는 Hello Android를 표현하기 위한 TextView이다. 공통으로 사용할 TextView에 해당한다.

MyApplicationTheme의 구성은 아래와 같다.

darkTheme를 통해 다크 테마의 정보를 외부에서 설정하거나, default 변수로 isSystemInDarkTheme을 가져와 처리한다. DarkTheme는 다크 테마 여부에 따라 DarkColorPalette, LightColorPalette을 선택해 사용한다.

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

    MaterialTheme(
        colors = colors,
        typography = typography,
        shapes = shapes,
        content = content
    )
}

이를 실행하면 2가지 테마 모두 대응된다. 왼쪽이 다크 테마, 오른쪽이 주간 테마 모드이다.

sample_03


Codelabs을 이어가보자

샘플 코드를 통해 컴포즈를 알아보았다. 베이직 Codelabs은 위에서 다룬 기본 샘플과 Declarative UI, 상태를 처리하는 방법, Flexible layouts, 테마 다루기를 알아볼 수 있다.


Declarative UI

Codelabs을 따라서 작성한 글이다.

기본으로 생성된 Greeting 코드에 Surface를 추가해 노란색을 추가했다.

여기서의 Surface는 Text를 감싸고 있어, Text 아래 배경색을 Yellow로 변경하게 된다.

@Composable
fun Greeting(name: String) {
    Surface(color = Color.Yellow) {
        Text(text = "Hello $name!")
    }
}

padding을 추가할 수 있다. modifier를 이용하면 여러 개의 수정을 이어갈 수 있는데, 내부적으로 return을 Modifier을 이어갈 수 있도록 구성해두었다.

여기서는 Modifier.padding을 추가해 Text에 padding을 추가했다.

@Composable
fun Greeting(name: String) {
    Surface(color = Color.Yellow) {
        Text(text = "Hello $name!", modifier = Modifier.padding(24.dp))
    }
}

아래처럼 노란색 위에 padding 만큼 늘어난 TextView를 확인할 수 있다.

sample_04


Compose 재사용

xml에서도 재사용을 위한 레이아웃을 정의하고 include를 활용해 View를 추가하는 경우가 많다.

Compose 역시 재사용 가능하다. 함수 형태로 재사용이 가능한 UI를 구성할 수 있는데 우리가 잘하고 있는 함수 분리를 잘 활용해 더 잘 수정하고, 사용이 편한 형태로 분리할 필요가 있다.

함수를 분리할 때는 @Composable 어노테이션을 추가하여 확장해야 하고, 필요한 함수를 잘 정의해야 한다. Composable 함수에서도 일반적인 함수들을 호출하여 사용하는 게 가능하다. 이들은 화면을 구성하지 않아도 동작하는데 문제없던 Util 형태의 함수들일 것이다. Util 함수들에까지 @Composable을 붙일 필요는 없다.

// Composable에서만 사용하는것이 아니므로 @Composable을 추가할 필요는 없다.
fun printName(name: String) {
    android.util.Log.d("TEMP", "name $name")
}

@Composable
fun Greeting(name: String) {
    Surface(color = Color.Yellow) {
        printName("Hello $name!") // printName 추가
        Text(
            text = "Hello $name!",
            modifier = Modifier.padding(24.dp)
        )
    }
}

그리고 재사용성을 높이기 위해서는 특정 클래스 내부에 정의하지 않고, 밖에 정의해야 한다. kotlin Top level 정의를 잘 해두어야 더 많은 곳에서 재사용 할 수 있다.

위에서 작성했던 코드의 재사용 가능성을 높이고, 이 활동과 관련한 Compose UI 로직을 포함한 @Composable MyApp 함수를 추가로 만들었다.

그리고 Greeting 함수의 재사용성을 높이기 위해 위에서 추가한 Surface는 제거하고, MyApp으로 이동시켰다.

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyApp()
        }
    }
}

// 재사용을 하기 위한 MyApp을 생성한다.
@Composable
fun MyApp() {
    MyApplicationTheme {
        Surface(color = Color.Yellow) {
            Greeting(name = "Android")
        }
    }
}

@Composable
fun Greeting(name: String) {
    Text(text = "Hello $name!", modifier = Modifier.padding(24.dp))
}

// Preview에서도 MyApp을 가져와 보여준다.
@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
    MyApp()
}

MyApp이 추가되어 Preview와 setContent에서 동시에 활용할 수 있게 되었다. 이런 식으로 분리를 한다면 좀 더 좋은 확장성을 가지는 형태로 작업 가능한데, 이어서 알아본다.


Container 함수 만들기

MyApp은 이제 여러 곳에서 공통으로 사용할 수 있는 Composable이 되었다. 그렇다면 이를 재활용해 부분부분 필요한 커스텀을 할 수 있을까?

매우 간단한 방법으로 활용할 수 있는데, 함수를 전달하도록 수정해 주면 된다.

주의할 것은 Composable 함수는 Unit을 반환하도록 Higher-Order function 정의만 해주면 된다. Composable 함수는 UI 구성 요소를 반환하지 않고, @Composable 어노테이션을 통해 처리하기에 Unit을 반환하는 함수를 하나 추가하면 된다고 한다.

fun MyApp(content: @Composable () -> Unit) { ... }

content: @Composable () -> Unit을 추가해 MyApp의 재사용성을 높이게 만들었다. MyApp을 호출하면 원하는 형태로 View를 그릴 수 있게 되었고, 정해진 Theme와 Surface를 활용할 수 있게 되었다.

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyApp {
                // MyApp을 람다식 표현으로 사용하고, 원하는 텍스트를 노출한다.
                Greeting(name = "Android")
            }
        }
    }
}

// 어노테이션 @Composable을 추가해 content를 밖에서 작업할 수 있도록 변경한다.
@Composable
fun MyApp(content: @Composable () -> Unit) {
    MyApplicationTheme {
        Surface(color = Color.Yellow) {
            content()
        }
    }
}

@Composable
fun Greeting(name: String) {
    Text(text = "Hello $name!", modifier = Modifier.padding(24.dp))
}

@Preview(showBackground = true, name = "TextPreview")
@Composable
fun DefaultPreview() {
    MyApp {
        // MyApp을 람다식 표현으로 사용하고, 원하는 텍스트를 노출한다.
        Greeting(name = "Preview Android")
    }
}

결과는 아래와 같다. Preview와 Android에서 서로 다른게 노출함을 알 수 있다.

sample_05


여러 줄의 레이아웃을 만들어보자.

이번엔 여러 줄의 레이아웃을 만들어본다. Column을 추가해 간단하게 여러 줄 표현이 가능한데, 아래와 같은 UI를 만들도록 한다.

sample_06

이를 표현하기 위한 코드는 많지 않다. Column과 위에서부터 사용하던 Greeting 함수를 사용하고, 라인을 그어주기 위한 Divider를 추가한다.

  • Column : 항목을 순서대로 배치하기 위해 사용한다.
  • Divider : 선 긋기 가능한 Compose 함수이다.

MyColumnScreen을 새롭게 추가하고, Column 블록을 하나 추가한다. Column 안에 Greeting과 Divider를 순서대로 정의한다.

@Composable
fun MyColumnScreen() {
    Column {
        Greeting(name = "Line one")
        Divider(color = Color.Black)
        Greeting(name = "Line Two")
    }
}

다시 Greeting을 수정하겠다.

지웠던 Surface를 다시 추가하고, modifier를 이용해 fillMaxWidth을 추가했다. 가로로 가득 찬 UI를 만들기 위해서 추가했다.

fillMaxWidth가 없다면 Text 넓이만큼만 차는 노란색을 볼 수 있으니 참고하시길.

@Composable
fun Greeting(name: String) {
    Surface(color = Color.Yellow, modifier = Modifier.fillMaxWidth()) {
        Text(text = "Hello $name!", modifier = Modifier.padding(24.dp))
    }
}

기존 PreviewsetContent의 코드도 기존 MyApp 대신 MyColumnScreen을 사용할 수 있도록 변경했다.

setContent {
    MyColumnScreen()
}
// 생략

@Preview(showBackground = true, name = "TextPreview")
@Composable
fun DefaultPreview() {
    MyColumnScreen()
}


리스트 형태로 받아서 그려보자.

MyColumnScreen은 확장성이 떨어진다. 외부에서 이름들을 받고, Greeting과 Divider를 순서대로 노출할 수 있는 편이 더 좋아 보인다.

이를 위해 @Composable 함수에 parameter로 List를 추가해 외부에서 전달받을 수 있는 형태로 재구성했다.

forEach 문을 이용해 name의 개수만큼 화면에 노출하도록 수정했다.

여기서는 Line One, Line Two를 추가했기에 기존과 동일한 화면이 노출된다.

@Composable
fun MyColumnScreen(names: List<String> = listOf("Line One", "Line Two")) {
    Column {
        names.forEach {
            Greeting(name = it)
            Divider(color = Color.Black)
        }
    }
}


상태 값을 가지고 처리해보자.

이 내용은 Codelabs 설명을 참고해 새로 작성했다.

상태 변경에 대한 반응은 Compose의 핵심 기는 중 하나라 한다. Compose를 활용한 앱은 Composable 함수를 호출하여 데이터를 포함한 UI를 갱신한다. 상태 변경이 일어나면 이를 자동으로 함수 호출하는 데 이를 recomposing이라 한다. 또한 Compose는 데이터가 변경된 구성 요소 만 재구성하고 변경 사항이 없는 UI에 대해서는 변경하지 않도록 개별 구성 요소에 필요한 데이터를 살펴본다고 한다.

RecyclerView에서 사용한 Diff.Util과 유사하다고 보면 되겠다. 수정사항이 있는 View만 재구성한다.

내부적으로 Compose는 커스텀 Kotlin 컴파일러 플러그인을 활용해 기본 데이터가 변경될 때 구성 가능한 함수를 다시 호출하고, UI 계층을 업데이트를 한다.

예를 들면 Composable 함수 MyApp을 호출하면 하드코딩 된 Greeting은 UI tree에 한 번 추가되고 변하지 않는다. 결국 본문인 MyApp이 재구성되어도 이미 추가되었기에 Greeting은 그대로 사용된다.

@Composable
fun MyApp() {
    Greeting(name = "Android")
}

그렇다면 이런 컴포즈에 상태를 추가하려면 어떻게 해야 할까?

Composable에 상태를 추가하려면 수정 가능한 mutableStateOf 함수를 사용해 추가할 수 있다. 모든 재구성에 대해 다른 상태를 가지지 않게 하려면 remember를 이용해 한 번 더 감싸 처리할 수 있다. 이러한 state를 가지는 composable을 여러 곳에서 사용하는 경우 각각은 따로 state 정보를 가지게 된다.

Composable 함수는 자동으로 subscribe 하고, 상태가 변경되면 subscribe를 통해 알림을 받아 화면을 갱신한다.

이제 샘플을 하나 추가한다. 사용자가 버튼을 클릭 한 횟수를 추적하는 카운터를 하나 추가하고, 이를 onClick 이벤트에서 처리한다. 그럼 아래와 같이 동작할 테고,

sample_07

이를 작업하는데 필요한 코드는 다음과 같다.

remember 람다 표현식에 mutableStateOf을 추가해 준다. mutableStateOf는 숫자 0을 가지고 있고, Button click 이벤트가 발생하면 value 체인지를 하고, 즉시 갱신한다.

@Composable
fun Counter() {
    val count = remember { mutableStateOf(0) }

    Button(onClick = {
      count.value++
    }) {
        Text("Clicked ${count.value} times")
    }
}

참고로 Compose Material Design Buttons 사양에 따라 Button, OutlinedButton 및 TextButton과 같은 다양한 Button을 제공한다. 여기서는 클릭 횟수를 보여주는 텍스트가 있는 버튼을 이용하고 있다.

Button은 count.value를 읽어 Button이 변경될 때마다 재구성되고, 새로운 count 값을 노출하도록 구성했다.

화면에서는 아래와 같이 32dp의 두께를 가진 Divider와 Counter를 추가하여 화면에 노출하게 된다.

@Composable
fun MyColumnScreen(names: List<String> = listOf("Line One", "Line Two")) {
    Column {
        names.forEach {
            Greeting(name = it)
            Divider(color = Color.Black)
        }
        Divider(color = Color.Transparent, thickness = 32.dp)
        Counter()
    }
}


Source of truth

이 내용은 Codelabs 설명을 참고해 새로 작성했다.

위에서 작성한 샘플에서 state 값이 유용하게 사용되려면 함수에 parameter로 포함되어 있는 게 좋다. 그래야 사용하는 쪽에서 제어하고 사용 방법 역시 다를 수 있다. 이런 부분을 state hoisting이라 표현한다.

이런 State hoisting을 사용함으로써 함수 내부 상태를 제어할 수 있게 만들 수 있다. 제어된 Composable 함수의 parameter를 이용해 상태를 노출하고, 제어하는 Composable에서 외부 인스턴스화해 이를 사용한다. 이렇게 작업함으로써 상태 복제와 버그를 줄일 수 있고, Composable 재사용 성과 테스트를 쉽게 할 수 있다.

사용자는 모든 상태에 관심을 가지지 않는다. 예를 들면 스크롤에서 scrollerPostion 정보는 필요할 수 있지만, maxPostion 정보는 불필요한 경우도 있다. 이런 상태들을 제공해 주고, 사용하는 쪽에서 제어하도록 만들 수 있다.

위에서 작성한 Counter 예제에 parameter를 추가해 사용하는 쪽에서 제어할 수 있도록 만들어본다. 여기서는 count와 updateCount를 추가하여 Counter의 활용성을 높일 수 있다.

@Composable
fun MyColumnScreen(names: List<String> = listOf("Line One", "Line Two")) {
    val counterState = remember { mutableStateOf(0) }

    Column {
        names.forEach {
            Greeting(name = it)
            Divider(color = Color.Black)
        }
        Divider(color = Color.Transparent, thickness = 32.dp)
        Counter(counterState.value) {
            counterState.value = it
        }
        Divider(color = Color.Transparent, thickness = 32.dp)
        Counter(counterState.value) {
            counterState.value = it
        }
    }
}

@Composable
fun Counter(count: Int, updateCount: (Int) -> Unit) {
    Button(onClick = { updateCount(count + 1) }) {
        Text("Clicked $count times")
    }
}


Flexible layouts

지금까지는 항목을 수직 순서대로 배치했다. 약간의 수정을 통해 레이아웃을 가로로 배치할 수 있다.

Row와 Column을 차례로 배치할 수 있고, 만약 특정 사이즈를 지정하고 싶다면 weight를 이용해 수정할 수 있다.

또는 다른 컨텐츠는 상단에 두고 버튼을 화면에 배치하고 싶다면 다음 단계를 따라 수행하면 된다.

  • 새로운 Column을 추가하고, weight를 최대로 사용할 수 있도록 수정한다. 여기에 포함된 Greeting과 Divider는 최대한 많은 공간을 차지하는 Column 안쪽에 위치 시킨다. 그리고 외부의 Column의 높이를 최대로 조절한다.
  • Counter는 최대치의 Column 밖에 포함하게 한다.(inflexible 위치에 포함 시킨다.)
@Composable
fun MyColumnScreen(names: List<String> = listOf("Line One", "Line Two")) {
    val counterState = remember { mutableStateOf(0) }

    // 최대 높이를 활용할 수 있도록 fillMaxHeight 추가
    Column(modifier = Modifier.fillMaxHeight()) {
        // weight 1로 최대 넓이를 가지도록 Column 추가
        Column(modifier = Modifier.weight(1f)) {
            names.forEach {
                Greeting(name = it)
                Divider(color = Color.Black)
            }
        }

        Divider(color = Color.Transparent, thickness = 32.dp)
        Counter(counterState.value) {
            counterState.value = it
        }
        Divider(color = Color.Transparent, thickness = 32.dp)
        Counter(counterState.value) {
            counterState.value = it
        }
    }
}

sample_08

Compose에서 Kotlin 활용하는 또 다른 예로, if else 문을 사용해 외부 state 정보에 따라 다르게 노출하도록 수정했다.

여기서는 5보다 크면 Green 색상을 사용하도록 했다.

@Composable
fun Counter(count: Int, updateCount: (Int) -> Unit) {
    Button(
        onClick = { updateCount(count + 1) },
        backgroundColor = if (count > 5) Color.Green else Color.White
    ) {
        Text("Clicked $count times")
    }
}

sample_09


Theming your app

마지막으로 테마를 다룬다. 처음 안드로이드 스튜디오에서 제공하는 기본 샘플을 보았었다. 이 글에서는 MyApplicationTheme을 기본으로 활용했다.

참고 : 원래 Codelabs에서는 Theme를 따로 다루지는 않고, 코드 작성 만 살펴보고, 여기에서 Theme를 살펴본다.

Theme 역시 다른 Composable과 마찬가지로 lambdas 식 표현으로 사용한다. MyApplicationTheme가 이에 해당한다.

이 테마는 Theme.kt 파일에 정의되어 있는데, 여기에서 볼 수 있듯 기본 테마로 MaterialTheme를 사용함을 알 수 있다. MaterialTheme는 머트리얼 디자인 - 링크 스타일링 원칙을 반영하는 Composable 함수이고, 이 정보를 통해 화면에 스타일 정보를 노출한다. 이를 사용하는 방법은 아래와 같다.

setContent {
    MyApplicationTheme {
        Greeting(name = "Android")
    }
}

MyApplicationTheme을 사용하면 Greeting의 Text에도 이를 영향받아 사용한다.

직접 MaterialTheme의 속성을 지정하고 싶다면 아래처럼 Style을 지정할 수 있다.

@Composable
fun Greeting(name: String) {
    Surface(color = Color.Yellow, modifier = Modifier.fillMaxWidth()) {
        Text(
            text = "Hello $name!",
            modifier = Modifier.padding(24.dp),
            style = MaterialTheme.typography.h1
        )
    }
}

style을 MaterialTheme.typography의 속성을 직접 지정하여 변경했다. MaterialTheme.typography에는 h1, body1, subtitle1과 같은 정의된 스타일이 있다. 여기서는 테마의 정의된 h1을 사용했고, 아래처럼 노출된다.

sample_10

참고로 copy를 이용해 typography를 변경할 수 있다. 예를 들면 MaterialTheme.typography.h1의 속성을 그대로 사용하고 색상만 변경한다면 MaterialTheme.typography.h1.copy.copy(color = Color.Red)처럼 적용할 수 있다.


직접 App theme 생성하기

새로운 앱 Theme도 생성할 수 있다. 재사용성을 높이기 위해서 parameter에 함수를 넘겨주도록 구성한다.

여기서 중요한 점은 재사용성을 높이려면 @Composable 함수에서 다시 하위의 @Composable을 취하도록 만들어야 한다. 그래서 아래처럼 content에 @Composable을 추가로 명시해 주어야 한다.

@Composable
fun BasicsCodelabTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    content: @Composable () -> Unit // 중요
) {
    // TODO
}

그리고 MaterialTheme에는 Colortypography에 대한 구성을 가진다. 아래처럼 DarkColor와 LightColors를 지정해 줄 수 있다.

private val DarkColors = darkColors(
    primary = purple200,
    primaryVariant = purple700,
    secondary = teal200
)

private val LightColors = lightColors(
    primary = purple500,
    primaryVariant = purple700,
    secondary = teal200
)

@Composable
fun MyAppTheme(content: @Composable () -> Unit) {
    val colors = if (darkTheme) {
        DarkColors
    } else {
        LightColors
    }

    MaterialTheme(colors = colors) {
        content()
    }
}


마무리

여기까지 Android Compose Basic Codelabs을 통해 살펴본 Jetpack Compose이다. 코드로 작성하는 Compose가 생소할 수 있다. 아직은 알파 버전이라 엄청 크게 유용하지는 않을 태고, 새롭게 학습해야 할 내용도 많다. 그래도 구글에서 다양한 Codelabs을 준비해뒀으니 학습을 추가로 해보는 것도 좋아 보인다.



About Taehwan

My name is Taehwan Kwon. I have developed Android for 6 years and blog has been active for eight years.

Comments