왜 Jetpack Compose 일까?

kkosang·2025년 1월 25일
0

JetpackCompose

목록 보기
1/2

들어가며

소개

이 글은 Compose의 기본 개념UI 렌더링 과정에 관심있는 독자를 대상으로 작성되었습니다.

이 문서를 읽으면

  • Compose의 유래와 기본 개념에 대해 학습할 수 있습니다.
  • 선언형 UI와 명령형 UI의 차이점에 대해 학습할 수 있습니다.
  • Compose의 렌더링 과정에 대해 학습할 수 있습니다.

목차

1. 왜 Compose라고 부르는 것일까?

2. 선언형 UI

  • 선언형과 명령형

3. Compose에서 UI를 그리는 방법

  • Composition
  • Layout
  • Drawing

4. 마치며

5. 출처

왜 Compose라고 부르는 것일까?

linear

Jetpack Compose라는 이름을 처음 들었을 때, 그 의미가 무엇일지 궁금했던 적이 있을겁니다. "Compose"는 구성하다, 조합하다라는 뜻을 가지고 있습니다. 그럼 왜 Android UI 프레임워크인 Jetpack Compose가 이런 이름을 가졌을까요?

Jetpack Compose는 선언형 방식으로 UI를 구성하는 Kotlin 언어 기반 Android UI 프레임워크입니다. 기존 Android View 시스템과 달리, Jetpack Compose에서는 개발자가 UI를 선언하기만 하면 프레임워크가 상태에 따라 자동으로 화면을 관리하고 렌더링합니다. 이러한 과정에서 UI를 구성하는 방식이 "Compose"와 밀접한 관계를 가지기 때문에 이러한 이름이 붙여졌습니다.

전통적인 객체 지향 프로그래밍(OOP)에서는 상속(Inheritance)이 중요한 개념으로 여겨졌습니다. 상속을 통해 기존 클래스의 기능을 확장하거나 변경할 수 있었죠. 하지만 최근에는 상속보다는 조합을 사용하라는 이야기를 들어보셨을 겁니다. 이와 관련한 내용은 여기에서 참고하실 수 있습니다.

조합이라는 개념은 객체를 재사용 가능한 구성 요소로 나누고 이를 결합하여 더 큰 객체를 만들어가는 방법입니다. Jetpack Compose는 이러한 조합의 개념을 UI 개발에서 적용하고 있습니다. UI를 하나의 큰 계층 구조로 만들지 않고 재사용 가능한 Composable 함수로 나누어 정의하고 있습니다. 이러한 Composable 함수를 조합하여 화면을 구성하기 때문에 Compose라는 이름이 붙여졌습니다.

선언형 UI

Jetpack Compose에서는 계층 구조를 활용한 상속 방식 대신 Composable을 조합하여 화면을 구성하는 것을 알 수 있었습니다. 그렇다면 이번에는 선언형 UI(Declarative UI)란 무엇이며 Jetpack Compose에서 어떻게 구현되고 있는지 알아보겠습니다.

명령형과 선언형

명령형(Imperative) 방식은 무엇(What)어떻게(How)를 중심으로 개발자가 모두 작성해주어야합니다. 기존의 Android View 시스템이 이 방식에 해당합니다.

다음은 UI를 기존의 Android XML로 구현한 코드입니다.

<TextView
    android:id="@+id/tv_greeting"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Hello, Kkosang!" />
val textView = findViewById<TextView>(R.id.tv_greeting)
textView.text = "Hello, James!"

반면 선언형(Declarative) 방식은 무엇(What)에만 집중하고 "어떻게(How)"는 프레임워크가 처리하는 방식입니다. 즉 화면에 무엇을 보여줄지 선언하고 구현에 대한 세부사항은 분리한 채, 프레임워크를 통해 렌더링하고 업데이트 하는 방식입니다. 이러한 방법을 Jetpack Compose에서 사용하고 있습니다.

다음은 UI를 Jetpack Compose로 구현한 코드입니다.

// 명령형 UI
@Composable
fun Greeting(name: String) {
    Text(text = "Hello, $name!")
}

이 코드는 Text라는 Composable 함수를 호출하여 UI를 선언적으로 구성합니다.

UI를 자동으로 업데이트 하기 위해서는 상태를 관리해야 합니다. Compose는 상태를 관찰하고 변경사항이 있을 때 Composable 함수를 다시 호출하여 UI를 갱신합니다. 상태 관리와 관련된 자세한 내용은 다음 글에서 다루도록 하겠습니다.

아래는 명령형 UI와 선언형 UI를 비교하여 정리한 표입니다.

명령형 UI선언형 UI
개발자가 UI 요소를 명시적으로 업데이트UI 상태(State)에 따라 자동 업데이트
화면의 변경 사항을 수동으로 처리 (조건부)상태 기반으로 화면 변경을 자동 처리
코드가 복잡하고 유지보수가 어려움코드가 간결하고 가독성이 높음
예: Android View 기반 XML 코드예: Jetpack Compose

Compose에서 UI를 그리는 방법

Jetpack Compose는 선언형 UI 프레임워크로 Composable 함수를 사용하여 UI를 구성합니다. 그렇다면 Compose는 어떤 과정을 거쳐서 화면에 렌더링할까요? 기존의 Android View 시스템처럼 화면을 그리는 과정은 측정(Measure), 레이아웃(Layout), 그리기(Draw)로 비슷하지만 Compose에서는 Composition이라는 단계로 시작됩니다. 각 단계에서 어떤 방식으로 처리되는지 알아보도록 하겠습니다.

udf 컴포즈

1.Composition

Composition 단계에서는 컴포저블 함수들을 호출하여 UI 트리 구조를 생성합니다. 이때 각 Composable 함수는 트리의 노드가 되어 서로의 부모-자식 관계를 형성합니다.

아래와 같은 프로필 카드 컴포저블 함수가 있다고 가정해보겠습니다.

@Composable
fun ProfileCard() {
    Row {
        Image() // 이미지 컴포넌트
        Column {
            Text(text = "Kkosang") // 텍스트 컴포넌트
            Text(text = "Compose 학습 중") 
        }
    }
}

위의 컴포저블 함수 ProfileCard가 호출되면 트리 구조를 구성합니다.
Row는 부모 노드가 되고 두 개의 자식 노드(Image,Column)를 갖습니다. 자식 노드 중 Column은 아래에 두 개의 Text 컴포저블이 자식 노드로 연결됩니다.

아래는 생성된 트리 구조입니다.

-> Composition

ProfileCard
   ├── Row
   │   ├── Image
   │   └── Column
   │       ├── Text ("Kkosang")
   │       └── Text ("Compose 학습 중")

2. Layout

Layout 단계에서는 Composition 단계에서 생성된 트리 구조를 기반으로, UI 요소의 측정과 배치가 이루어집니다.

트리 구조는 아래와 같은 세 단계를 거쳐서 탐색됩니다.

  • 1. 하위요소 측정
    • 각 노드는 하위 요소가 있는 경우, 요소를 측정하여 크기를 계산합니다.
  • 2. 자체 크기 결정
    • 하위 요소의 크기를 기반으로 자신의 크기를 결정합니다.
  • 3. 하위 요소 배치
    • 결정된 자신의 크기를 기준으로 하위 요소들을 적절히 배치합니다.

탐색 과정이 끝나면, 각 노드에는 할당된 width,height 그리고 배치 될 좌표(x,y)가 있습니다.

ProfileCard의 Layout 단계 과정은 다음과 같습니다.

  1. Row가 자식 노드들을 측정

    • Row는 자신이 포함하는 자식 요소인 Image와 Column을 차례로 측정합니다.
  2. Image 측정

    • Image는 자식이 없는 단순 노드이므로 자신의 크기를 결정하고 이를 Row에 반환합니다.
  3. Column 측정

    • Column은 먼저 자신이 포함하는 두 개의 Text 컴포저블을 측정합니다.
  4. 첫 번째 Text 측정

    • 첫 번째 Text("Kkosang")는 자식이 없는 단순 노드로 자신의 크기를 결정하고 이를 Column에 반환합니다.
  5. 두 번째 Text 측정

    • 두 번째 Text("Compose 학습 중")도 마찬가지로 자신의 크기를 결정하고 이를 Column에 반환합니다.
  6. Column 크기 결정

    • Column은 자식 Text들의 측정 결과를 사용하여 자신의 크기를 결정합니다.
    • 너비: 두 텍스트 중 가장 넓은 값
    • 높이: 두 텍스트의 높이 합
  7. Column 배치

    • Column은 자신의 자식들을 배치합니다. Text들은 수직으로 쌓여 배치됩니다.
  8. Row 크기 결정

    • Row는 측정된 Image와 Column의 결과를 사용해 자신의 크기를 결정합니다.
    • 너비: 자식들의 너비 합
    • 높이: 자식들 중 가장 높은 값
  9. Row 배치

    • Row는 자식들을 배치합니다.
    • Image는 좌측에 Column은 우측에 배치됩니다.

3. Drawing

마지막으로 Drawing 단계에서는 Composition과 Layout 단계를 거쳐 결정된 노드의 크기와 위치 정보를 바탕으로 화면에 UI를 그립니다.
이때 UI를 그리기 위하여 트리 구조를 다시 탐색하며 각 노드의 내용을 화면에 그립니다. 탐색 과정은 최상위 노드에서부터 시작하여 하위 노드로 내려가며 이에따라 UI 요소를 위에서 아래로 그리게 됩니다.

ProfileCard의 Drawing 과정은 아래와 같은 방식으로 처리됩니다.

  1. 최상위 노드 그리기

    • Row와 같은 상위 노드는 스타일을 화면에 그립니다.
  2. 자식 노드 그리기

    • Row는 자식인 Image와 Column을 그리도록 요청합니다.
  3. 순서대로 하위 요소 그리기

    • Image는 콘텐츠를 그립니다.
    • Column은 자신을 그린 뒤 자식 노드로 넘어갑니다.
  4. 자식 노드 반복 처리

    • Column의 첫 번째 Text와 두 번째 Text가 각각 순서대로 그려집니다.

이 과정이 완료되면 최종적으로 화면에 ProfileCard의 모든 요소가 렌더링됩니다.

마치며

Jetpack Compose는 선언형 UI와 단방향 데이터 흐름 패턴을 중심으로 동작하며 이를 통해 보다 직관적인 방식으로 UI를 설계할 수 있습니다. Composition, Layout, Drawing 단계를 통해 효율적으로 UI를 구성하고 렌더링합니다. 또한 변경 사항에 따라 자동으로 화면을 갱신합니다.

Jetpack Compose를 효율적으로 사용하기 위해서는 상태 관리의 개념을 이해하는 것이 중요합니다. 다음 글에서는 상태를 관리하고 UI를 효율적으로 관리하는 방법에 대해 알아보도록 하겠습니다 :)

출처

https://developer.android.com/develop/ui/compose/phases

https://tecoble.techcourse.co.kr/post/2020-05-18-inheritance-vs-composition/

0개의 댓글