본문 바로가기

안드로이드/Compose UI

[안드로이드 Jetpack Compose UI] LazyRow를 사용해서 RecyclerView 구현하기


안녕하세요. 이번 시간에는 안드로이드 Jetpack Compose에서 LazyRow를 사용해 가로 방향으로 무한 스크롤이 되면서 메모리 최적화 기능이 적용된 RecyclerView를 구현하겠습니다. 기존의 안드로이드 레이아웃 XML 방식을 사용해 보신 분들이라면 RecyclerView를 만들 때, 필요한 사전작업이 많았다는 걸 아실 텐데요. Compose로 넘어와서는 상당히 간단하게 구현할 수 있게 됐습니다. Compose에서 제공하는 LazyRow를 사용하면 가로 방향으로 무한 스크롤이 되는 RecyclerView를 만들 수 있습니다. 그럼 지금부터 코드와 함께 예제를 보여드리겠습니다.

안드로이드 Jetpack Compose에서 구현한 Horizontal RecyclerView
안드로이드 Jetpack Compose에서 구현한 Horizontal RecyclerView

1. LazyRow를 사용해서 RecyclerView 만들기

LazyRow는 항목을 가로로 배치하고, 사용자가 스크롤을 통해 양옆으로 탐색할 수 있게 해 줍니다. XML 기반의 RecyclerView에서는 Adapter 클래스가 추가로 필요했지만, Compose의 LazyRow를 사용하면 가로 방향으로 무한 스크롤이 가능한 RecyclerView를 손쉽게 구현할 수 있으며, Compose의 itemsIndexed 함수를 사용해 각 항목에 고유한 인덱스를 부여할 수 있습니다. 이를 통해 정보를 제공하는 동시에 메모리 사용을 최적화할 수 있습니다.

@Composable
fun CustomVerticalRecyclerView(users: List<ProfileEntity>) {
    LazyRow(
        modifier = Modifier.fillMaxSize(),
        horizontalArrangement = Arrangement.spacedBy(8.dp),
        content = {
            itemsIndexed(users) { index, user ->
                CustomUserProfile(
                    id = user.id,
                    name = user.name,
                    age = user.age,
                    nickName = user.nickName,
                    hobby = user.hobby
                )
            }
        })
}

@Composable
fun CustomUserProfile(id: Int, name: String, age: Int, nickName: String, hobby: String) {
    Card(
        modifier = Modifier
            .padding(4.dp)
            .wrapContentWidth()
            .height(130.dp)
            .wrapContentWidth(Alignment.Start),
        shape = RoundedCornerShape(15.dp),
        elevation = CardDefaults.cardElevation(defaultElevation = 4.dp),
        colors = CardDefaults.cardColors(
            containerColor = Color(0xFFECEFF1),
        )
    ) {
        Row(
            modifier = Modifier
                .fillMaxHeight()
                .padding(top = 12.dp, bottom = 12.dp, start = 12.dp, end = 30.dp),
            verticalAlignment = Alignment.CenterVertically
        ) {
            // 프로필 이미지나 아이콘을 여기에 넣을 수 있습니다.
            Icon(
                imageVector = Icons.Filled.AccountCircle,
                contentDescription = "Profile",
                modifier = Modifier
                    .size(60.dp)
                    .padding(4.dp),
                tint = Color.Gray
            )

            Spacer(modifier = Modifier.width(12.dp))

            Column {
                Text(
                    text = "# $id",
                    style = TextStyle(
                        color = Color.Black,
                        fontSize = 22.sp,
                        fontWeight = FontWeight.Bold
                    )
                )

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

                Text(
                    text = "$name a.k.a $nickName",
                    style = TextStyle(
                        color = Color.Black,
                        fontSize = 18.sp,
                        fontWeight = FontWeight.Bold
                    )
                )

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

                Text(
                    text = "Age: $age",
                    style = TextStyle(
                        color = Color.DarkGray,
                        fontSize = 14.sp
                    )
                )

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

                Text(
                    text = "Hobby: $hobby",
                    style = TextStyle(
                        color = Color.DarkGray,
                        fontSize = 14.sp
                    )
                )
            }
        }
    }
}

2. LazyRow 사용하기

데이터 모델링은 ViewModel에서 추가했습니다. 서버 API 통신을 해서 데이터를 불러오는 것처럼 재현하기 위해서 generateDummyProfiles 함수를 통해 무작위 프로필 데이터를 생성하며, 이 데이터는 Composable 함수에서 LazyRow를 구성하는 데 사용했습니다.

data class ProfileEntity(
    val id: Int,
    val name: String,
    val age: Int,
    val nickName: String,
    val hobby: String,
)

@HiltViewModel
class HorizontalRecyclerViewViewModel @Inject constructor(
    savedStateHandle: SavedStateHandle,
) : ViewModel() {
    val users = mutableStateOf<List<ProfileEntity>>(generateDummyProfiles())
    private fun generateDummyProfiles(): List<ProfileEntity> {
        val names = listOf(
            "John", "Emily", "Michael", "Rachel", "Sophie",
            "Daniel", "Laura", "David", "Sarah", "Chris",
            "Anna", "Max", "Julia", "Robert", "Lisa",
            "James", "Maria", "Paul", "Alice", "Tom"
        )
        val nickNames = listOf(
            "Skywalker", "Ranger", "Phoenix", "Shadow", "Maverick",
            "Viper", "Wizard", "Rogue", "Spartan", "Hunter"
        )
        val hobbies = listOf(
            "Reading", "Gaming", "Traveling", "Cooking", "Photography",
            "Painting", "Music", "Hiking", "Cycling", "Swimming"
        )

        return List(5000) { index ->
            ProfileEntity(
                id = index,
                name = names.random(),
                nickName = nickNames.random(),
                hobby = hobbies.random(),
                age = (18..60).random()
            )
        }
    }
}


ViewModel에서 생성한 데이터를 HorizontalRecyclerViewScreen 컴포저블에서 가져와 사용합니다. UI로직과 데이터 처리를 분리한 구조이기 때문에 코드의 가독성과 관리를 향상하는데 도움을 주는 장점이 있습니다. 그리고 ViewModel에서 데이터를 관리하면 안드로이드의 라이프사이클을 인식하므로 화면 회전에도 데이터 상태를 유지할 수 있습니다.

@Composable
fun HorizontalRecyclerViewScreen(
    mainNavController: NavHostController,
    viewModel: HorizontalRecyclerViewViewModel
) {
    val users by remember { viewModel.users }

    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(top = 10.dp)
    ) {
        Spacer(modifier = Modifier.height(10.dp))

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

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

        CustomVerticalRecyclerView(
            users = users
        )
    }
}

3. LazyRow에서 itemIndexed 사용하기

LazyRow의 itemIndexed 함수를 사용하면, 각 항목에 고유 인덱스를 부여할 수 있습니다. 인덱스와 각 아이템의 항목을 람다의 인자로 전달받을 수 있기 때문에 데이터 바인딩하는데 무척 편리합니다. 그래서 실무에서는 itemIndexed를 많이 사용하게 되는데, 상황에 맞춰서 item, items 함수를 사용할 때도 편리합니다.

4. 정리

안드로이드 Jetpack Compose의 LazyRow를 이용한 RecyclerView 구현은 기존 XML 방식에 비해서 코드가 간결하여 간단하게 개발할 수 있습니다. 또한, 아이템 간의 간격을 추가할 때도 Arrangement.spacedBy() 함수를 통해 설정할 수 있으며 별도의 Adapter 클래스가 필요하지 않은 것도 편하게 느껴집니다.

이 글을 통해 안드로이드 Jetpack Compose UI에서 LazyRow를 활용한 RecyclerView 구현 방법을 이해하고, 여러분의 프로젝트에 적용하는데 도움이 되기를 바랍니다.