# Parallax Scrolling

Parallax scrolling can be a really interesting technique to use to give parts of your app a bit more life and character. It is a technique built on the mathematical principle if we are moving then objects closer to us appear to move faster than those further away. A great example of this is if we’re driving at night, objects close by such as trees or buildings move through our field of vision quite rapidly, but the moon stays static and appears to follow us. In this article we’ll discover how easy it is to implement parallax scrolling in our app.

Parallax scrolling has been around for a while. Parallax scrolling became popular in video games in the 1980’s and these days can be seen on numerous websites, and it is also becoming popular in apps – specifically in explanatory on-boarding flows. In such cases it can make what can sometimes be quite dry, but necessary content rather more visually exciting. The technique that we’re going to cover is the layer method of parallax scrolling where an image is divided in to separate layers – each representing objects at a different distance from the viewpoint. By scrolling these layers at different rates, we can achieve the parallax effect.

The parallax scrolling is going to be driven by scrolling a `ViewPager2` . I recently wrote about `ViewPager2` and the implementation in this codebase is based on the code from that article. If you’re not familiar with `ViewPager2` it may be worth reading that article before continuing, as it will make what follows much easier to understand.

Let’s begin by looking at the images that represent the different layers. I obtained all of the components for these images from https://publicdomainvectors.org which provides royalty-free vector images, which are open for modification. I extracted and combined separate images in to the layers that I needed.

A couple of things worth pointing out here. Firstly I’ve elected to go with three layers here. Although we could get away with using two, I find a three layer parallax more pleasing. It’s certainly possible to to use more layers, but each additional layer will require additional memory, rendering time, and overdraw. This may not scale well to lower-spec devices, so be careful when considering more layers.

Secondly, I have elected to use `VectorDrawable`s here because they scale much better. However, these are some quite complex vectors which contain complex paths, and create large bitmaps when rendered. Once again this may not scale well to lower-spec devices, but my dev device is a Pixel 2XL which is more than capable of handling these. In a real-world project it may be better to use a bitmap format instead of vectors.

The first layer is the layer that will represent the background and contain the objects that are furthest away from the viewer:

This contains the sky and a cityscape from a distance. The bottom section of the image looks a little odd, but this will actually be overlaid with other layers, so won’t be seen. The second layer represents the middleground objects which are closer than the background objects, but still some distance away from the viewer:

This contains a road, some grass, and some bushes. It is this that will overlay the bottom of the background image. The final layer is the foreground object which appear much closer to the viewer:

This contains a tree, some bushes and a fountain.

One important thing about the middleground and background images is that they are both wider than we actually need. When they are displayed in the layout we’ll only see the middle section of each image. However this will be useful because we can actually change precisely which part of the image is visible by applying a `translationX` to the `ImageView`s containing them. This is important for a layered parallax scrolling implementation.

To overlay these we create a layout with three `ImageViews` overlaid one on top of the other, with the background at the rear, then the middleground, and finally the foreground at the front:

```

```

The `Fragment` which inflates this is about as simple as it gets:

```class ParallaxFragment: Fragment() {

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? = inflater.inflate(R.layout.fragment_main, container, false)

companion object {

fun newInstance(): Fragment = ParallaxFragment()
}
}
```

If we run this we can see the three layers overlaid:

To implement this we need to make a small change to the `SectionsPagerAdapter` from the article on `ViewPager2` to use this `Fragment` instead:

```class SectionsPagerAdapter(
private val activity: FragmentActivity

override fun createFragment(position: Int): Fragment =
ParallaxFragment.newInstance()

fun getPageTitle(position: Int): CharSequence =
activity.resources.getString(TAB_TITLES[position])

override fun getItemCount(): Int = TAB_TITLES.size

companion object {
private val TAB_TITLES = arrayOf(
R.string.tab_text_1,
R.string.tab_text_2
)
}
}
```

Scrolling between pages provides a standard `ViewPager` behaviour with the images all moving in unison – not the parallax effect that we want:

To create the parallax effect we need to move the different layers at different rates. To achieve this we need to know about the scroll position of the `ViewPager2`, and we can get this by attaching a `ViewPager2.PageTransformer` instance to the `ViewPager2`:

```class MainActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val viewPager = findViewById(R.id.view_pager).apply {
orientation = ViewPager2.ORIENTATION_HORIZONTAL
setPageTransformer(ParallaxPageTransformer())
}
val tabs: TabLayout = findViewById(R.id.tabs)
TabLayoutMediator(tabs, viewPager, true) { tab, position ->
}.attach()
}
}
```

All of the work is done in the `ParallaxPageTransformer` instance, but it’s surprisingly small:

```class ParallaxPageTransformer : ViewPager2.PageTransformer {

companion object {
private const val FOREGROUND_FACTOR = 0.5f
}

private val cache = WeakHashMap()

override fun transformPage(page: View, position: Float) {
val offset = page.width * position
page.getMappings().also { mappings ->
mappings[R.id.image_background]?.translationX = -offset
mappings[R.id.image_foreground]?.translationX = offset * FOREGROUND_FACTOR
}
}

private fun View.getMappings(): ViewMappings =
cache[this] ?: ViewMappings().also { mappings ->
mappings.put(R.id.image_background, findViewById(R.id.image_background))
mappings.put(R.id.image_middleground, findViewById(R.id.image_middleground))
mappings.put(R.id.image_foreground, findViewById(R.id.image_foreground))
cache[this] = mappings
}

private class ViewMappings : SparseArray()
}
```

The only method that we need to override is `transformPage()` which takes two arguments – the first is the `View` that the scrolling applies to, and the second is the scrolling position for that `View`. Typically during a scroll, this will be called twice for each positional update, one for each of the `View`s representing the two visible pages in the `ViewPager2`. These `View`s will actually be the layouts for the individual `Fragment`s representing the pages in the `ViewPager2` . The `position` will be a float value between `-1` and `1` with `-1` representing the `View` being completely off the left hand side of the screen; `0 `representing the `View` being centred and fully visible ion the screen; and `1` representing the `View` being completely off the right hand side of the screen. Fractional values represent states in between these.

We first calculate an `offset` value in pixels which we obtain from the width of the `View` multiplied by the `position` value (this implementation assumes horizontal scrolling).

Next we look up a set of `View` mapping. I am using a cache here to avoid having to make multiple `findViewById()` calls within what is effectively an animation as this could cause jank if we do not render the frame within 16ms). I use a `WeakHashMap` to store these mappings which uses a key of the `View` representing the page being updated. A `WeakHashMap` is useful here because it will only hold a weak reference to the key, and therefore that key can be garbage collected. If it is GC’d then the value for that key / value pair in the `WeakHashMap` will be removed. Therefore this protects us from leaking the `Fragment` layouts by holding string references to them. The value of each entry in the `WeakHashMap` is a `SparseArray` which is a mapping between the resource IDs and the individual `Views` fro those IDs within the layout. Essentially this caches the `ImageView`s representing the background, middleground, and foreground images meaning that we don’t have to do a `findViewById()` for each one in every frame of the animation – instead we do a much cheaper `WeakHashMap`, then `SparseArray` lookup.

Once we have these mappings, we can apply a `translationX` to each to tweak it’s position. For the background image, we apply the negated `offset` value which we calculated earlier. This will essentially lock the background in to position, and prevent it from moving during the scroll. We are not actually applying the `translationX` to the middleground component, so this will move at the same speed as the `ViewPager2` scrolling – i.e. it will track the user’s finger / or the fling. For the foreground image we apply the offset mutliplied by a scaling factor. I played with various values here, but rather liked the effect with a scale factor of `0.5` so that’s what we’ve gone with. That causes the foreground elements to move out of the frame faster than the swipe of fling.

If we run this we get the following:

The background remains completely static, while the middleground precisely tracks the swipe or fling, The foreground moves slightly faster, and we get quite a subtle, but overall quite pleasant parallax effect.

This no longer appears to be two separate pages in a `ViewPager2`, and I have left the `TabLayout` in place to make it clear that it is still a `ViewPager2` underneath. In a real-world scenario it would be much nicer if we didn’t draw attention to this, and allow the illusion of a single scene.

This illusion is possible because I have designed the component images to permit this. Also, in the real world it would be preferable to have distinct scenes on each page, but that is easily achievable by using different layer images on each `Fragment`. I have just gone with a repetitive scene to keep the example code leaner.

So it is actually the `PageTransformer` instance which makes this really easy, and we’ve covered this in some depth to properly explain why things have been implemented in the way that they have, but much of the code is in the caching optimisation, and the actual code to perform the parallax scrolling is really quite simple – it’s just applying different `translationX` values to the `ImageView`s based on the current scrolling position for each of the `Fragment` layout hierarchies. We get a really nice visual effect without too much effort!