본문 바로가기

안드로이드/Compose UI

[안드로이드 Jetpack Compose UI] LazyColumn을 사용해서 무한 스크롤 뷰 만들기


안녕하세요. 이번 포스팅에서는 안드로이드 Jetpack Compose에서 무한스크롤 뷰를 만드는 방법에 대해 설명하겠습니다. 안드로이드 Compose UI에서는 기존 XML 기반의 레이아웃에서 사용했던 Adapter 없이 간단하게 무한스크롤 뷰를 구성할 수 있습니다. 바로 LazyColumn 컴포저블을 사용하면 됩니다. 선언적 UI 구성의 장점을 살려서 간결하고 유연한 코드 작성이 가능합니다.

안드로이드 Jetpack Compose에서 구현한 Vertical RecyclerView
안드로이드 Jetpack Compose에서 구현한 Vertical 무한 스크롤 뷰

1. LazyColumn을 사용해서 무한 스크롤 뷰 만들기

LazyColumn은 Compose에서 제공하는 컴포저블로 기존의 RecyclerView에서의 기능을 수행합니다. XML 레이아웃 기반 RecyclerView의 어댑터 패턴과는 다르게 LazyColumn에서는 Compose의 선언적 방식을 통해 각 아이템을 간단하게 렌더링 할 수 있습니다. LazyColumn은 아이템을 동적으로 로드하며, 뷰 홀더 패턴을 내부에서 처리하기 때문에 불필요한 렌더링을 최소화하고 성능을 최적화합니다.

아래의 코드는 사용자 데이터를 나타내는 CustomUserProfile 컴포저블을 LazyColumn에 넣어서 여러 사용자 정보를 리스트 형식으로 표시하는 예제입니다. 이때, LazyColumn의 itemsIndexed 함수를 사용해서 데이터 리스트와 인덱스를 활용해 각 사용자의 정보를 표시했습니다.

@Composable
fun CustomRecyclerView(users: List<UserEntity>) {
    LazyColumn(
        modifier = Modifier.fillMaxSize(),
        verticalArrangement = Arrangement.spacedBy(8.dp),
        content = {
            itemsIndexed(users) { index, user ->
                CustomUserProfile(
                    id = user.id,
                    name = user.name,
                    age = user.age
                )
            }
        })
}


@Composable
fun CustomUserProfile(id: Int, name: String, age: Int) {
    Card(
        modifier = Modifier
            .fillMaxWidth()
            .height(100.dp),
        shape = RoundedCornerShape(10.dp),
        border = BorderStroke(1.dp, Color.LightGray),
        colors = CardDefaults.cardColors(
            containerColor = Color(0xFFFFC20D),
        )
    ) {
        Column(
            modifier = Modifier
                .fillMaxWidth()
                .fillMaxHeight()
                .padding(start = 10.dp)
                .background(Color.White),
            verticalArrangement = Arrangement.Center,
            horizontalAlignment = Alignment.Start
        ) {

            Text(
                modifier = Modifier.padding(start = 10.dp),
                style = TextStyle(
                    color = Color.Black,
                    fontSize = 20.sp,
                    fontWeight = FontWeight.Bold
                ),
                text = "# $id"
            )

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

            Text(
                modifier = Modifier.padding(start = 10.dp),
                style = TextStyle(
                    color = Color.Black,
                    fontSize = 16.sp,
                    fontWeight = FontWeight.SemiBold
                ),
                text = "Name : $name"
            )

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

            Text(
                modifier = Modifier.padding(start = 10.dp),
                style = TextStyle(
                    color = Color.Gray,
                    fontSize = 14.sp,
                    fontWeight = FontWeight.Normal
                ),
                text = "Age : $age"
            )
        }
    }
}

2. LazyColumn 사용하기

ViewModel내에 LazyColumn에서 사용될 데이터를 추가했습니다. ViewModel에서 사용자 데이터 리스트를 생성하고, 이를 LazyColumn에 전달했습니다. 이를 통해서 사용자 인터랙션에 따라 데이터를 업데이트하거나 변경할 수 있습니다.

data class UserEntity(
    val id: Int,
    val name: String,
    val age: Int,
)

@HiltViewModel
class RecyclerViewViewModel @Inject constructor(
    savedStateHandle: SavedStateHandle,
) : ViewModel() {
    val users = mutableStateOf<List<UserEntity>>(generateDummyUsers())

    private fun generateDummyUsers(): List<UserEntity> {
        val names = listOf(
            "Olivia", "Emma", "Ava", "Charlotte", "Sophia",
            "Amelia", "Isabella", "Mia", "Evelyn", "Harper",
            "Liam", "Noah", "Oliver", "Elijah", "William",
            "James", "Benjamin", "Lucas", "Henry", "Alexander"
        )

        return List(5000) { index ->
            UserEntity(
                id = index,
                name = names.random(),
                age = (18..60).random()
            )
        }
    }
}


RecyclerViewScreen에서는 ViewModel에서 생성한 users 리스트를 가져와서 CustomRecyclerView의 인자로 전달하고 있습니다. by 키워드를 사용해서 users가 바로 데이터를 가리키도록 구현했습니다. 만약에 by를 사용하지 않는다면 users.value로 접근할 수 있습니다.

@Composable
fun RecyclerViewScreen(
    mainNavController: NavHostController,
    viewModel: RecyclerViewViewModel
) {
    val users by remember { viewModel.users }
    
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(start = 10.dp, end = 10.dp)
    ) {
        Spacer(modifier = Modifier.height(10.dp))

        Text(
            text = "item's count : ${users.size}",
        )

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

        CustomRecyclerView(
            users = users
        )
    }
}

3. LazyColumn에서 itemIndexed 사용하기

LazyColumn에서는 item, items, itemIndexed 함수를 제공하는데, itemIndexed 함수를 사용하면 각 아이템의 인덱스와 데이터를 함께 사용할 수 있습니다. 실무에서는 대부분 itemIndexed를 많이 사용하게 됩니다.

4. 정리

안드로이드 Jetpack Compose의 LazyColumn을 이용한 무한 스크롤 뷰 구현은 기존 XML 레이아웃 방식에 비해서 간결하고 효율적입니다. 선언적 UI 방식을 사용함으로써 직관적으로 UI를 설계할 수 있고, 별도의 Adapter가 필요하지 않기 때문에 코드와 파일도 많이 줄어듭니다. 이번 글을 통해 여러분만의 RecyclerView를 구현하는데 도움이 됐으면 좋겠습니다.