
Mastering Scroll in Jetpack Compose — PART 1
Mastering Scroll in Jetpack Compose — PART 1 관련
Scrolling is a fundamental element of any mobile app, and Jetpack Compose provides powerful tools to create smooth and efficient scrolling experiences. This article dives into the world of scroll in Compose, starting with the foundational concepts and gradually progressing towards more complex scenarios.
Compose offers two workhorses for creating scrollable lists:LazyColumnfor vertical scrolling andLazyRowfor horizontal scrolling. They behave similarly toRecyclerViewin XML, efficiently rendering only the visible items while maintaining excellent performance.
Lazy Column
@Composable
fun LazyColumnExample() {
    val items = listOf("Item 1", "Item 2", "Item 3", "Item 4", "Item 5", "Item 6", "Item 7", "Item 8", "Item 9", "Item 10","Item 1", "Item 2", "Item 3", "Item 4", "Item 5", "Item 6", "Item 7", "Item 8", "Item 9", "Item 10")
    LazyColumn(
        modifier = Modifier
            .fillMaxSize()
            .background(Color.LightGray)
    ) {
        items(items.size) { item ->
            Box(
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(16.dp),
                contentAlignment = Alignment.Center
            ) {
                Text(
                    text = items.get(item),
                    color = Color.Black
                )
            }
        }
    }
}
@Preview
@Composable
fun Preview() {
    LazyColumnExample()
}

Preview()Lazy Row
@Composable
fun LazyRowExample() {
    val items = listOf("Item 1", "Item 2", "Item 3", "Item 4", "Item 5", "Item 6", "Item 7", "Item 8", "Item 9", "Item 10","Item 1", "Item 2", "Item 3", "Item 4", "Item 5", "Item 6", "Item 7", "Item 8", "Item 9", "Item 10")
    LazyRow(
        modifier = Modifier
            .fillMaxSize()
            .background(Color.LightGray)
    ) {
        items(items.size) { item ->
            Box(
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(16.dp),
                contentAlignment = Alignment.Center
            ) {
                Text(
                    text = items.get(item),
                    color = Color.Black
                )
            }
        }
    }
}
@Preview
@Composable
fun Preview() {
    LazyRowExample()
}

Preview()WhileLazyColumnandLazyRowhandle most scrolling needs,ScrollStateoffers finer control. It acts as a state holder, keeping track of the current scroll position for various scrollable components likeColumnorLazyColumn.
Understanding Scroll State
In Jetpack Compose,ScrollStateis a state holder that keeps track of the current scroll position for scrollable components such asColumn,LazyColumn, or other containers that support scrolling.ScrollStategives us:
- Position Tracking: You can use
ScrollStateto access the current scroll offset or position of a scrollable component. - Smooth Scrolling:
ScrollStateallows you to control smooth scrolling to specific positions in a list. - Listening to Scroll Events: You can observe changes in the scroll position, which is particularly useful for things like showing/hiding toolbar animations based on scroll offset.
 
Important Properties and Methods ofScrollState
value: The current scroll offset in pixels.maxValue: The maximum scroll offset. This is helpful for detecting when the scroll has reached the end of a container.
animateScrollTo(offset: Int): Smoothly animates scrolling to the given offset in pixels.scrollTo(offset: Int): Instantly scrolls to the given offset.
Scroll State Types in Compose
There are different types of scroll states depending on the type of container:
- ScrollState: Used for simple scrolling in containers like
Column. - LazyListState: Specifically used for
LazyColumnandLazyRow, giving more control over items and visibility states. 
Example 1: Using ScrollState with Column
To start, let’s see a simple example where we useScrollStateto observe and control the scroll position of aColumnthat supports vertical scrolling.
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
@Composable
fun ScrollableColumnExample() {
    // Initialize the scroll state
    val scrollState = rememberScrollState()
    Column(
        modifier = Modifier
            .fillMaxSize()
            .verticalScroll(scrollState) // Attach scroll state to Column
    ) {
        // Display some items with varying colors
        for (i in 1..50) {
            Box(
                modifier = Modifier
                    .fillMaxWidth()
                    .height(100.dp)
                    .background(if (i % 2 == 0) Color.LightGray else Color.Gray),
                contentAlignment = Alignment.Center
            ) {
                Text("Item $i")
            }
        }
    }
    // Observe the scroll offset and print it
    LaunchedEffect(scrollState.value) {
        println("Current scroll position: ${scrollState.value}")
    }
}

Preview()Explanation
In this example:
- We create a
Columnwith aScrollStatethat allows it to scroll vertically. verticalScroll(scrollState)attaches the scroll state to the column.- Inside the
LaunchedEffect, we print the current scroll position each timescrollState.valuechanges. 
This example demonstrates basic scroll behavior in aColumnand how to observe the scroll position.
Example 2: Smooth Scrolling withScrollState
If you want to programmatically scroll to a specific position, you can usescrollState.animateScrollTo(offset). This is helpful for features like “scroll to top” or “scroll to a specific item.”
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.launch
@Composable
fun SmoothScrollingExample() {
    val scrollState = rememberScrollState()
    val coroutineScope = rememberCoroutineScope()
    Column(modifier = Modifier.fillMaxSize()) {
        Button(
            onClick = {
                // Smooth scroll to the top
                coroutineScope.launch {
                    scrollState.animateScrollTo(0)
                }
            },
            modifier = Modifier.fillMaxWidth()
        ) {
            Text("Scroll to Top")
        }
        Spacer(modifier = Modifier.height(16.dp))
        Column(
            modifier = Modifier
                .fillMaxSize()
                .verticalScroll(scrollState)
        ) {
            for (i in 1..50) {
                Text(
                    text = "Item $i",
                    modifier = Modifier
                        .fillMaxWidth()
                        .padding(16.dp),
                    color = Color.White
                )
            }
        }
    }
}
Explanation
- We use
rememberCoroutineScope()to launch a coroutine that allows asynchronous scrolling. - The button calls
scrollState.animateScrollTo(0)to scroll smoothly to the top of the list. animateScrollTo()is an asynchronous function, making the scrolling smooth and animated.
Nested Scrolling
Nested scrolling is a concept where multiple scrolling containers work together to create a single scroll gesture.
Compose provides multiple ways of handling nested scrolling between composables. A typical example of nested scrolling is a list inside another list, and a more complex case is a collapsing toolbar.
Let’s understand the basic nested scrolling with an example.

Here we have a scrollable list, and each list has a child list which is also scrollable. we are also adding expand and collapse view to show and hide each list item’s child list.
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ExpandLess
import androidx.compose.material.icons.filled.ExpandMore
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@Composable
fun NestedScrollingExample() {
    // Parent scroll state
    val parentScrollState = rememberScrollState()
    // Sample list data
    val items = (1..10).toList()
    Column(
        modifier = Modifier
            .fillMaxSize()
            .verticalScroll(parentScrollState)
            .padding(16.dp)
    ) {
        items.forEach { item ->
            ExpandableItem(item)
        }
    }
}
@Composable
fun ExpandableItem(item: Int) {
    // State to track if the item is expanded
    var isExpanded by remember { mutableStateOf(false) }
    Column(
        modifier = Modifier
            .fillMaxWidth()
            .padding(vertical = 8.dp)
            .background(Color.LightGray)
    ) {
        // Header for the expandable item
        Row(
            modifier = Modifier
                .fillMaxWidth()
                .clickable { isExpanded = !isExpanded }
                .padding(16.dp),
            verticalAlignment = Alignment.CenterVertically
        ) {
            Text(
                text = "Item $item",
                fontSize = 18.sp,
                modifier = Modifier.weight(1f)
            )
            Icon(
                imageVector = if (isExpanded) Icons.Default.ExpandLess else Icons.Default.ExpandMore,
                contentDescription = "Expand/Collapse"
            )
        }
        // Child scrollable list, visible only when expanded
        if (isExpanded) {
            val childScrollState = rememberScrollState()
            Column(
                modifier = Modifier
                    .fillMaxWidth()
                    .height(150.dp) // Fixed height for nested scrollable area
                    .verticalScroll(childScrollState)
                    .background(Color.White)
                    .padding(8.dp)
            ) {
                // Nested list content
                (1..5).forEach { subItem ->
                    Text(
                        text = "Sub-item $subItem of Item $item",
                        fontSize = 16.sp,
                        modifier = Modifier
                            .fillMaxWidth()
                            .padding(vertical = 4.dp)
                            .background(Color(0xFFF0F0F0))
                            .padding(8.dp)
                    )
                }
            }
        }
    }
}
@Preview
@Composable
fun showPeview() {
    NestedScrollingExample()
}
How Nested Scrolling Works Here
- Theparent scroll(
parentScrollState) allows the entire list of items to scroll vertically. - Eachchild scroll(
childScrollState) manages the scrolling within the expanded item independently. - This approach avoids using
LazyColumnorLazyRow, handling scrolling manually withScrollStateinstead. 
Up Next: Conquering Collapsing Toolbars
This article has covered the fundamental aspects of scroll in Compose. In the next part, we’ll tackle a more intricate scenario: creating a collapsing toolbar that reacts to scrolling within a list. We’ll explore how to leverage nested scrolling to achieve this dynamic and visually appealing effect.
This concludes the first part of our series on scroll in Jetpack Compose. Stay tuned for the next chapter where we’ll unlock the secrets of collapsing toolbars and complex nested scrolling!
Now PART 2 is available
References
I hope this article was helpful to you. You can write me back atkarishma.agr1996@gmail.comif you want me to improve something in upcoming articles. Your feedback is valuable.
Also, follow me on Medium andLinkedin (karishma-agrawal-she-her-06966a126)
Your claps are appreciated to help others find this article 😃 .

Info
This article is previously published on proandroiddev
