Synchronising widget animations with the scroll of a PageView in Flutter
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?
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.
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.
ValueNotifier is a class that holds a value and notifies who’s listening to it when this value changes. Check here for further documentation.
We are going to:
- Create a
NotifyingPageViewwidget, which wraps a PageView and notifies the changes to the provided
- Create a screen with a
ValueNotifier<double>to pass to the
NotifyingPageViewin order to get updates about the scroll status.
- Complete the rest of the screen with the
NotifyingPageViewand our widget to transform.
- Launch the app and see our work in action! 🚀
Nothing fancy in the
build function here.
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
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
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
1 , while the range is going to be from
-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
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
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:
NotifyingPageView, which is supplied with the notifier.
AnimatedBuildertuned 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
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
We’ve seen a simple way to update the
ValueNotifier and use it with an
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
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
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!
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!