Compose 디자인 시스템

GDSC Android·2024년 3월 21일

Compose란?

SwiftUI, Flutter 와 같은 선언형 UI 중의 하나.

장점
1. 간단한 코드
2. 직관적
3. 기존의 모든 코드와 호환
4. Android 플랫폼 API에 직접 액세스하고 머터리얼 디자인, 어두운 테마, 애니메이션 등을 기본적으로 지원

디자인 시스템이란?

디지털 제품 또는 서비스를 개발하는 과정에서 사용되는 일련의 디자인 가이드라인, 구성 요소, 패턴, 및 리소스의 집합
장점 : 인터페이스에서 일관성을 유지할 수 있도록 도와줌

-> Compose 아키텍처를 사용한 일관된 UI 구축

Compose 아키텍처

Compose UI 설계

  • UI 상태가 변경될 때마다 Compose는 변경된 UI 트리 부분을 다시 만듬

단방향 데이터 흐름

: 상태는 아래로 이동하고 이벤트는 위로 이동하는 디자인 패턴

→ 단방향 데이터 흐름을 따라 UI에 상태를 표시하는 컴포저블과 상태를 저장하고 변경하는 앱 부분을 서로 분리 가능

단방향 데이터 흐름을 사용하는 앱의 UI 업데이트 루프

이벤트: UI의 일부가 이벤트를 생성하여 위쪽으로 전달하거나(예: 처리하기 위해 ViewModel에 전달되는 버튼 클릭) 앱의 다른 레이어에서 이벤트가 전달됩니다(예: 사용자 세션이 종료되었음을 표시).
상태 업데이트: 이벤트 핸들러가 상태를 변경할 수도 있습니다.
상태 표시: 상태 홀더가 상태를 아래로 전달하고 UI가 상태를 표시합니다.

💡 Jetpack Compose를 사용할 때 이 패턴 따르면
1. 테스트 가능성
- 상태와 상태를 표시하는 UI를 분리하여 격리 상태에서 더 쉽게 테스트
2. 상태 캡슐화
- 상태는 한곳에서만 업데이트 가능
- 컴포저블의 상태에 관한 정보 소스가 하나뿐 → 일관되지 않은 상태로 인한 버그 가능성 줄어듦
3. UI 일관성
- 관찰 가능한 상태 홀더(StateFlow 또는 LiveData)를 사용함으로써 모든 상태 업데이트가 UI에 즉시 반영됨

→ 분리 및 재사용을 유도하기 위해 각 Composable에는 가능한 한 최소한의 정보 포함

@Composable
fun Header(title: String, subtitle: String) {
    // Recomposes when title or subtitle have changed.
}

@Composable
fun Header(news: News) {
    // Recomposes when a new instance of News is passed in.
}

디자인 원칙

함께 조립할 수 있는 작고 집중된 기능을 제공하는 것

컨트롤

  • 상위 수준의 구성요소는 자동으로 더 많은 작업을 실행하지만 개발자가 직접 제어할 수 있는 정도를 제한 → 더 많은 제어가 필요한 경우 ‘드롭다운’하여 하위 수준의 구성요소 사용
val color = animateColorAsState(if (condition) Color.Green else Color.Red)

// 구성요소를 항상 회색으로 시작해야 한다면 위의 animateColorAsState API로는 시작할 수 없음
//  -> 하위 수준의 Animatable API를 사용하도록 드롭다운
val color = remember { Animatable(Color.Gray) }
LaunchedEffect(condition) {
    color.animateTo(if (condition) Color.Green else Color.Red)
}

→ 상위 수준의 animateColorAsState API 자체는 하위 수준의 Animatable API를 기반으로 빌드 가능
→ 하위 수준의 API 사용이 좀 더 복잡하지만 상위 수준의 API보다 세부적인 제어가 가능

맞춤설정

Material 레이어에서 제공하는 Button

@Composable
fun Button(
    // …
    content: @Composable RowScope.() -> Unit
) {
    Surface(/* … */) {
        CompositionLocalProvider(/* … */) { // set LocalContentAlpha
            ProvideTextStyle(MaterialTheme.typography.button) {
                Row(
                    // …
                    content = content
                )
            }
        }
    }
}

Button은 4가지 구성요소로 조합되어 있다.

  1. Surface : 배경, 도형, 클릭 처리 등을 제공하는 Material Desgin 시스템에 기반한 Compose의 컨테이너
  2. CompositionLocalProvider : 버튼이 사용되거나 중지될 때 콘텐츠의 알파를 변경
  3. ProvideTextStyle : 사용할 기본 텍스트 스타일을 설정
  4. Row : 버튼 콘텐츠의 기본 레이아웃 정책을 제공

⇒ 이 Button에서 매개변수 이외의 요소를 맞춤설정 하려면 수준을 드롭 다운하고 구성요소를 fork

Button에 그라데이션 배경이 필요한 경우
-> Row는 단순한 레이아웃 컨테이너로 개발자가 직접 스타일과 배치를 제어할 수 있다.

@Composable
fun GradientButton(
    // …
    background: List<Color>,
    modifier: Modifier = Modifier,
    content: @Composable RowScope.() -> Unit
) {
    Row(
        // …
        modifier = modifier
            .clickable(onClick = {})
            .background(
                Brush.horizontalGradient(background)
            )
    ) {
        CompositionLocalProvider(/* … */) { // set material LocalContentAlpha
            ProvideTextStyle(MaterialTheme.typography.button) {
                content()
            }
        }
    }
}

Material 개념을 전혀 사용하지 않으려면(예: 맞춤 디자인 시스템을 빌드하는 경우) 기본 레이어 구성요소만 사용하도록 축소

@Composable
fun BespokeButton(
    // …
    backgroundColor: Color,
    modifier: Modifier = Modifier,
    content: @Composable RowScope.() -> Unit
) {
    Row(
        // …
        modifier = modifier
            .clickable(onClick = {})
            .background(backgroundColor)
    ) {
        // No Material components used
        content()
    }
}

정확환 추상화 선택

  • 일반적으로 최상위 수준 구성요소를 기반으로 개발하는 것이 좋음
profile
건국대학교 GDSC 안드로이드 파트의 블로그입니다

0개의 댓글