Hi Flutter lovers!

Today we’re going to see how we can hook an animation with the scroll progress of a PageView in Flutter.

Have you ever needed to animate a widget according to the scroll of a PageView ? Well I did, and came up with a way of doing that, which is what I’m going to cover with this article.

What are we talking about?

A PageView in Flutter is a widget which takes care of displaying a list of widgets like pages of a carousel. It takes care of snapping to the next page if you “scroll past the other half” and other really cool stuff.

You can learn more about these little guys in Deven Joshi’s article.

Lazy people can get the idea with this 1-minute-ish video from Flutter:

PageView provides, like most of the widgets of the scrolling family, a way to listen to its status changes. Our goal here is to listen to these changes and transform some other widget(s) accordingly.

We are going to need to know what a ValueNotifier is, too.

Basically, a ValueNotifier is a class that holds a value and notifies who’s listening to it when this value changes. Check here for further documentation.

Roadmap

We are going to:

Create a NotifyingPageView widget, which wraps a PageView and notifies the changes to the provided ValueNotifier . Create a screen with a ValueNotifier<double> to pass to the NotifyingPageView in order to get updates about the scroll status. Complete the rest of the screen with the NotifyingPageView and our widget to transform. Launch the app and see our work in action! 🚀

Creating the NotifyingPageView

Nothing fancy in the build function here.

In the initState method we instantiate the PageController , which allows us to control and get information about the PageView it’s attached to.

The interesting stuff happens in the _onScroll method, let’s focus on that.

_pageController.page is a double which goes from 0.0 to the number of the pages minus one (counting is from zero right?), covering a continuous interval according to the scroll position.

That is, if we have 3 pages, the range would be between 0.0 and 2.0 , and for example, if we are at page 0 and dragging forward to page 1 , halfway we’re going to have _pageController.page == 0.5 and so on until we hit 1 .

We are sure we’ve reached a “complete” page when the two numbers are equal, that meaning, for example, 1.0 == 1 .

We achieve that with the clause in the if statement. If the condition is satisfied, we keep track of the page we’re in.

We then update our ValueNotifier with the difference between the actual page and the previous.

Depending on the direction of the scroll, values can be either positive or negative. When scrolling to the next page we’re going to have values ranging from 0 to 1 , while the range is going to be from 0 to -1 for backward scrolling.

NOTE: Using this logic the difference will snap to 0.0 the moment we hit the new page, take that in account when you’re using this value for your animations.

Before going to the next part, some of you might point out that we could update the _previousPage using the onPageChange event offered by PageView , but here’s why I choose not to.

This is what the docs say about onPageChange is

Called whenever the page in the center of the viewport changes.

This means that we don’t get all the values between a page and the other, and we would update the _previousPage before we need (the centre changes way before the whole page comes in).

Creating the screen and the ValueNotifier

A simple StatefulWidget , we create the ValueNotifier and override the dispose method in order to dispose it when the time of our screen will inesorably come.

Let’s complete the work.

Building the page with NotifyingPageView and the transforming widget

We create a Column which two children:

The NotifyingPageView , which is supplied with the notifier. An AnimatedBuilder tuned to the _notifier, which builds its child by rotating it by 2* pi * _notifier.value .

Transform.rotate allows to easily rotate a widget around its center by the supplied angle in radians (hence the use the dear pi from dart:math ).

Each time the notifier changes, the AnimatedBuilder reacts accordingly.

The fact that the value snaps to 0.0 when the new page kicks in does not bother us, because the rotation is not affected by this kind of discontinuity.

A transition would suffer from the snap, though I suggest to use a different logic for updating the notifier.

See everything in action

Considerations

We’ve seen a simple way to update the ValueNotifier and use it with an AnimatedBuilder.

You can easily choose different mathematical functions to determine the new value (to avoid the snap to 0 , for example).

You can also swap the ValueNotifier with an AnimationController to orchestrate more complex animations driven by a Tween bound to the AnimationController .

Like ValueNotifier , you can set the value of an AnimationController by assigning it to the its value property. Just be sure to have a value between 0 and 1 in this case, since AnimationController values need to be in that range.

You could also listen directly to the ValueNotifier and do everything you like with the value that it yields, which gives you unlimited possibilities!

Conclusions

That’s all for now! We’ve seen how to easily leverage Listenable values and AnimatedBuilder to transform our widgets accordingly to the scroll of a PageView (or everything else that works in a similar manner).

I hope you’ve enjoyed the article! You can find the full code (without the sake of brevity) here on GitHub.

Until the next article, happy coding!