Swipeable Image Slider in Jetpack Compose

Introduction

When using several images in an Android app, we typically need an image carousel or some sort of view pager to display the images. We can easily demonstrate this in modern Android with animation thanks to Jetpack compose. 

In this article, we'll develop a custom composable function that can be used to display a list of images with animation, which will be slidable. So let's begin now without more delay.

Setup

Add the dependency in 'app/build.gradle'.

implementation "io.coil-kt:coil-compose:2.3.0"

Consider adding the latest dependency of the coil. You can the latest dependency here https://coil-kt.github.io/coil/compose/

and don't forget to add the internet permission in the AndroidManifest.xml file.

<uses-permission android:name="android.permission.INTERNET"/>

Now with the above setup, let's move towards the Our Goal with the following steps.

Step 1. Create new JetPack Compose project

Create a new Project in JetPack Compose or open anyone if you have, and in the main activity, delete some existing code and place a new composable function CustomSlider() as the below code.

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ImageAnimationAppTheme {
                // A surface container using the 'background' color from the theme
                Surface(
                    modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background
                ) {

                    val sliderList = remember {
                        mutableListOf(
                            "https://www.gstatic.com/webp/gallery/1.webp",
                            "https://www.gstatic.com/webp/gallery/2.webp",
                            "https://www.gstatic.com/webp/gallery/3.webp",
                            "https://www.gstatic.com/webp/gallery/4.webp",
                            "https://www.gstatic.com/webp/gallery/5.webp",
                        )
                    }
                    CustomSlider(sliderList = sliderList)
                }
            }
        }
    }
}

Here I created a list of URL strings which I passed through the parameter of the custom slider that is going to serve us the images. The CustomSlider is the function that we are going to define in Step 2. You can modify this custom composable function to your need.

Step 2. Define CustomSlider Composable function

Here we define a `CustomSlider` composable function, which creates a customizable image slider component. The function takes several parameters to customize its appearance and behavior. The `pagerState` and `scope` variables are created using `rememberPagerState` and `rememberCoroutineScope`, respectively, to manage the state of the pager and handle coroutine operations.

The composable function wraps its content in a `Column` layout, aligning its children horizontally and vertically at the center. Inside this column, there's a `Row` that encompasses the `HorizontalPager` component. The `HorizontalPager` is responsible for displaying the images in a horizontal sequence, and its properties include the `pageCount` (number of images), `state` (pager state), and `modifier` (layout modifiers).

Within the `HorizontalPager`, each image page is defined using a lambda where the `page` parameter indicates the index of the current page. The `pageOffset` is calculated to create a parallax effect based on the pager's current position. Additionally, a `scaleFactor` is computed to adjust the scale and opacity of the images as they slide in and out of view. This gives a dynamic effect to the images based on their position.

The `Box` composable is used to wrap each image. It applies various modifiers such as `graphicsLayer` to scale the image, `alpha` to control its opacity, `padding` for spacing, and `clip` to round the corners of the image.

The `CustomSlider` composable creates an advanced image slider component with customizable options for image appearance and behavior, leveraging Jetpack Compose's declarative approach to UI design. This code snippet demonstrates the power and flexibility of Jetpack Compose in creating interactive and visually engaging UI components.

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun CustomSlider(
    modifier: Modifier = Modifier,
    sliderList: MutableList<String>,
    backwardIcon: ImageVector = Icons.Default.KeyboardArrowLeft,
    forwardIcon: ImageVector = Icons.Default.KeyboardArrowRight,
    dotsActiveColor: Color = Color.DarkGray,
    dotsInActiveColor: Color = Color.LightGray,
    dotsSize: Dp = 10.dp,
    pagerPaddingValues: PaddingValues = PaddingValues(horizontal = 65.dp),
    imageCornerRadius: Dp = 16.dp,
    imageHeight: Dp = 250.dp,
) {

    val pagerState = rememberPagerState()
    val scope = rememberCoroutineScope()

    Column(
        horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center
    ) {
        Row(
            modifier = modifier.fillMaxWidth(),
            verticalAlignment = Alignment.CenterVertically,
            horizontalArrangement = Arrangement.Center,
        ) {

            
            HorizontalPager(
                pageCount = sliderList.size,
                state = pagerState,
                contentPadding = pagerPaddingValues,
                modifier = modifier.weight(1f)
            ) { page ->
                val pageOffset =
                    (pagerState.currentPage - page) + pagerState.currentPageOffsetFraction

                val scaleFactor = 0.75f + (1f - 0.75f) * (1f - pageOffset.absoluteValue)


                Box(modifier = modifier
                    .graphicsLayer {
                        scaleX = scaleFactor
                        scaleY = scaleFactor
                    }
                    .alpha(
                        scaleFactor.coerceIn(0f, 1f)
                    )
                    .padding(10.dp)
                    .clip(RoundedCornerShape(imageCornerRadius))) {
                    AsyncImage(
                        model = ImageRequest.Builder(LocalContext.current).scale(Scale.FILL)
                            .crossfade(true).data(sliderList[page]).build(),
                        contentDescription = "Image",
                        contentScale = ContentScale.Crop,
                        placeholder = painterResource(id = R.drawable.img),
                        modifier = modifier.height(imageHeight)
//                            .alpha(if (pagerState.currentPage == page) 1f else 0.5f)
                    )
                }
            }
            
        }
    }
}

Step 3. Define Left-Right buttons 

We successfully animated the 'HorizontalPager' in the previous steps. Now we will define left and right buttons with dots indicators in the slider. These elements will improve user interaction and experience. The dots button is clickable, so we can browse directly using dots, buttons, and, of course, slides.

The left navigation button (`IconButton`) is enabled when the pager can scroll backward. When clicked, it triggers an animation to scroll the pager to the previous page using `animateScrollToPage` from the `pagerState`. The icon for this button is provided through the `backwardIcon` parameter.

Similarly, the right navigation button (`IconButton`) is enabled when the current page is not the last page in the `sliderList`. Similar to the left button, clicking it triggers an animation to scroll the pager to the next page.

Following the navigation buttons, there's another `Row` that displays pagination dots beneath the slider. Each dot corresponds to a page in the `sliderList`. The color of the dot is determined by whether it corresponds to the current page, and it is set using the `dotsActiveColor` and `dotsInActiveColor` parameters. Clicking on the dot animates the pager to the selected page.

This extended version of the `CustomSlider` composable adds navigation buttons (backward and forward) and pagination dots to the image slider. These elements enhance user interaction, allowing users to navigate between images and providing a visual indicator of their position in the slider. This code illustrates how Jetpack Compose empowers developers to create complex and interactive UI components with ease.

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun CustomSlider(
                   //existing parameters
) {


    val pagerState = rememberPagerState()
    val scope = rememberCoroutineScope()

    Column(
        horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center
    ) {
        Row(
            modifier = modifier.fillMaxWidth(),
            verticalAlignment = Alignment.CenterVertically,
            horizontalArrangement = Arrangement.Center,
        ) {

            IconButton(enabled = pagerState.canScrollBackward, onClick = {
                scope.launch {
                    pagerState.animateScrollToPage(pagerState.currentPage - 1)
                }
            }) {
                Icon(imageVector = backwardIcon, contentDescription = "back")
            }
            HorizontalPager(
                pageCount = sliderList.size,
                state = pagerState,
                contentPadding = pagerPaddingValues,
                modifier = modifier.weight(1f)
            ) { page ->
            
            		//existing code 

            }
            IconButton(enabled = pagerState.currentPage != sliderList.size - 1, onClick = {
                scope.launch {
                    pagerState.animateScrollToPage(pagerState.currentPage + 1)
                }
            }) {
                Icon(imageVector = forwardIcon, contentDescription = "forward")
            }
        }
        Row(
            modifier
                .height(50.dp)
                .fillMaxWidth(), horizontalArrangement = Arrangement.Center
        ) {
            repeat(sliderList.size) {
                val color = if (pagerState.currentPage == it) dotsActiveColor else dotsInActiveColor
                Box(modifier = modifier
                    .padding(2.dp)
                    .clip(CircleShape)
                    .size(dotsSize)
                    .background(color)
                    .clickable {
                        scope.launch {
                            pagerState.animateScrollToPage(it)
                        }
                    })
            }
        }
    }
}

You can download the attached project in this article. Let's check out the output.

Output

Conclusion

In this article, we created a swipeable image slider in Jetpack Compose. This was just a normal example of how we can use composable functions to make the user experience more interactive. There are endless possibilities to customize these composable functions according to your own needs and preferences. So don't wait any longer – create your own custom components and develop stunning and interactive UI elements that cater to the modern mobile app experience. Hope this has been a helpful guide for you; it will be helpful for me if you write your genuine comment about this article. Thank you!


Similar Articles