Jetpack Compose

노준혁·2023년 3월 4일
0
  • Jetpakc Compose는 네이티브 UI를 빌드하기 위한 Android 최신 toolkit
  • 애플리케이션 구축과 관련된 작업을 최소화한다.

  • Rendering
    컴퓨터 그래픽스에서 3차원 모델, 2차원 이미지, 혹은 비디오 프레임 등의 시각적인 요소들을 화면에 출력하기 위해, 컴퓨터의 CPU나 GPU에 의해 그리는 과정

  • Android에서의 Rendering
    화면에 표시할 뷰를 계산하고, 뷰의 변경사항을 반영해 화면에 그리는 과정

    • 안드로이드에서 렌더링이 느릴 경우 앱의 성능이 저하되거나 UI가 끊기는 현상이 발생할 수 있으므로, 효율적인 렌더링 방법을 사용하는 것이 중요
    • 최근에는 Compose이나 Vulkan 등을 이용하여 보다 효율적인 렌더링 방법을 적용하는 추세.

  • Compose elevation
    Compose의 elevation은 뷰의 상대적인 높이를 조절하는 속성
    뷰가 화면에 얼마나 떠있는가를 나타내며, 높이가 높을수록 다른 뷰보다 더 위에 위치함.
    • 이는 뷰의 그림자 효과를 가져오며, 시각적 효과를 표현한다.
    • elevation 값이 높을수록 그림자의 깊이가 깊어짐.
    • 일반적으로 Material Design에 따라 설계된 UI요소에서 사용됨.
    • ex) CardView OR Button 같은 UI 요소에서 elevation으로 그림자 효과를 줌.
    • 0dp(Default) ~ 24dp(최대)
@Composable
fun MyCardView() {
    Card(
        modifier = Modifier
            .fillMaxWidth()
            .padding(16.dp)
            .elevation(8.dp)
    ) {
        // 카드뷰 내용
    }
}

  • Compose remember
    Compose에서 'remember'는 상태를 보존하고 재사용하기 위한 함수
    'remember' 함수를 통해 상태를 유지할 수 있으므로 구성 요소가 다시 렌더링 될 때마다 상태를 새로 고치는 대신 이전에 계산된 값을 다시 사용할 수 있음.

    • remember 함수는 다양한 타입의 상태를 저장할 수 있으며, 람다 함수 내에서 상태를 초기화하고 반환함.
    • 반환된 값을 사용하여 상태를 변경하고 가져올 수 있음.

  • Android XML과 Compose 비교
  1. Android XML
    View와 Layout을 정의하고 배치하는 데에 사용되는 마크업 언어
    • 장점)
      • 다양한 View와 Layout을 쉽게 정의하고 배치 가능
      • Android XML을 통해 개발자가 쉽게 디자인 툴을 사용해 UI 레이아웃 구성 가능
  2. Compose
    Kotlin을 사용해 UI를 선언하고 렌더링하는데 사용하는 네이티브 UI 툴킷
    • 장점)
      • 선언적 프로그래밍 모델을 사용해 UI를 구성하므로 코드의 가독성 증가
      • 중첩된 XML 레이아웃을 피함
      • 코드를 쉽게 테스팅할 수 있음.
  • 아직 Compose는 Android XML보다 자료도 적고 관련 강의가 적기에 러닝 커브가 큼.
    또한, 일부 기능이 완전하지 않아 불안정하지만 향 후 전망이 밝은 트렌디한 방식.

  • Compose typography
    Compose에서 타이포그래피(Typography)는 앱에서 사용되는 글꼴, 글자 크기, 글꼴 두께 등의 속성을 정의하는 방법을 제공한다.

    • 타이포그래피를 사용하여 텍스트를 일관되고 시각적으로 효과적인 방법으로 표시
    • Compose에서는 Material Design 표준에 따라 미리 정의된 타이포그래피를 제공하며, 이러한 타이포그래피를 수정하여 앱의 디자인을 커스터마이징 가능.
    • ex) Material Design 표준에서는 H1, H2, H3 등의 머리글 타이포그래피가 정의되어 있으며, Compose에서는 이러한 타이포그래피를 다음과 같이 사용
Text("Hello, World!", style = MaterialTheme.typography.h1)

위 코드에서 MaterialTheme.typography.h1은 Material Design 표준에 따라 미리 정의된 H1 타이포그래피를 나타낸다. Text()함수는 이 타이포그래피를 적용하여 "Hello, World!" 텍스트를 표시.


  • Lambda expression
    람다식(Lambda expression)은 함수를 간단하게 표현하는 방법 중 하나.
    일반적인 함수와는 달리 이름이 없으며, 함수의 매개변수, 함수 본문, 반환값 등을 간결하게 표현 가능

    • 코틀린에서의 람다식은 아래와 같은 형태를 가진다.
{ 매개변수 ->
    // 함수 본문
    반환값
}
val sum = { x: Int, y: Int ->
    x + y
}

위 코드에서 val sum은 변수명이며, { x: Int, y: Int -> x + y } 부분이 람다식.
이 람다식은 두 개의 Int형 매개변수 x, y를 받아 덧셈 연산을 수행한 결과를 반환.

람다식은 함수를 인자로 전달하거나 반환값으로 사용할 수 있어, 함수형 프로그래밍 패러다임에서 자주 사용.
예를 들어, map, filter, reduce와 같은 함수형 연산을 수행하는 함수들은 람다식을 매개변수로 받아 사용한다.


  • Kotlin Trailing lambda exp
    함수의 마지막 인자가 람다식일 때, 람다식을 괄호 밖으로 빼내어 간단하게 작성하는 방법.
fun someFunction(x: Int, y: Int, z: () -> Unit) {
    // some logic
}

someFunction(1, 2, { 
    println("This is a lambda expression") 
})

위 예시에서 someFunction의 마지막 인자는 람다식이다. 하지만 이 람다식이 길어지면 코드의 가독성이 떨어질 수 있기에 아래 예시처럼 후행 람다를 사용해 코드를 간략하게 작성한다.

someFunction(1, 2) { 
    println("This is a lambda expression") 
}

즉, 함수 호출에서 람다식을 제일 마지막에 작성하면 괄호 밖으로 빼서 사용할 수 있는 개념이 Trailing lambda.
아처럼 후행 람다는 코드 가독성을 높여주는 장점이 있다.


  • Compose에서 람다식 활용 Ex
@Composable
fun MyApp() {
    val moneyCounter = remember {
        mutableStateOf(0)
    }

    // A surface container using the 'background' color from the theme
    Surface(
        modifier = Modifier
            .fillMaxHeight()
            .fillMaxWidth(),
        color = Color(0xFF546E7A)
    ) {
        Column(
            verticalArrangement = Arrangement.Center,
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            Text(text = "$${moneyCounter.value}", style = TextStyle(
                color = Color.White,
                fontSize = 35.sp,
                fontWeight = FontWeight.ExtraBold
            ))
            Spacer(modifier = Modifier.height(130.dp))
            /*
            람다식인
            { moneyCounter.value = it +1 }은 '(Int) -> Unit' 타입의 함수.
            이 람다식이 updateMoneyCounter 함수를 대체할 수 있게 되는 것이다.
            따라서, CreateCircle 함수가 호출될 때,
            첫 번째 파라미터 moneyCounter는 moneyCounter.value로 할당되며,
            두 번째 파라미터 updateMoneyCounter는 람다식 { moneyCounter.value = it + 1 }로 할당된다.
            CreateCircle 함수 내에서 updateMoneyCounter를 호출하면
            람다식 { moneyCounter.value = it + 1 }이 실행되는 원리.
             */
            CreateCircle(moneyCounter = moneyCounter.value) { newCounter ->
                moneyCounter.value = newCounter
            }
            if (moneyCounter.value > 25) {
                Text("Lots of Money !")
            }
        }
    }
}


//@Preview
@Composable
// updateMoneyCounter는 Int타입의 값을 받아서 반환값이 없는 함수
fun CreateCircle(moneyCounter: Int = 0,
                 updateMoneyCounter: (Int) -> Unit) {

    // Card의 패딩은 3dp
    Card(modifier = Modifier
        .padding(3.dp)
        // 높이, 너비 모두 45dp
        .size(105.dp)
        .clickable {
            // moneyCounter.value += 1
            updateMoneyCounter(moneyCounter + 1)
            Log.d("Counter", "CreateCircle: $moneyCounter")
        },
        shape = CircleShape,
        elevation = 4.dp
    ) {
        Box(contentAlignment = Alignment.Center) {
            Text(text = "Tap", modifier = Modifier)
        }
    }
}

  • Compose Hosting
    Composable 함수를 호출하여 View를 렌더링하는 기능을 수행하는 Android 애플리케이션의 구성 요소.

    • Composable 함수를 호스팅하는 것은 Compose UI의 핵심 개념 중 하나이며, Android 애플리케이션에서 UI 레이아웃을 작성하는 데 사용됨.

    • Hosting은 Composable 함수를 호출하는 코드 블록이다.
      이러한 코드 블록에서 Composable 함수에 대한 호출을 작성하여 UI 구성 요소를 생성하는데, 이러한 호스팅 함수에는 setContent, Scaffold 및 Column 등이 있음.

    • Hosting 함수는 UI 레이아웃의 계층 구조와 Composable 함수가 렌더링 되는 방식을 정의한다.

    • Hosting 함수에서는 Composable 함수의 인자를 전달할 수 있다.

    • Hosting 함수는 애플리케이션의 상태를 나타내는 객체를 Composable 함수에 전달하거나, Composable 함수에서 사용되는 애니메이션, 트랜지션 등을 정의할 수도 있다.


  • Compose Modifier
    Composable 함수의 특성과 레이아웃을 수정하는 역할을 수행.

    • Composable 함수 내에서 Modifier를 사용하여 뷰의 속성을 변경하거나 레이아웃을 변경한다.

    • Modifier에는 여러 메서드가 있으며, 각 메서드는 해당하는 속성을 변경한다.

    • 예를 들어, Modifier.size(width = 100.dp, height = 100.dp)를 사용하여 크기를 100dp로 설정할 수 있으며, Modifier.background(color = Color.Red)를 사용하여 배경 색상을 빨간색으로 설정할 수도 있다.

    • Modifier는 체인 형태로 사용할 수 있으며, 체인의 마지막에는 항상 Composable 함수가 위치해야 한다.

    • 이러한 Modifier 체인은 읽기 쉽고 유지 보수가 용이하며, 뷰를 조작하는데 매우 편리.

    • Modifier는 레이아웃 변경 또는 뷰 속성 변경과 같이 뷰의 변화를 캡슐화하여 뷰 계층 구조를 더욱 깨끗하게 유지할 수 있도록 한다.

    • Modifier는 새로운 Composable 함수를 만들어 뷰 계층 구조를 구성하는데 사용할 수도 있음.


  • Compose Widget
    Compose에서는 레이아웃과 위젯이 개념적으로 구분되지는 않음.
    대신, 모든 UI 요소는 위젯이라는 개념으로 표현됨.

    • Compose에서 위젯은 앱의 UI 구성 요소를 나타낸다.

    • 예를 들어, 버튼, 텍스트, 이미지 등 모든 UI 구성 요소는 위젯.

    • 위젯은 각각의 고유한 상태(state)를 가지고 있으며, Compose는 이러한 위젯의 상태를 변경하고 조작하는 데 사용됨.

    • 위젯은 또한, 다른 위젯의 조합으로 만들어질 수 있는데 이러한 조합을 통해 Compose에서는 다양한 레이아웃 및 UI 디자인을 생성할 수 있게 됨.

    • Compose에서는 모든 위젯이 함수이며, Composable 함수라는 특별한 종류의 함수를 사용하여 UI를 작성한다.

    • Composable 함수는 인자를 받아들이고 UI를 생성하는 데 사용함.

    • Composable 함수 내에서는 다른 Composable 함수를 호출하거나 조합하여 UI를 구성할 수 있음.

    • 이렇게 Compose에서는 모든 위젯이 함수로 표현되기 때문에, 함수의 재사용성과 유연성이 향상됨.


  • Compose State
    Compose에서의 State는 값이 변경될 수 있는 데이터를 나타내기 위한 개념.

    • State는 Composable 함수 내에서 변경 가능한 값을 저장하고, 이 값이 변경되면 Composable 함수가 다시 실행되어 UI가 업데이트됨.

    • State는 Compose에서 변경 가능한 데이터를 나타내기 위한 주요한 개념 중 하나이며, 다양한 타입의 데이터를 저장할 수 있다.

    • 예를 들어 IntState, StringState, BooleanState와 같은 State 타입이 존재.

    • 이러한 State 타입은 값이 변경될 때마다 Composable 함수를 다시 실행하도록 보장한다. 즉, Recomposition을 수행될 수 있게 함.

    • State는 일반적으로 변수처럼 사용되며, 변수에 접근하거나 값을 변경할 때 value 속성을 사용한다.

@Composable
fun Counter() {
    var count by remember { mutableStateOf(0) }
    Button(onClick = { count++ }) {
        Text("Count: $count")
    }
}

위 코드에서 count는 mutableStateOf(0)으로 초기화된 IntState.

count 값이 변경될 때마다 Composable 함수가 다시 실행되므로, Button에 표시되는 텍스트가 업데이트 즉, Recomposition 된다. Button을 클릭할 때마다 count 값이 1씩 증가하고, 이 값이 변경되면 Text에 표시되는 내용이 변경되는 로직 구조.


  • Compose Recomposition
    Compose에서 Recomposition(재구성)은 UI 상태 변화에 따라 Composable 함수들이 호출되고 다시 그려지는 과정.

    즉, UI 상태 변화가 있을 때마다 Composable 함수들이 다시 실행되어 UI를 업데이트하는 과정.

    • Recomposition은 Compose의 핵심 기능 중 하나이며, 이를 통해 선언적 UI 프로그래밍의 장점을 최대한 활용할 수 있게 된다.

    • Recomposition을 통해 Composable 함수들은 상태를 가지지 않는 순수 함수로 작성될 수 있는 것이며, UI 상태 변화에 따라 재구성됨으로써 항상 최신 UI 상태를 보장할 수 있다.

    • Recomposition은 Compose 엔진에 의해 자동으로 처리되기 때문에 개발자가 수동으로 호출하거나 관리할 필요가 없다.

    • 대신, Composable 함수에서 상태나 프로퍼티를 변경하는 것만으로 Recomposition이 자동으로 발생하기 때문이다.

    • 이를 통해 개발자는 UI 업데이트에 집중할 수 있으며, 불필요한 UI 업데이트를 최소화하여 성능을 향상시킬 수 있다.


  • Compose Container function
    Compose에서 Container 함수는 다양한 레이아웃 기능을 제공하는 함수 중 하나.
    Container 함수를 사용하여 자식 요소를 포함하는 컨테이너를 만들 수 있다.

    • Container 함수는 @Composable 함수이며, 다양한 속성(Modifier, padding, background, fillMaxWidth, fillMaxHeight 등)을 제공하여 다양한 레이아웃을 구성할 수 있음.
    • Container 함수는 주로 레이아웃을 구성하는 데 사용되며, 예를 들어 Column과 Row 함수를 사용하여 세로 또는 가로로 자식 요소를 배치할 수 있다.
    • Box 함수를 사용하여 자식 요소를 겹쳐서 배치할 수도 있다.
    • ConstraintLayout 함수를 사용하여 제약 조건을 설정하여 자식 요소를 배치할 수도 있다.
    • Container 함수는 다른 Compose 함수와 함께 사용하여 다양한 UI를 만들 수 있음.

  • Compose Surface
    Compose에서 Surface는 일반적으로 다른 구성요소를 포함하는 컨테이너.

    • Surface는 주어진 모양과 색상으로 화면을 렌더링하고, 그림자, 경계선 및 기타 시각적 요소를 추가하여 디자인 요소를 강화할 수 있다.

    • Compose에서 Surface 컴포넌트는 박스, 버튼, 카드 등의 구성 요소를 배치할 수 있는 가장 기본적인 컨테이너.

    • Surface의 주요 기능 중 하나는 그림자를 추가하는 것. 그림자를 통해 UI 구성 요소 간의 계층을 표현할 수 있으며, 사용자가 인터랙션할 때 그림자가 애니메이션화될 수도 있음.

    • Surface의 기본 속성에는 color, contentColor, elevation, onClick 등이 있으며, 이를 통해 UI 요소의 배경색, 전경색, 그림자, 클릭 핸들러 등을 제어할 수 있습니다. Surface 컴포넌트는 여러 종류의 모양을 가질 수 있으며, Material Design에서는 Card, Dialog, Drawer 등의 구성 요소로 사용됨.

    • Surface는 주로 콘텐츠를 그룹화하고 배경을 렌더링하기 위해 사용됨.

    • Surface는 다음과 같은 속성을 포함한다.

      • color: Surface의 배경 색상을 정의.
      • elevation: Surface의 그림자를 정의.
        (숫자가 클수록 그림자가 진하고, 작을수록 그림자가 연해짐)
      • shape: Surface의 모양을 정의.
      • RoundedCornerShape나 CutCornerShape와 같은 사전 정의된 모양을 사용하거나, 직접 모양을 정의할 수도 있음.
      • border: Surface의 경계선을 정의.

ex ) 빨간색 배경과 8dp 크기의 그림자를 가진 Surface

Surface(
    modifier = Modifier
        .size(200.dp)
        .padding(16.dp),
    color = Color.Red,
    elevation = 8.dp,
) {
    // content
}

Surface는 또한 자식 요소를 포함할 수 있기에, Surface 내부에서 다른 구성요소들을 구성 및 배치 가능.


  • Compose Column
    Compose의 Column은 세로 방향으로 UI 요소를 배치하기 위해 사용되는 컨테이너.

    • Column 내에 들어있는 요소들은 위에서 아래 방향으로 차례대로 배치됨.
    • Column은 가로 폭이 충분히 확보되어 있지 않은 경우 자식 요소의 내용이 잘리지 않도록 스크롤 가능한 VerticalScrollbar과 함께 사용 가능.

ex) Column을 사용하여 두 개의 Text 요소를 세로로 나란히 배치

Column(
    modifier = Modifier.fillMaxSize(),
    verticalArrangement = Arrangement.Center,
    horizontalAlignment = Alignment.CenterHorizontally
) {
    Text("Hello")
    Text("Compose")
}

위의 코드는 전체 화면을 fillMaxSize()로 채우며, verticalArrangement 및 horizontalAlignment 속성을 사용하여 Text 요소들이 수직 및 수평 방향으로 중앙에 배치되도록 설정.


  • Compose Lazy Column
    데이터를 동적으로 로드하고 화면에 렌더링하는 데 사용되는 선형 레이아웃.

    • 일반적인 Column과 비슷하지만, 레이아웃에 필요한 아이템만 로드하므로 성능이 향상됨.
    • 특히 많은 수의 아이템이 있는 목록이나 그리드를 처리해야하는 경우 유용
    • 일반적으로, Lazy Column은 대용량의 데이터를 처리할 때 사용됨.
    • 기존의 RecyclerView와 비슷한 방식으로 작동하며, 필요한 데이터만 렌더링하므로 전체 데이터셋을 로드하지 않고도 화면에 적합한 크기로 목록을 렌더링 가능.
  • Lazy Column의 3가지 유형
  1. verticalScroll:
    LazyColumn을 수직 방향으로 스크롤할 수 있게 만드는 데 사용됩니다.
LazyColumn(
    modifier = Modifier.verticalScroll(rememberScrollState())
) {
    // items to be displayed vertically
}
  1. horizontalScroll:
    LazyColumn을 수평 방향으로 스크롤할 수 있게 만드는 데 사용됩니다.
LazyColumn(
    modifier = Modifier.horizontalScroll(rememberScrollState())
) {
    // items to be displayed horizontally
}
  1. itemsIndexed:
    LazyColumn의 인덱스를 사용하여 각 항목의 위치를 지정하는 데 사용됩니다.
LazyColumn {
    itemsIndexed(items = listOf("apple", "banana", "cherry", "date")) { index, item ->
        Text("$index - $item")
    }
}

LazyColumn은 RecyclerView와 달리 뷰홀더를 생성하거나 바인딩할 필요가 없음.
대신 Compose는 필요할 때 새로운 뷰를 생성하고 필요하지 않은 뷰를 버리는 방식으로 동작한다.
이러한 방식으로 Lazy Column은 필요한 항목만 렌더링하여 앱의 성능을 크게 향상시키게 됨.


  • Compose에서 RecylcerView와 같은 레이아웃은 'LazyColumn' 또는 'LazyRow'로 사용됨.

ex) LazyColumn을 사용하여 RecyclerView를 구현하려면 다음과 같이 작성할 수 있음.

@Composable
fun MyList(dataList: List<String>) {
    LazyColumn {
        items(dataList) { data ->
            Text(data)
        }
    }
}

LazyColumn을 사용하여 리스트를 만들고 items 함수를 사용하여 각 항목을 표시.
items 함수는 데이터 목록과 각 데이터 항목에 대한 콜백 함수를 인자로 받는다.

LazyRow를 사용하면 수평 스크롤 가능한 RecyclerView를 만들 수 있다.

또한, LazyColumn과 LazyRow에는 데이터를 동적으로 로드할 수 있는 기능이 내장되어 있으므로 대규모 데이터 세트를 처리하는 데도 유용

  • 그리디 레이아웃 형식으로 만들기 위해서는,
    Compose에서 그리드 레이아웃 형식의 RecyclerView를 만들기 위해서는 LazyVerticalGrid나 LazyHorizontalGrid를 사용

ex) items 매개변수에 List나 LazyListScope를 넘겨주어 그리드 뷰를 구성

@Composable
fun GridRecyclerView(gridItems: List<GridItem>) {
    LazyVerticalGrid(
        cells = GridCells.Fixed(2), // 2개의 열을 갖는 그리드 뷰
        contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp)
    ) {
        items(gridItems) { item ->
            // 각각의 그리드 아이템을 표시할 UI Composable
            GridItem(item)
        }
    }
}

@Composable
fun GridItem(item: GridItem) {
    // 각각의 그리드 아이템 UI
    Text(
        text = item,
        modifier = Modifier
            .padding(16.dp)
            .fillMaxWidth()
            .height(48.dp)
            .border(1.dp, Color.Gray)
            .padding(16.dp),
        textAlign = TextAlign.Center
    )
}

LazyVerticalGrid의 cells 매개변수는 그리드 뷰에서 몇 개의 열을 보여줄 것인지를 설정한다. 위 예제에서는 2개의 열을 갖는 그리드 뷰를 생성.

items 매개변수에는 그리드 뷰에 표시할 List를 전달합니다. 그리드 뷰를 더욱 효율적으로 구성하기 위해서는 items 매개변수에 LazyListScope를 사용하여 아이템을 동적으로 로딩하는 방법도 존재.


  • Compose Clip

Compose에서 Modifier의 clip() 함수는 해당 Composable의 클리핑 모양을 지정할 때 사용됨.
클리핑 모양은 Composable의 영역을 어떻게 자를 것인지를 결정.

clip() 함수에 전달할 수 있는 인자는 크게 세 가지.

1. Shape
Shape 인자를 통해 Composable의 영역을 자를 모양을 지정. 
MaterialTheme 클래스에서 제공하는 Shape or 사용자가 직접 만든 Shape을 사용.

2. Rectangle size
사각형 모양으로 자를 때 사용.

3. Circle radius
원 모양으로 자를 때 사용.

ex) 50dp 크기의 원 모양으로 클리핑된 Surface 생성

Surface(
    modifier = Modifier
        .size(100.dp)
        .clip(CircleShape)
) {
    // ...
}

Modifier의 clip() 함수를 사용하여 Composable의 클리핑 모양을 지정하면 해당 Composable이 지정된 모양에 맞게 잘려서 표시됨.
응용하여 ImageView 등의 이미지를 원 모양이나 사각형 모양으로 클리핑하여 표시할 수 있음.


  • Compose imeAction
    Compose의 TextField에서 imeAction은 사용자가 키보드에서 엔터 키를 눌렀을 때 실행되는 동작을 지정하는 데 사용.

    • ex) imeAction = ImeAction.Done을 설정하면 키보드에서 엔터 키를 눌렀을 때, onImeActionPerformed 콜백 함수가 실행되며, 이 콜백 함수에서 정의한 동작이 수행됨. ImeAction 클래스에는 Done 이외에도 Go, Search, Send 등의 여러 상수가 있으며, 각각은 다른 동작을 수행하도록 지정됨.

이를 통해 사용자 인터페이스를 보다 쉽게 구성할 수 있으며, 사용자 경험을 향상시킬 수 있음.


profile
https://github.com/nohjunh

0개의 댓글