본문 바로가기
안드로이드/Compose

[Compose] 목록 및 그리드

by jinwo_o 2024. 11. 17.

 

 

목록 및 그리드  |  Jetpack Compose  |  Android Developers

이 페이지는 Cloud Translation API를 통해 번역되었습니다. 목록 및 그리드 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 많은 앱에서 항목의 컬렉션을 표시해

developer.android.com

@Composable
fun MessageList(messages: List<Message>) {
    Column {
        messages.forEach { message ->
            MessageRow(message)
        }
    }
}
  • 스크롤이 필요하지 않은 경우 (방향에 따라) 간단한 Column 또는 Row 를 사용하여 다음과 같이 목록을 반복하여 각 항목의 콘텐츠를 내보낼 수 있다.
  • verticalScroll() 수정자를 사용하여 Column 을 스크롤 가능하게 만들 수 있다.
@Composable
fun MessageList(messages: List<Message>) {
    val scrollState = rememberScrollState()

    Column(modifier = Modifier.verticalScroll(scrollState)) {
        messages.forEach { message ->
            MessageRow(message)
        }
    }
}

 

지연 목록

  • 많은 수의 항목이나 길이를 알 수 없는 목록을 표시해야 하는 경우 Column 과 같은 레이아웃을 사용하면 모든 항목이 표시 가능 여부와 관계없이 구성되고 배치되므로 성능 문제가 발생할 수 있다.
  • Compose 는 구성요소의 표시 영역에 표시되는 항목만 구성하여 배치하는 구성요소 집합을 제공한다. 이러한 구성요소에는 LazyColumn 및 LazyRow 가 포함된다.
  • 지연 구성요소는 @Composable 콘텐츠 블록 구성요소를 수락하고 앱에서 직접 컴포저블을 내보낼 수 있도록 허용하는 대신 LazyListScope.() 블록을 제공한다.
    • 이 *LazyListScope 블록은 앱에서 항목 콘텐츠를 설명할 수 있는 DSL 을 제공한다.

 

LazyListScope DSL

LazyColumn {
    // 1. 단일 아이템 추가
    item {
        Text(text = "First item")
    }

    // 2. 5개 아이템 추가
    items(5) { index ->
        Text(text = "Item: $index")
    }
    
    // 3. 아이템 컬렉션 추가
    items(messages) { message ->
        MessageRow(message)
    }
    
    // 4. 각 아이템에 대한 인덱스와 내용 제공
    itemsIndexed(messages) { index, message ->
        MessageRowIndexed(index, message)
    }
}

 

지연 그리드

LazyVerticalGrid(
    columns = GridCells.Adaptive(minSize = 128.dp)
) {
    items(photos) { photo ->
        PhotoItem(photo)
    }
}
  • LazyVerticalGrid 및 LazyHorizontalGrid 컴포저블은 그리드로 항목 표시를 지원한다.
  • 지연 세로 그리드는 여러 열에 걸쳐 세로로 스크롤 가능한 컨테이너에 항목을 표시하는 반면, 지연 가로 그리드는 가로축을 중심으로 동일하게 동작한다.
  • 그리는 목록과 동일한 강력한 API 기능을 가지며 콘텐츠를 설명하기 위한 매우 유사한 DSL(LazyGridScope.())을 사용한다.
  • LazyVerticalGrid 의 Column 매개변수와 LazyHorizontalGrid 의 row 매개변수는 셀이 열이나 행으로 형성되는 방식을 제어한다.
  • GridCells.Adaptive :  각 셀에 최소한 minSize 공간이 있고 모든 추가 공간이 균등하게 분산된다는 조건 하에 가능한 한 많은 행이나 열로 구성된 그리드를 정의한다.
    • 예시) LazyVerticalGrid.Adaptive(20.dp)는 가능한 한 많은 열이 있고 모든 열이 최소 20.dp 이며 동일한 너비를 갖는다. 화면 너비가 88.dp 이면 각각 22.dp 인 열이 4개 있다.
    • 남은 너비는 열 수가 계산된 후 열 간에 균등하게 분배된다.
    • 이러한 적응형 크기 조절 방법은 다양한 화면 크기에서 항목 집합을 표시하는 데 유용하다.
  • 디자인에서 특정 항목만 비표준 측정기준이 있어야 하는 경우 그리드 지원을 사용하여 항목에 맞춤 열 스팬을 제공할 수 있다.
    • LazyGridScope DSL item 및 items 메서드의 span 매개변수를 사용하여 열 스팬을 지정한다.
    • 스팬 범위의 값 중 하나인 maxLineSpan 은 적응형 크기 조절을 사용할 때 유용하다.
LazyVerticalGrid(
    columns = GridCells.Adaptive(minSize = 30.dp)
) {
    item(span = {
        GridItemSpan(maxLineSpan)
    }) {
        CategoryCard("Fruits")
    }
}

 

지연 시차 그리드

LazyVerticalStaggeredGrid(
    columns = StaggeredGridCells.Adaptive(200.dp),
    verticalItemSpacing = 4.dp,
    horizontalArrangement = Arrangement.spacedBy(4.dp),
    content = {
        items(randomSizedPhotos) { photo ->
            AsyncImage(
                model = photo,
                contentScale = ContentScale.Crop,
                contentDescription = null,
                modifier = Modifier
                    .fillMaxWidth()
                    .wrapContentHeight()
            )
        }
    },
    modifier = Modifier.fillMaxSize()
)
  • LazyVerticalStaggeredGrid 및 LazyHorizontalStaggeredGrid 는 지연 로드된 비슷한 간격의 항목 그리드를 만들 수 있는 컴포저블이다.
  • 지연 세로 지그재그형 그리드는 여러 열에 걸쳐 세로로 스크롤 가능한 컨테이너에 항목을 표시하며, 개별 항목의 높이를 다르게 지정할 수 있다. 지연된 가로 그리드는 너비가 다른 항목이 있는 가로축에서 동일한 동작을 보인다.
  • 고정된 수의 열을 설정하려면 StaggeredGridCells.Adaptive 대신 StaggeredGridCells.Fixed(columns) 를 사용하면 된다.

 

콘텐트 패딩

LazyColumn(
    contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp),
) { ... }
  • 콘텐츠 가장자리 주변에 패딩을 추가해야 하는 경우가 있다.
  • 지연 구성요소를 사용하면 일부 paddingValues 을 contentPadding 매개변수에 전달하여 이 작업을 지원할 수 있다.

 

콘텐츠 간격

LazyColumn(
    verticalArrangement = Arrangement.spacedBy(4.dp),
) { ... }

LazyRow(
    horizontalArrangement = Arrangement.spacedBy(4.dp),
) { ... }

LazyVerticalGrid(
    columns = GridCells.Fixed(2),
    verticalArrangement = Arrangement.spacedBy(16.dp),
    horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
    items(photos) { item ->
        PhotoItem(item)
    }
}
  • 항목 사이에 간격을 추가하려면 Arrangement.spacedBy() 를 사용한다.
  • 그리드는 세로 및 가로 정렬을 모두 허용한다.

 

항목 키

  • 기본적으로 각 항목의 상태는 목록이나 그리드에 있는 항목의 위치를 기준으로 키가 지정된다.
  • 하지만 이 경우 위치를 효율적으로 변경하는 항목에 상태가 저장되지 않아 데이터 세트가 변경되면 문제가 발생할 수 있다. LazyColumn 내 LazyRow 시나리오의 경우 행에서 항목 위치가 변경되면 사용자가 행 내에서 스크롤 위치를 잃게 된다.
LazyColumn {
    items(
        items = messages,
        key = { message ->
            message.id
        }
    ) { message ->
        MessageRow(message)
    }
}
  • 이 문제를 해결하려면 각 항목에 안정적이고 고유한 키를 제공하여 key 매개변수에 블록을 제공해야 한다. 안정적인 키를 제공하면 데이터 세트가 변경되어도 항목 상태의 일관성이 유지된다.
LazyColumn {
    items(books, key = { it.id }) {
        val rememberedValue = remember {
            Random.nextInt()
        }
    }
}
  • 키를 제공하면 Compose 가 재정렬을 올바르게 처리할 수 있다. 예를 들어, 항목에 저장된 상태가 포함되어 있는 경우 키를 설정하면 위치가 변경될 때 Compose 가 항목과 함께 이 상태를 옮길 수 있다.
LazyColumn {
    items(books, key = {
        // primitives, enums, Parcelable, etc.
    }) {
        // ...
    }
}
  • 하지만 항목 키로 사용할 수 있는 유형에는 한 가지 제한사항이 있다. 키 유형은 Activity 가 다시 생성될 때 상태를 유지하는 Android의 메커니즘인 Bundle 에서 지원해야 한다. Bundle 은 primitives, enum, Parcelable 과 같은 유형을 지원한다.
LazyColumn {
    items(books, key = { it.id }) {
        val rememberedValue = rememberSaveable {
            Random.nextInt()
        }
    }
}
  • Activity 가 다시 생성될 때 또는 이 항목에서 스크롤하여 벗어났다가 다시 스크롤하여 돌아올 때도 항목 컴포저블 내의 rememberSaveable 을 복원할 수 있도록 키를 Bundle 에서 지원해야 한다.

항목 애니메이션

  • 맞춤 애니메이션 사양을 제공할 수 있다. (animateItem 수정자)

 

고정 헤더(실험용)

  • '고정 헤더' 패턴은 그룹화된 데이터 목록을 표시할 때 유용하다. (stickyHeader())

 

스크롤 위치에 반응

  • 많은 앱이 스크롤 위치와 항목 레이아웃 변경사항에 반응하고 이를 수신 대기해야 한다. 지연 구성요소는 LazyListState 를 호이스팅하여 이 사용 사례를 지원한다.
    • LazyListState 는 firstVisibleItemIndex 및 firstVisibleItemScrollOffset 속성을 제공한다.

 

스크롤 위치 제어

  • LazyListState 는 스크롤 위치를 '즉시' 스냅하는 scrollToItem() 및 애니메이션을 사용하여 스크롤하는(부드럽게 스크롤하는) animateScrollToItem() 함수를 통해 이 기능을 지원한다.

 

큰 데이터 세트(페이징)

@Composable
fun MessageList(pager: Pager<Int, Message>) {
    val lazyPagingItems = pager.flow.collectAsLazyPagingItems()

    LazyColumn {
        items(
            lazyPagingItems.itemCount,
            key = lazyPagingItems.itemKey { it.id }
        ) { index ->
            val message = lazyPagingItems[index]
            if (message != null) {
                MessageRow(message)
            } else {
                MessagePlaceholder()
            }
        }
    }
}
  • Paging 라이브러리를 사용하면 앱에서 큰 항목 목록을 지원하며 필요에 따라 작은 목록을 로드하고 표시할 수 있다.
  • 페이징된 콘텐츠 목록을 표시하려면 collectAsLazyPagingItems() 확장 함수를 사용한 다음 반환된 LazyPagingItems 를 LazyColumn 의 items() 에 전달하면 된다.