How can I implement a HorizontalPager with endless scrolling using Jetpack Compose?

Abhishek Dharmik
4 min readFeb 19, 2024

--

In Android app development, displaying content in a visually appealing and interactive manner is crucial for providing an excellent user experience. One popular way to achieve this is by using a horizontal pager, which allows users to swipe through a series of items horizontally, similar to flipping through pages in a book. In this article, we will explore how to implement a horizontal pager in Kotlin for Android using the Jetpack Compose framework.

Setting up the Code

Jetpack Compose HorizontalPager with Endless Scroll

To begin, let’s take a look at the following code snippet:

fun ContentView() {

val list = listOf(
R.drawable.daredevil,
R.drawable.batman,
R.drawable.iron_man,
R.drawable.dr_strange,
R.drawable.black_panther,
)

val pagerState = rememberPagerState()

Box(
modifier = Modifier
.fillMaxSize()
.background(Color.White),
contentAlignment = Alignment.Center
) {

CompositionLocalProvider(LocalOverscrollConfiguration provides null) {
HorizontalPager(
modifier = Modifier.height(250.dp),
pageCount = Int.MAX_VALUE,
pageSpacing = 15.dp,
contentPadding = PaddingValues(horizontal = 40.dp),
state = pagerState
) { index ->
list.getOrNull(
index % (list.size)
)?.let { item ->
BannerItem(image = item)
}
}
}
}

LaunchedEffect(key1 = Unit, block = {
var initPage = Int.MAX_VALUE / 2
while (initPage % list.size != 0) {
initPage++
}
pagerState.scrollToPage(initPage)
})
}

Understanding the Code

Let’s break down the code and understand each component:

1. Data Preparation

The list variable represents a list of drawable resources. Each drawable resource corresponds to an image that will be displayed within the horizontal pager. In this example, the list includes images of Daredevil, Batman, Iron Man, Doctor Strange, and Black Panther.

2. Pager State Initialization

The pagerState variable is created using the rememberPagerState() function. This function is provided by the Jetpack Compose library and is used to retain the state of the horizontal pager across recompositions. It keeps track of the current page and provides methods for scrolling to specific pages.

3. UI Composition

The Box composable is used as the root container for the horizontal pager. It has a modifier that sets its size to fill the maximum available space and a background color of white.

Within the Box, we use the CompositionLocalProvider composable to override the default overscroll configuration. By providing null as the value for LocalOverscrollConfiguration, we disable the overscroll effect when reaching the ends of the pager.

4. Horizontal Pager

The HorizontalPager composable is the core component responsible for displaying the content in a horizontal swipeable manner. It takes several parameters:

  • modifier: Sets the height of the pager to 250.dp, providing a fixed height for the pager.
  • pageCount: Sets the number of pages in the pager. In this case, Int.MAX_VALUE is used to create a large number of pages to allow for continuous scrolling.
  • pageSpacing: Specifies the spacing between pages. Here, it is set to 15.dp.
  • contentPadding: Sets the horizontal padding for the content within each page. It is set to 40.dp.
  • state: Binds the pagerState variable to the pager, allowing it to manage the scrolling behavior and keep track of the current page.

Inside the HorizontalPager, a lambda function is called for each page, passing the index of the current page as a parameter. Using the index and the size of the list, the current drawable resource is retrieved using list.getOrNull(index % list.size). If a valid drawable resource is found, it is passed to the BannerItem composable, which represents the UI for displaying an image banner.

fun BannerItem(image: Int) {
Box(
modifier = Modifier
.fillMaxSize()
.clip(RoundedCornerShape(10.dp))
.background(Color.Black),
contentAlignment = Alignment.Center
) {
Image(
painter = painterResource(id = image),
contentScale = ContentScale.Crop,
contentDescription = "Banner Image"
)
}
}

5. Initial Page Setup

The LaunchedEffect composable is used to perform a side effect when the component is first launched. In this case, it is used to set the initial page of the pager.

The block parameter within LaunchedEffect contains the code that calculates the initial page. It starts with Int.MAX_VALUE / 2 and increments it until the page is divisible by the size of the list. This ensures that the initial page is a multiple of the list size, allowing for a seamless scrolling experience.

Once the initial page is calculated, pagerState.scrollToPage(initPage) is called to scroll the pager to the calculated page.

Full Source Code:

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ContentView()
}
}
}

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun ContentView() {

val list = listOf(
R.drawable.daredevil,
R.drawable.batman,
R.drawable.iron_man,
R.drawable.dr_strange,
R.drawable.black_panther,
)

val pagerState = rememberPagerState()

Box(
modifier = Modifier
.fillMaxSize()
.background(Color.White),
contentAlignment = Alignment.Center
) {

CompositionLocalProvider(LocalOverscrollConfiguration provides null) {
HorizontalPager(
modifier = Modifier.height(250.dp),
pageCount = Int.MAX_VALUE,
pageSpacing = 15.dp,
contentPadding = PaddingValues(horizontal = 40.dp),
state = pagerState
) { index ->
list.getOrNull(
index % (list.size)
)?.let { item ->
BannerItem(image = item)
}
}
}
}

LaunchedEffect(key1 = Unit, block = {
var initPage = Int.MAX_VALUE / 2
while (initPage % list.size != 0) {
initPage++
}
pagerState.scrollToPage(initPage)
})
}

@Composable
fun BannerItem(image: Int) {
Box(
modifier = Modifier
.fillMaxSize()
.clip(RoundedCornerShape(10.dp))
.background(Color.Black),
contentAlignment = Alignment.Center
) {
Image(
painter = painterResource(id = image),
contentScale = ContentScale.Crop,
contentDescription = "Banner Image"
)
}
}

Conclusion

The code provided demonstrates how to implement a horizontal pager in Kotlin for Android using the Jetpack Compose framework. The HorizontalPager composable, along with the rememberPagerState() function, provides a powerful way to manage the state and behavior of the pager.

Check out Git Repo: Endless-Compose-Pager-Example

--

--

Abhishek Dharmik
Abhishek Dharmik

Written by Abhishek Dharmik

Android developer with a passion for IoT. Creating innovative solutions that bridge software and hardware. Tech enthusiast and problem-solver.

No responses yet