Beeeam

LazyColumn 본문

Android

LazyColumn

Beamjun 2024. 1. 23. 18:06

ListView

리스트를 보여주는 뷰들은 자주 사용되는데 이는 Column, Row 또는 LazyColumn, LazyRow를 사용해서 구현할 수 있다.

Column이나 Row만 사용해도 리스트를 보여줄 수 있는데 왜 LazyColumn이나 LazyRow를 사용하는걸까?

 

Normal vs Lazy

1. 뷰가 초기화될 때 차이

Column이나 Row는 초기화될 때 화면에 보여지는 아이템 개수에 상관없이 리스트의 모든 아이템을 만든다.

하지만 LazyColumn, LazyRow는 화면에 보여지는 아이템의 개수 만큼의 아이템들을 만들게 된다. 그리고 스크롤해서 표시 영역에서 벗어나게 되면 아이템을 파괴하고 새로운 아이템을 만든다.

이러한 이유때문에 리스트의 개수가 적으면 Column, Row를 사용하고 리스트의 개수가 많으면 LazyColumn, LazyRow를 사용하면 좋다.

2. 스크롤

Column, Row는 아이템의 개수가 많아서 표시 영역을 벗어나는 경우 해당 아이템들은 짤리게 된다. 그래서 ScrollState를 사용하여 스크롤 상태를 부여해야 스크롤을 할 수 있게 만들어 해당 아이템들을 볼 수 있다.

하지만 LazyColumn, LazyRow는 기본적으로 스크롤이 가능해서 추가적으로 ScrollState를 부여할 필요가 없다.

Column, Row

Column, Row를 사용하는 예시를 보자

(예시는 Column으로만 작업할게요~)

@Composable
fun MainScreen() {
    Column(
        modifier = Modifier.fillMaxSize()
    ) {
        repeat(100) { index ->
            Text(
                modifier = Modifier.fillMaxWidth(),
                text = index.toString()
            )
        }
    }
}

위의 코드로 0 ~ 99의 Text를 보여주는 Column을 만들 수 있다.

 

실행 화면을 확인하면 99까지 모든 아이템을 확인할 수 없다. 확인할 수 없는 아이템들을 보려면 Column에 스크롤을 추가하면 되는데 다음과 같이 구현할 수 있다.

@Composable
fun MainScreen() {
    val scrollState = rememberScrollState()
    Column(
        modifier = Modifier
            .fillMaxSize()
            .verticalScroll(scrollState),
    ) {
        repeat(100) { index ->
            Text(
                modifier = Modifier.fillMaxWidth(),
                text = index.toString()
            )
        }
    }
}

rememberScrollState() 함수를 사용해서 스크롤 상태 객체를 만들고 이를 Column의 Modifier.verticalScroll() 함수의 인자에 전달하면 된다.

 

한번에 리스트의 처음이나 마지막으로 이동하는 즉, 사용자가 원하는 위치로 자동으로 스크롤되게 하는 방법은 ScrollState 인스턴스의 다음 함수들을 호출하는 것이다.

animateScrollTo(value: Int) : 애니메이션을 이용하여 지정한 픽셀 위치까지 부드럽게 스크롤

scrollTo(value: Int) : 지정한 픽셀 위치까지 바로 스크롤

 

자연스러운 화면을 위해서 이 함수들은 비동기적으로 동작해야 하고 이 때문에 코루틴 스코프 내에서 실행되야 한다. 그래서 rememberCoroutineScope() 함수를 사용하여 코루틴 스코프 인스턴스를 얻고 이 안에서 실행하면 된다.

@Composable
fun MainScreen() {
    val scrollState = rememberScrollState()
    val coroutineScope = rememberCoroutineScope()
    Box(
        modifier = Modifier.fillMaxSize()
    ) {
        Column(
            modifier = Modifier
                .verticalScroll(scrollState),
        ) {
            repeat(100) { index ->
                Text(
                    modifier = Modifier.fillMaxWidth(),
                    text = index.toString()
                )
            }
        }
        Button(
            onClick = {
                coroutineScope.launch {
                    scrollState.animateScrollTo(0)
                }
            },
            modifier = Modifier.align(Alignment.BottomCenter)
        ) {}
    }
}

 

LazyColumn, LazyRow

(예시는 LazyColumn으로만 작업할게요~)

@Composable
fun MainScreen() {
    val itemList = mutableListOf<Int>()
    repeat(100) {
        itemList.add(it)
    }
    Box(
        modifier = Modifier.fillMaxSize()
    ) {
        LazyColumn {
            items(items = itemList) { content ->
                Text(
                    modifier = Modifier.fillMaxWidth(),
                    text = content.toString()
                )
            }
        }
    }
}

LazyColumn 안에 item 또는 items 블럭을 사용하여 요소를 추가할 수 있다.

item 블럭을 사용하면 한 개의 아이템만 만들 수 있고 items를 사용하면 여러 개의 아이템을 만들 수 있다.

 

LazyColumn에서 사용자가 원하는 위치로 자동으로 스크롤되게 하는 방법은 LazyListState 인스턴스가 제공하는 함수를 호출하는 것이다.

animateScrollToItem(index: Int): 지정한 리스트 아이템까지 부르럽게 스크롤

scrollToItem(index: Int): 지정한 리스트 아이템까지 곧바로 스크롤한다.

 

이 함수들도 마찬가지로 코루틴 스코프 안에서 실행한다.

@Composable
fun MainScreen() {
    val coroutineScope = rememberCoroutineScope()
    val listState = rememberLazyListState()
    val itemList = mutableListOf<Int>()
    repeat(100) {
        itemList.add(it)
    }
    Box(
        modifier = Modifier.fillMaxSize()
    ) {
        LazyColumn(
            state = listState,
        ) {
            items(items = itemList) { content ->
                Text(
                    modifier = Modifier.fillMaxWidth(),
                    text = content.toString()
                )
            }
        }
        Button(
            onClick = {
                coroutineScope.launch {
                    listState.animateScrollToItem(0)
                }
            },
            modifier = Modifier.align(Alignment.BottomCenter)
        ) {}
    }
}

 

Sticky Header

스티키 헤더를 사용하면 밑의 이미지와 같이 아이템을 한 헤더의 아래에 모을 수 있다.

이 헤더는 아이템들이 스크롤 되어 사라져도 남아있다가 그룹핑된 아이템들이 모두 사라져야 화면에서 사라진다.

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun MainScreen() {
    val phones = listOf(
        "Apple iPhone 12", "Google Pixel 4", "Samsung Galaxy 6s", "OnePlus 7",
        "Apple iPhone 7", "Google Pixel 6", "Samsung Galaxy Z Flip", "OnePlus 9 Pro", "Apple iPhone 13",
        "Google Pixel 4a", "Apple iPhone 8", "Samsung Galaxy S23", "Samsung Galaxy S24", "Apple iPhone 14",
        "Apple iPhone 15 Pro", "Google Pixel 4a", "Samsung Galaxy Z Flip 4", "Samsung Galaxy Z Flip 5"
    )
    val groupedPhone = phones.groupBy { it.substringBefore(' ') }

    LazyColumn(
        modifier = Modifier.fillMaxSize()
    ) {
        groupedPhone.forEach { (manufacturer, phone) ->
            stickyHeader {
                Text(
                    modifier = Modifier
                        .background(Color.Black)
                        .fillMaxWidth(),
                    text = manufacturer,
                    fontSize = 20.sp,
                    color = Color.White,
                )
            }
            items(items = phone) {
                Text(
                    text = it,
                    modifier = Modifier
                        .fillMaxWidth()
                        .padding(20.dp),
                )
            }
        }
    }
}

'Android' 카테고리의 다른 글

명령형 vs 선언형, Jetpack Compose  (0) 2024.02.04
MVI  (0) 2024.01.24
컴포즈 내비게이션  (2) 2023.12.26
컴포즈 공부 4일차 (code lab)  (0) 2023.12.01
컴포즈 공부 3일차  (1) 2023.11.28