[Android] Jetpack Compose를 알아보자

유민국·2023년 11월 9일
0
post-thumbnail

android Jetpack Compose 튜토리얼
Jetpack Compose : 네이티브 Android UI를 빌드하기 위한 최신 도구 키트

  • 직관적인 Koltin API로 Android에서의 UI 개발을 간소화 하고 가속화 한다.

📘 Jetpack Compose

Jetpack Compose는 선언적 UI 프레임워크로, Android 앱의 사용자 인터페이스를 빌드하고 관리하기 위한 새로운 방식을 제공한다.

🌟특징

1] 선언적 DSL (Domain-Specific Language): Compose는 Kotlin 기반의 DSL을 사용하여 UI를 선언한다.

  • UI를 어떻게 나타낼지를 코드로 직접 표현
  • 코드가 간결하고 가독성이 높아진다.

2] 상태 자동 추적: 상태가 변경되면 Compose가 자동으로 새로운 UI를 계산하고 업데이트한다.

  • UI 업데이트 로직을 간소화
  • 개발자가 UI 상태에 집중할 수 있도록 도와준다.

3] 핫 리로딩 (Hot Reloading): Compose는 핫 리로딩을 지원하여 앱을 다시 빌드하지 않고도 UI 변경 사항을 실시간으로 확인할 수 있습니다.

  • 더 빠른 개발과 디버깅이 가능해진다.

4] 강력한 조합성: Compose에서는 UI를 작은 조각으로 분할하고 이러한 조각들을 조합하여 복잡한 UI를 만들 수 있다.

  • 코드의 재사용성을 높이고 유지보수를 쉽게 만든다.

5] 자동 메모리 최적화: Compose는 기본적으로 효율적인 메모리 관리를 제공한다.

  • 변경사항이 없는 부분은 다시 계산되지 않으며, 필요한 경우에만 업데이트가 이루어진다.

6] 풍부한 내장 컴포넌트 및 테마: Compose는 풍부한 내장 컴포넌트를 제공하며, 미리 정의된 테마를 사용하여 앱의 디자인을 일관되게 구성할 수 있다.

7] 기존 뷰 시스템과의 통합: Compose는 기존의 안드로이드 뷰 시스템과 통합되어 사용될 수 있다.

  • 점진적으로 앱을 마이그레이션할 수 있따.
  • 기존 뷰 기반의 앱에 Compose를 일부 통합할 수 있다.

8] 확장 가능한 API: Compose는 개발자가 필요에 따라 사용자 정의 컴포넌트를 만들고 확장할 수 있는 API를 제공한다.

📘 Composable functions

컴포저블을 만드려면 @Composable 어노테이션을 추가한다.

@Composable 함수 안에서는 Jetpack Compose의 DSL을 사용하여 UI를 정의한다.

  • 코드로 UI의 모양과 동작을 선언하는 간결하고 가독성이 높은 방식을 제공한다.
  • 프로그래매틱 방식으로 앱의 UI를 정의할 수 있다.

kotlin의 다른 함수처럼 사용이 가능하다(ex. for문 안에 구문으로 사용가능)

상태의 불변성: @Composable 함수에서 사용되는 데이터는 불변성을 유지해야한다.

  • 변경 가능한 상태를 사용하는 것이 아니라 새로운 상태를 생성하여 업데이트하는 방식이다.
  • 상태 자동 추적

📌 리컴포지션이란?

데이터가 변경될 때마다 다시 실행되어 UI를 업데이트한다

  • 상태 변화에 따라 자동으로 UI가 갱신되는 메커니즘을 제공

Compose는 데이터(상태)가 변경된 구성요소만 다시 구성하고 영향을 받지 않는 구성요소는 다시 구성하지 않고 건너뛰도록 개별 컴포저블에서 필요한 데이터를 확인한다.

import androidx.compose.runtime.Composable

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MessageCard("Android")
        }
    }
}

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

컴포저블 재사용

재사용할 수 있도록 컴포저블로 만들어서 사용하도록 한다.

  • 기본적으로 빈 수정자(modifier: Modifier = Modifier) 매개변수를 포함하는 것이 좋다.
@Composable
private fun MyApp(modifier: Modifier = Modifier) {
    Surface(
        modifier = modifier,
        color = MaterialTheme.colorScheme.background
    ) {
        Greeting("Android")
    }
}

setContent

setContent 블록은 Composable 함수가 호출되는 Activity의 레이아웃을 정의한다.
Jetpack Compose는 Kotlin 컴파일러 플러그인을 사용하여 Composable 함수를 앱의 UI요소로 변환해준다.

@Preview

@Preview

  • 해당 Composable 함수 UI를 미리 확인할 수 있다.
  • @Preview내의 컴포저블 함수에는 매게변수 사용하지 않도록 한다.
@Preview(name = "Light Mode")
@Preview(
    uiMode = Configuration.UI_MODE_NIGHT_YES,
    showBackground = true,
    name = "Dark Mode"
)
fun PreviewMessageCard(){...}

showBackground = true를 통해 미리보기의 배경화면을 표시할 수 있다
widthDp를 통해 너비를 조절 할 수 있다.
uiMode = UI_MODE_NIGHT_YES를 통해 다크 모드로 미리보기를 볼 수 있다.

  • 위의 예시처럼 한 함수에 여러줄을 작성하면 다양한 미리보기를 확인할 수 있다.

📘 계층구조를 통해 UI 구성하기

Compose에서는 다른 Composable 함수끼리 계층 구조를 통해 UI를 만들 수 있다.

@Composable
fun MessageCard(msg: Message) {
    // Add padding around our message
    Row(modifier = Modifier.padding(all = 8.dp)) {
        Image(
            painter = painterResource(R.drawable.profile_picture),
            contentDescription = "Contact profile picture",
            modifier = Modifier
                // Set image size to 40 dp
                .size(40.dp)
                // Clip image to be shaped as a circle
                .clip(CircleShape)
        )

        // Add a horizontal space between the image and the column
        Spacer(modifier = Modifier.width(8.dp))

        Column {
            Text(text = msg.author)
            // Add a vertical space between the author and message texts
            Spacer(modifier = Modifier.height(4.dp))
            Text(text = msg.body)
        }
    }
}

레이아웃 구성

컴포저블을 장식하거나 구성하기 위해 Compose는 Modifier를 사용한다.

  • 컴포저블의 크기, 레아아웃, 모양을 변경하거나 클릭 리스너를 다는 등 상위 수준 상호작용을 추가할 수 있다

Modifier

Modifier는 Jetpack Compose에서 UI 요소에 적용되는 속성을 정의하는 데 사용되는 클래스이다.

Modifier는 Compose에서 UI를 설계하고 레이아웃을 지정하는 역할을 한다.

여러 Modifier 함수를 조합하여 UI 요소의 크기, 색상, 패딩, 정렬 등을 지정할 수 있다.

Modifier 함수

📘 Compose에서 Magerial Design

Compose의 많은 UI 요소가 Material Design을 즉시 사용 가능하도록 구현한다.

Material Design 사용

  • Material Design은 Color, Typography, shape의 세 가지 핵심 요소를 중심으로 이루어진다.
  • 📌 Material Design의 색상, 텍스트, 배경을 사용하면 Jetpack Compose는 기본적으로 다크 테마에 반응할 수 있다.(테마 색상 선택은 IDE로 생성된 Theme.kt파일에 정의 되어 있다.)

MaterialTheme

  • Material 디자인 사양의 스타일 지정 원칙을 반영한 테마
  • Empty Activity로 앱을 만들면 생성되는 BasicsCodelabTheme는 내부적으로 MaterialTheme를 래핑하므로 모든 하위 컴포저블에서 MaterialTheme의 세 가지 속성(colorScheme, typography, shapes)를 가져올 수 있다.

copy : 일반적으로 MaterialTheme의 스타일을 유지하는게(색상, 모양, 글꼴 등) 좋지만 만약 일부분을 수정하고 싶다면 copy를 통하여 수정이 가능하다.

Text(
	text = name,
	style = MaterialTheme.typography.headlineMedium.copy(
		fontWeight = FontWeight.ExtraBold
	)
)

MaterialIcon implementation

매터리얼 아이콘
implementation "androidx.compose.material:material-icons-extended:$compose_version"

🎯 목록 UI 만들기 및 애니메이션

메시지 목록 만들기

Compose의 LazyColumnLazeRow컴포저블은 화면에 표시되는 요소만 렌더링하기 때문에 긴 목록에 매우 효과적으로 설계되어 있다.

만든 메시지 목록에 애니메이션 적용

여러 줄인 메시지 상자를 1줄로 만들어두고, 클릭시 메시지 상자를 확대하는 기능을 추가해본다.

  • 메시지의 확장 유무를 추적하기 위해서 remembermutableStateOf 함수를 사용한다.

Modifier의 clickable을 사용하여 메시지 상자의 확장 여부를 컨트롤 한다.
Composable 함수는 remember를 사용하여 메모리에 로컬 상태를 저장하고 mutableStateOf에 전달된 값의 변경사항을 추적할 수 있다.

  • 이 상태를 사용하는 컴포저블 및 하위 요소는 값이 업데이트되면 자동으로 다시 그려지며 이를 리컴포지션이라고 한다.

📌 remember 및 mutableStateOf와 같은 Compose의 상태 API를 사용하여 상태를 변경하면 UI가 자동으로 업데이트된다.

// We keep track if the message is expanded or not in this
// variable
var isExpanded by remember { mutableStateOf(false) }

// We toggle the isExpanded variable when we click on this Column
Column(modifier = Modifier.clickable { isExpanded = !isExpanded }) {
	Text(
		text = msg.author,
		color = MaterialTheme.colors.secondaryVariant,
		style = MaterialTheme.typography.subtitle2
	)

	Spacer(modifier = Modifier.height(4.dp))

	Surface(
		shape = MaterialTheme.shapes.medium,
		elevation = 1.dp,
		) {
			Text(
			text = msg.body,
			modifier = Modifier.padding(all = 4.dp),
			// If the message is expanded, we display all its content
			// otherwise we only display the first line
			maxLines = if (isExpanded) Int.MAX_VALUE else 1,
			style = MaterialTheme.typography.body2
			)
		}
}

전체 코드 확인하기

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Column {
                ComposeTutorialTheme {
                    Conversation(SampleData.conversationSample)
                }
            }
        }
    }
}

data class Message(val author:String, val body:String)

@Composable
fun Conversation(messages: List<Message>) {
    LazyColumn {
        items(messages) { message ->
            MessageCard(message)
        }
    }
}

@Composable
fun MessageCard(msg: Message) {
    // Add padding around our message
    Row(modifier = Modifier.padding(8.dp)) {
        Image(
            painter = painterResource(R.drawable.ic_launcher_background),
            contentDescription = "Contact profile picture",
            modifier = Modifier
                // Set image size to 40 dp
                .size(40.dp)
                // Clip image to be shaped as a circle
                .clip(CircleShape)
                .border(1.5.dp, MaterialTheme.colorScheme.secondary, CircleShape)
        )

        // Add a horizontal space between the image and the column
        Spacer(modifier = Modifier.width(8.dp))

        // We keep track if the message is expanded or not in this
        // variable
        var isExpanded by remember { mutableStateOf(false) }

        // We toggle the isExpanded variable when we click on this Column

        Column(modifier = Modifier.clickable { isExpanded = !isExpanded }) {
            Text(
                text = msg.author,
                color = MaterialTheme.colorScheme.secondary,
                style = MaterialTheme.typography.bodyLarge
            )

            Spacer(modifier = Modifier.height(4.dp))
            Surface(shape = MaterialTheme.shapes.medium, shadowElevation = 1.dp) {
                Text(
                    text = msg.body,
                    modifier = Modifier.padding(all = 4.dp),
                    // If the message is expanded, we display all its content
                    // otherwise we only display the first line
                    maxLines = if (isExpanded) Int.MAX_VALUE else 1,
                    style = MaterialTheme.typography.bodyMedium
                )
            }
        }
    }
}

@Preview(name = "Light Mode")
@Composable
fun PreviewMessageCard() {
    ComposeTutorialTheme {
        Conversation(SampleData.conversationSample)
    }
}
profile
안녕하세요 😊

0개의 댓글