안드로이드에서 **스티키 헤더(Sticky Header)**는 리스트 항목을 스크롤할 때 특정 헤더가 화면 상단에 고정되도록 하는 기능을 말합니다. 긴 리스트에서 섹션별로 구분되는 항목을 쉽게 구분할 수 있도록 돕는 UI 요소로, 사용자 경험을 향상시킬 수 있습니다. 예를 들어, 쇼핑몰의 카테고리별 상품 리스트에서 각 카테고리 이름이 스크롤을 내려도 화면 상단에 고정되도록 하는 방식입니다.
스티키 헤더의 주요 사용 예시
- 섹션 타이틀 고정: 각 카테고리나 날짜별로 그룹화된 항목들을 구분하는 데 사용됩니다. 예를 들어, 쇼핑몰에서 "전자제품", "의류", "책" 등의 카테고리가 있고, 각 카테고리의 항목이 스크롤될 때 해당 카테고리 제목이 화면 상단에 고정되는 방식입니다.
- 스크롤 내비게이션: 긴 리스트에서 여러 섹션을 구분하여 사용자에게 목록의 흐름을 명확히 안내할 수 있습니다.
안드로이드에서 스티키 헤더 구현하기 (Jetpack Compose)
Jetpack Compose에서는 LazyColumn과 stickyHeader를 사용하여 손쉽게 스티키 헤더를 구현할 수 있습니다. stickyHeader는 각 섹션의 첫 번째 아이템을 화면 상단에 고정시키며, 사용자가 스크롤할 때 헤더가 상단에 고정된 상태로 표시됩니다.
기본 스티키 헤더 구현 예시
@Composable
fun StickyHeaderExample() {
val sections = listOf(
"Section 1" to List(20) { "Item #$it" },
"Section 2" to List(20) { "Item #${it + 20}" },
"Section 3" to List(20) { "Item #${it + 40}" }
)
LazyColumn {
sections.forEach { (header, items) ->
stickyHeader {
SectionHeader(header)
}
items(items) { item ->
ItemContent(item)
}
}
}
}
@Composable
fun SectionHeader(header: String) {
Text(
text = header,
style = MaterialTheme.typography.h6,
modifier = Modifier
.fillMaxWidth()
.background(MaterialTheme.colors.primary)
.padding(16.dp)
)
}
@Composable
fun ItemContent(item: String) {
Text(
text = item,
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
)
}
코드 설명
- 섹션 리스트: sections 리스트는 여러 섹션과 해당 섹션에 속한 아이템들을 나열합니다. 각 섹션의 제목은 "Section 1", "Section 2", "Section 3"처럼 구분됩니다.
- stickyHeader 사용: LazyColumn에서 stickyHeader를 사용하여 각 섹션의 제목을 고정시킵니다. 이때, 각 섹션의 첫 번째 항목을 화면 상단에 고정하게 됩니다.
- 섹션 헤더: 각 섹션의 제목을 SectionHeader 함수에서 정의하고, 스타일을 적용하여 화면에 표시합니다.
중간에 스티키 헤더가 있을 때 처리 방법
중간에 스티키 헤더가 있을 때는 각 섹션의 첫 번째 아이템이 화면 상단에 고정되어 보여지는데, 이때 여러 섹션의 헤더가 동시에 고정되지 않도록 해야 합니다. 사용자가 스크롤할 때 첫 번째 섹션의 헤더가 화면 상단에 고정되고, 섹션이 변경될 때마다 그 섹션의 헤더가 새로 고정되어 표시됩니다.
예시: 상단과 중간에 스티키 헤더가 있을 때
@Composable
fun StickyHeaderWithMultipleSections() {
val sections = listOf(
"Section 1" to List(20) { "Item #$it" },
"Section 2" to List(20) { "Item #${it + 20}" },
"Section 3" to List(20) { "Item #${it + 40}" }
)
LazyColumn {
sections.forEach { (header, items) ->
stickyHeader {
SectionHeader(header)
}
items(items) { item ->
ItemContent(item)
}
}
}
}
동작 원리
- 스크롤 시 첫 번째 섹션 고정: 첫 번째 섹션인 "Section 1"의 헤더는 화면 상단에 고정됩니다. 사용자가 스크롤할 때 해당 섹션의 아이템들이 사라지지 않고 계속 표시됩니다.
- 다음 섹션 헤더 고정: 첫 번째 섹션이 스크롤로 사라지면, 두 번째 섹션인 "Section 2"의 헤더가 화면 상단에 고정됩니다. 이 방식은 각 섹션에 대해 반복됩니다.
- 헤더 간 간격 관리: 중간에 스티키 헤더가 있을 때 다른 섹션의 헤더가 겹치지 않도록 각 섹션에 적절한 간격을 두어야 합니다. 각 섹션의 헤더가 자연스럽게 스크롤되면서 고정됩니다.
스티키 헤더가 여러 개인 경우 처리 방법
스티키 헤더가 여러 개인 경우, 각 섹션의 첫 번째 항목이 화면 상단에 고정됩니다. 이때 섹션 간 구분이 명확하게 되도록 각 섹션에 충분한 간격을 두어야 하며, stickyHeader를 통해 섹션별 헤더가 고정된 상태로 스크롤되게 설정해야 합니다.
예시: 스티키 헤더가 여러 개 있을 때
@Composable
fun StickyHeaderMultiple() {
val sections = listOf(
"Top Section" to List(20) { "Top Item #$it" },
"Middle Section" to List(20) { "Middle Item #$it" },
"Bottom Section" to List(20) { "Bottom Item #$it" }
)
LazyColumn {
sections.forEach { (header, items) ->
stickyHeader {
SectionHeader(header)
}
items(items) { item ->
ItemContent(item)
}
}
}
}
결론
스티키 헤더는 긴 리스트에서 사용자가 섹션별로 어떤 항목을 보고 있는지 쉽게 알 수 있게 해주는 중요한 UI 요소입니다. LazyColumn에서 stickyHeader를 사용하여 각 섹션의 제목을 고정시킬 수 있으며, 여러 섹션이 있을 때 각 섹션의 첫 번째 아이템을 고정하여 표시하는 방식으로 구현할 수 있습니다. 중간에 스티키 헤더가 있는 경우, 각 섹션의 헤더가 자연스럽게 스크롤하면서 화면 상단에 고정되도록 처리할 수 있습니다.