Spring Back Recyclerview — The Basics

Kaustubh Patange
The Startup
Published in
5 min readFeb 3, 2021

--

So this is going to be a very easy workflow to achieve a Spring back sort of animation. I said sort-of because a lot of (maybe?) the libraries do it but what we are going to do is build it from scratch so that you understand what actually goes into it & also it’s very fun :).

But let’s first see what we are trying to achieve (below gif).

Gif is playing at 0.5x

So we have a very basic screen with a RecylerView & an effect is going when we either reach at the bottom or to the top of the list on scrolling. On flinging the RecyclerView, we start scrolling with some velocity & after reaching on either end sides we just don’t abruptly stop the motion. Instead, we disperse the momentum to scale it a little bit beyond & snap it back to its original size just like a spring.

This is not very hard to achieve & if you have read the above paragraph I just gave away a key point to create this animation. Anyone guessing… Yup, it’s “Spring”. Thanks to physics-based spring animation introduced way back in support library now available as unbundled API for androidx.

Before getting started, if you want to get all the code from the article (Kotlin) into a written sample, check out this Github repository.

Understanding Concepts

Whenever I write an article the first thing I do is to make you understand or give you a summary of how we are going to achieve it.

Below is a diagram with respect to RecyclerView which gives you a rough idea of what we will be doing (with the view).

If you know some of the internals of how RecyclerView works then you will definitely know that RecyclerView is a ViewGroup , the only difference is, it uses different approaches to properly add items as its child depending on your data set. So anything that we will be following can be applied to any other view components as well (eg: Layouts).

Deriving the solution

If you look at the chart it basically tells you all the dependency we will be needing. Like on following the bottom-to-top approach we know that in order to run a Spring animation on RecyclerView we need a velocity which we need to calculate in onScrollStateChange (one of the callbacks fromRecyclerView.OnScrollListener ) & to compute this velocity we also need initial fling velocities (can be get by setting a fling listener to RecyclerView).

So the first thing we need is the initial fling velocities. Fortunately, there is a listener we can set on RecyclerView to listen for the changes.

You must return false from the callback so that RecyclerView will handle all of the scrolling & properly deals with it.

Now we need to do the actual computation part technically in the Scroll listener, let’s set it.

If you looked at the animation carefully we are only running it whenever we reach the end or at the top & reaching the end implies the scrolling is stopped. The latter can be derived from the newState parameter of onScrollStateChange, like newState == RecyclerView.SCROLL_STATE_IDLE .

But what about the end of scrolling thing, how do we detect this. Again RecyclerView has some methods to do it.

recyclerView.computeVerticalScrollOffset() // currentY
recyclerView.computeVerticalScrollRange() // totalY
recyclerView.computeHorizontalScrollOffset() // currentX
recyclerView.computeHorizontalScrollRange() // totalX

The first two will be used if the scroll direction is Vertical & the next two when it is Horizontal & to know if the direction is Vertical or Horizontal we can check the stored fling velocities. If flingVy is non zero it means the scrolling is vertical (similar for flingVx ), the most important thing is only either one of them will be non-zero because guess what RecyclerView can either scroll vertically or horizontally.

scrollOffset() — It will give you the scroll distance from the respective X or Y axes.

scrollRange() — This will give you the total distance for scrolling to reach the end of the list. This is irrespective of the view’s dimension but solely based on internal calculation. Also, to get a hypothetical height/width of RecyclerView if it is not recycling then you need to subtract measured height/width from this range. Why? Because it is already displaying one part of the range on the screen.

POV: The velocity we get from onFlingListener is the scrolling velocity (pixel/sec) not the scaling velocity so we need to divide it by the height/width respectively.

Putting all these pieces together we will get something like below.

In the above code, we are also setting the pivot axis of the RecyclerView this will be the hanging point (like pinning an item around its edge) so that it should not scale from the other side. For eg: if flingVy is greater than 0 it means scroll direction is vertical & we need to set the pivot as the total height of the RecyclerView to avoid it scaling beyond the measured height.

Now comes the part of writing the Spring animation, you can be as creative as you want by understanding the docs, or can use the basic one that I do.

implementation "androidx.dynamicanimation:dynamicanimation-ktx:1.0.0" // dependency needed for SpringAnimationval anim = SpringAnimation(recyclerView, property, 1f)
anim.setStartVelocity(velocity.coerceAtMost(2f)).start()

After putting all this together you have successfully created a Spring-back animation. You can find this whole source code baked into a helper class SpringScrollHelper to be ready for use as follows,

SpringScrollHelper().attachToRecyclerView(recyclerView)

Conclusion

If you are following through you will see how easy was it to create & implement spring back animation. You can modify the code however you want & can also improve the animation by tweaking the Spring force (reference here).

Again, you can find all this code on my Github repository.

--

--