Flutter Design Challenge : Spotify Album Scroll Interaction

Rutvik Tak
9 min readNov 13, 2021

A sweet usage of Slivers!

Yet another challenge awaits the Flutter Devs! This time we’ll look into building some custom scroll interaction inspired by the Spotify mobile app with Flutter.

We’ll look into how we are going to implement it and also answer the why? part of it. Goal is to keep our logic as clear as possible while adding the details and making it easier for ourself to handle the interactions.

Lets’ Go!!!🚀

Table Of Content :

  • Design Challenge : Spotify Album Scroll Interaction
  • Analysing Static Design
  • Analysing Motion Design
  • Implementation
  • Summary

Design Challenge : Spotify Scroll Interaction

We’ll be building this scroll interaction.

Analysing Static Design :

  1. SliverCustomAppBar :
  • FixedAppBar(3) : This is the part of SliverCustomAppBar ,it’ll hold basic app bar parts like leading, title, etc.
  • AlbumImage(*CustomFlexibleSpace)(2): This is flexible space for SliverCustomAppBar .
  • SliverCustomAppBar(1): It will include the FixedAppBar and AlbumImage , if we examine the design carefully then we can say that FixedAppBar is stacked over AlbumImage as it’s always on the top in the whole interaction.

2. AlbumInfo + AlbumSongsList :

  • AlbumInfo(4) + AlbumSongsList(5): Pretty much self-explanatory what they are.

3. PlayPauseButton :

  • PlayPauseButton(6): This is the button i.e stacked over our scrollable content.

Analysing Motion Design :

  1. AlbumImage : It has basically three states, an InitialState and AnimatingState and NotVisibleState.
  • InitialState : InitialState can be the one in which it has max size or when its size shrink/expand as we scroll. In this state the only thing that’s changing for it, is its size.
  • AnimatingState : In this state it starts offsetting(moving up/down) from its current position and also starts fading as we scroll. Both the offset and its fading is controlled by the scrolling.
  • NotVisibleState : In this state, it’s not visible.

It transitions from InitialState → AnimatingState after we scroll to some fixed extent in upward/downward direction. From there to
AnimatingState → NotVisibleState after we reach a particular extent.

2. FixedAppBar : It also has two states, an InitialState and VisibleState.

  • InitialState : This is when only the leading icon is visible and background is transparent for it.
  • VisibleState : This is when the appbar titleis visible and background gradient is visible.

It’ll transition from InitialState → VisibleState after particular extent from the point when the AlbumImage goes into NotVisibleState.

Transitions to and from both states is controlled by the scrolling.

3. PlayPauseButton : Only thing that updates for this is it’s position from top. And it adjusts its position from top depending on the scrolling and where we are in our interaction.

Implementation :

We’ll be making use of Slivers ,Stack and Implicit Animation widgets to implement this scroll interaction. If you haven’t learnt about them yet then I would suggest you to take a moment and understand them before moving on.
You can learn about slivers through this Flutter Slivers section. It has great resources on everything slivers and for Implicit Animations you can also check out my this Animations In Flutter article on it.

That said! If you’re ready then let’s move on 🚀

  1. Base Code : We’ll start with this base code.
  • SpotifyAlbumView :
Source Code

We start off by using CustomScrollView which will help us to create custom scrolling effects using slivers . Also passing the _scrollController
that we created to it, so we can listen to scrolling events.

We also use LinearGradient which helps to add gradient background and we specify the colors, stops and direction in which it should spread.

2. SliverCustomAppBar :

This is the most interesting part 😁. This is where the sliver magic happens ✨.

Source Code

SliverAppBar is build up of two important things that decides its behaviour when scrolling.

  • SliverPersistentHeader : This helps in creating a sliver that adjusts its height depending on the scroll. This helps in shrinking/growing effect.
    Learn more about it here.
  • SliverPersistentHeaderDelegate : This is the delegate that SliverPersistentHeader uses to build your widgets. It provides us with important information such as minExtent, maxExtent and shrinkOffset which we can further use to create custom interaction events.

The `shrinkOffset` is a distance from [maxExtent] towards [minExtent] representing the current amount by which the sliver has been shrunk.
It goes from 0 → [maxExtent] as user scrolls.

We’ll pass on the max and min height that we want for the AppBar and the builder function to the SliverPersistentHeaderDelegate and then pass our custom delegate to the delegate parameter of the SliverPersistentHeader .

With this, we are done with implementing our custom version of SliverAppBar and now it’s time for us to handle the interactions within it while scrolling.
Isn’t this the part you all are waiting for!😁
Not so early!😅 There’s still one small but very helpful thing that we need to cover. It’ll help us implement the logic for interactions very easily.

  • shrinkToMaxAppBarHeightRatio:

To visualize the shrinkToMaxAppBarHeightRatio you can refer to the above diagram. The ratio goes from 0 → 1 .

shrinkToMaxAppBarHeightRatio

With this ratio, it gets much easier for us to know how much user has scrolled in terms of values from 0 → 1 and where he is from the top and also we only have to deal with values from 0 → 1 which are easier to handle.

2. AlbumImage :

To implement the interaction for AlbumImage, we’ll need to know when to change between the three different states it has, as mentioned above.

Let’s first set the dimensions for our AlbumImage .

Our Image needs to shrink when user scrolls, so we have one fixed height and we are just subracting the shrinkOffset from it as user scrolls.

Source Code

We also wrapped our Image with AnimatedOpacity as we’ll require it later anyway.

Result :

That’s good. But now we need to adjust its padding from the top and offset it upward/downward as user scrolls to get the behaviour in our interaction.

  • adding padding :

...padding.top gives us the height of portion of app ui that’s obscured by the status bar and then we add a bit of extraTopPadding to it from our side.

  • albumPositionFromTop : To animate this property, we need to know when the AlbumImage goes into the AnimatingState , let’s see how we can do this.
Source Code

First, let’s wrap our AlbumImage in a Stack to position it from top. We’ll be using shrinkToMaxAppBarHeightRatio to determine albumPositionFromTop. We are setting up a point after which we want to animate the AlbumImage , then once the user scrolls beyond that point, we’ll start updating the position.

Result :

That’s super sweet! Only one last thing remains now for AlbumImage, it’s to change its opacity after it goes in AnimatingState and after some point, make it go into the NotVisibleState i.e update its opacity to 0.

First , let’s decide when we want to make it go into the NotVisibleState

final animateOpacityToZero = shrinkToMaxAppBarHeightRatio > 0.6;

0.6 , is just some point I pick which feels perfect to transition, you can adjust it as you like.

Source Code

Logic to update opacity is quite simple, we first check if we are in NotVisibleState or not, if yes, the change opacity to 0 ,if not then check if we want to go in AnimatingState and change opacity accordingly by subtracting the shrinkToMaxAppBarHeightRatio.

Result :

WOhhh!😮‍💨
That was a lot! Finally done with the major part of the interaction.🙌

3. FixedAppBar :

This will be pretty easy to implement as we have all things sort out now about controlling and how we are managing between changing different states for interactions using shrinkToMaxAppBarHeightRatio .

Our FixedAppBar , gets visible after some time when AlbumImage goes into NotVisibleState .
There’s also fading animation to the title, let’s see how we can calculate it with help of this parameter.

If FixedAppBar is in VisibleState, we start calculating the opacity else it’s 0 .

For animating the both the FixedAppBar and title opacity, we’ll make use of AnimatedContainer and AnimatedOpacity respectively.

Source Code

Result :

Looks nice!
Amazing!!! Now we are onto the last part of the interaction. Let’s do this!

4. PlayPauseButton :

For this interaction, as we discussed in the Analysing section above that it’s stacked over our scrollableContent, so we’ll wrap our scrollableContent in the stack with this button on top.

In above code, along with PlayPauseButton , I also added the AlbumInfo and updated AlbumSongsList to show some content, both are static parts of the ui.

For the PlayPauseButton , we need to calculate its position from top at any given point of time.

Source Code

This algorithm calculates the position from top for PlayPauseButton and checks for isFinalPosition which is when we want the button to be seem like it’s stacked on top of our FixedAppBar in interaction. It takes into account the different heights we have available for different elements and using those values adjust its position. We check if we reached the FinalPosition for PlayPauseButton and set position accordingly.

Let’s set it up!

Source Code

And we are done! Excited to see how it looks!✨

Final Result :

Summary :

  • We learned how to create CustomSliverAppBar and about the mechanism behind how to handle interactions with Slivers.
  • Used combination of multiple widgets to achieve our goal, like using stack with slivers.
  • Build up on the logic of interactions with simple flow.

🙏 Thanx for making it this far and I really appreciate you reading it. This was quite a challenge to write down with lots of components and interactions taking place, but I hope you learned something today from this.
I wish on publishing more such challenges in future so stay tuned!🙌💙

As always you can find the source code here.

👏 Enjoyed the article! Would appreciate the clap :)
🚀 More such articles coming where we explore flutter and other interesting stuff in app development.
👋 Follow up for upcoming articles if you enjoyed reading and learning from what I write.
💬 Let me know what you liked and what you would like to read in upcoming articles.

--

--