ylliX - Online Advertising Network
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: 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.

In Jetpack Compose, ScrollState is a state holder that keeps track of the current scroll position for scrollable components such as Column, LazyColumn, 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 Scrolling: ScrollState 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.

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.

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.

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.



Source link

Leave a Reply

Your email address will not be published. Required fields are marked *