Parallax Scrolling using LazyColumn
Tags: android, jetpack, compose, kotlin
Published: 2022-08-15 10:35:09

A parallax scrolling effect is always nice to look at and it catches the attention of the user. Using Jetpack Compose this effect can be implemented fairly easy.

![Parallax Scrolling Effect](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s6tzysrab8fyhwfwplmt.gif)
 
## Just another item
In the example we have an image on top of a list with text items. This could mean we have an Image followed by a **LazyColumn**, but when looking at the API **LazyColumn** we can not only call the **items** method and give it a list with data to create our items from.

```Kotlin
val list = (0..1_000).map{ "Item $it" }.toList()

LazyColumn {
    items(list) { item ->
        Text(text = item)
    }
}
```

We can also call multiple **item** and **items** methods in the scope of a **LazyColumn**. It is not limited to just having it call the **items** function. 
For this means, we can use **item** to display the image, followed by **items** to create the list.

```Kotlin
val list = (0..1_000).map{ "Item $it" }.toList()

LazyColumn {

    item {
        Image(
            painter = resourcePainter(id = R.drawable.image),
            contentDescription = "Top image"),
            contentScale = ContentScale.Crop,
            modifier = Modifier.fillParentMaxWidth()
    }

    items(list) { item ->
        Text(text = item)
    }
}
```

With that we already have a list with two types of items and of course right now they are scrolling in the same speed.

![LazyColumn with two item types](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rsrw5s1neybz8p8dwbwx.gif)


## Not so fast
In order to achieve a parallax effect, we need to reduce the scrolling speed of the image, while keeping the scrolling speed of the other items untouched.

To influence the scrolling we need 
1. A way to get to scrolling information
2. A way to manipulate the scrolling behaviour of the image.

For the scrolling information we can utilise **LazyColumns** parameter **LazyListState**, it contains the information we need. To get to it, instead of keeping the default, we create our own and pass it in. 

```Kotlin
val lazyListState = rememberLazyListState()

LazyColumn(state = lazyListState){
    ...
}
```

Derived from the **LazyListState** we can calculate a slower translation of our image along the y-axis, compared to the other items.

```Kotlin
val firstItemTranslationY by remember {
		derivedStateOf {
			when {
				lazyListState.layoutInfo.visibleItemsInfo.isNotEmpty() && lazyListState.firstVisibleItemIndex == 0 -> lazyListState.firstVisibleItemScrollOffset * .6f
				else                                                                                               -> 0f
			}
		}
	}
```

We check if the image is visible on screen, if not we don't want to do anything and just say behave normally, by returning a translation of 0. As long as the image is visible we take the current scrolling offset and multiple it by a number between 1 and 0. The closer the number is to 1 the slow the image will move with the scrolling. For the parallax scroll effect we want to keep the image longer in the screen as it normally would be.

You can play around with that value to adjust the speed as you like. For this example let's keep it somewhere in the middle with 0.6.

Now we have the value to adjust our images scrolling. But where should be apply it? For that Compose offers a pretty handy **Modifier** that we can use. **Modifier.graphicsLayer** provides us with the possibility to manipulate scale, rotation, alpha, translation and more of a Composable it is applied used on.

The translation sounds like a good option. Let's apply **Modifier.graphicsLayer** to our image and set the vertical translation to the value we just calculated.

```Kotlin
...
item {
    Image(
        painter = painterResource(id = R.drawable.image),
        contentDescription = "Top image",
        contentScale = ContentScale.Crop,
        modifier = Modifier
            .fillParentMaxWidth()
            .graphicsLayer {
                translationY = firstItemTranslationY
            }
    )
}
...
```

![Parallax Scrolling](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vdl9lykl3takfp56t5o6.gif)
 

Just to play around a little more, let's also fade out the image slowly while it is scrolling.

```Kotlin
val visibility by remember {
		derivedStateOf {
			when {
				lazyListState.layoutInfo.visibleItemsInfo.isNotEmpty() && lazyListState.firstVisibleItemIndex == 0 -> {
					val imageSize = lazyListState.layoutInfo.visibleItemsInfo[0].size
					val scrollOffset = lazyListState.firstVisibleItemScrollOffset

					scrollOffset / imageSize.toFloat()
				}
				else                                                                                               -> 1f
			}
		}
	}
```

The procedure is basically the same as before, the difference is instead of taking the offset of the first item we take its size and its scroll offset and calculate the alpha value.

When we apply it we subtract our calculated visibility from 1 and the image will be fully transparent when it leaves the screen.

```Kotlin
.graphicsLayer {
    alpha = 1f - visibility
    translationY = firstItemTranslationY
}
```

![Parallax Scrolling and Alpha](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1dsgkd3j68ph4hfto2u4.gif)

## Conclusion
With Jetpack Composes LazyColumn and **Modifier.graphicsLayer** it is really simple to create a parallax scrolling effect. Calculate the translation derived from the **LazyListState** and apply it to the **Modifier**.

The whole code for this can be found on [GitHub](https://github.com/a-frank/parallax-scrolling-compose).

I also added a fading TopBar to this example. I wanted to have the Back Arrow always visible and therefore ended up creating my own TopBar. If you have any ideas on how to make it work with the standard Compose TopBar, leave a comment or write me directly. I would like to hear about it :-).

Hope you enjoyed this small example on parallax scrolling.
See you in the next one :wave:.