Blog Infos
Author
Published
Topics
, , , ,
Published
Photo by Andrew Neel on Unsplash

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: LazyColumn for vertical scrolling and LazyRow for horizontal scrolling. They behave similarly to RecyclerView in 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()
}

 

 

 

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()
}

 

 

While LazyColumn and LazyRow handle most scrolling needs, ScrollState offers finer control. It acts as a state holder, keeping track of the current scroll position for various scrollable components like Column or LazyColumn.

Understanding Scroll State

In Jetpack Compose, ScrollState is a state holder that keeps track of the current scroll position for scrollable components such as ColumnLazyColumn, or other containers that support scrolling. ScrollState gives us:

  1. Position Tracking: You can use ScrollState to access the current scroll offset or position of a scrollable component.
  2. Smooth ScrollingScrollState allows you to control smooth scrolling to specific positions in a list.
  3. 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 of ScrollState

Properties:

  • 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.

Methods:

  • 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 LazyColumn and LazyRow, giving more control over items and visibility states.
Example 1: Using ScrollState with Column

To start, let’s see a simple example where we use ScrollState to observe and control the scroll position of a Column that 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}")
    }
}

 

 

Explanation

In this example:

  • We create a Column with a ScrollState that allows it to scroll vertically.
  • verticalScroll(scrollState) attaches the scroll state to the column.
  • Inside the LaunchedEffect, we print the current scroll position each time scrollState.value changes.

This example demonstrates basic scroll behavior in a Column and how to observe the scroll position.

Example 2: Smooth Scrolling with ScrollState

If you want to programmatically scroll to a specific position, you can use scrollState.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.

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

No results found.

Jobs

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
  • The parent scroll (parentScrollState) allows the entire list of items to scroll vertically.
  • Each child scroll (childScrollState) manages the scrolling within the expanded item independently.
  • This approach avoids using LazyColumn or LazyRow, handling scrolling manually with ScrollState instead.
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 at karishma.agr1996@gmail.com if you want me to improve something in upcoming articles. Your feedback is valuable.

Also, follow me on Medium and Linkedin

Your claps are appreciated to help others find this article 😃 .

This article is previously published on proandroiddev.com.

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
It’s one of the common UX across apps to provide swipe to dismiss so…
READ MORE
blog
In this part of our series on introducing Jetpack Compose into an existing project,…
READ MORE
blog
In the world of Jetpack Compose, where designing reusable and customizable UI components is…
READ MORE
blog

How to animate BottomSheet content using Jetpack Compose

Early this year I started a new pet project for listening to random radio…
READ MORE
Menu