Custom scroll physics in Flutter
In this article, we will write our own ScrollPhysics
to alter the behavior of the scroll in a ListView
.
KISS (Keep It Simple… pleaSe)
Recurrent scenario, we have a set of pages or slides that we want to iterate over.
The code to do this is pretty simple. We just need to use PageView
with default properties.
Awesome. But.
Sometimes we want to give the user a hint, or maybe it’s not really pages but elements in a list what we are iterating over. In those cases, it would be great if the current element would fill only a fraction of the viewport so that we can see part of the next (or previous element).
No worries, Flutter got it covered. We can use a PageController
.
Code is still pretty simple. We just need to set the viewportFraction
to the fraction of the viewport we want to be filled by the current element in the list.
Awesome. BUT.
What if this is not exactly what we want? Instead of the current element being centered I want this to look more like a list of items, but I still want to scroll one item at a time?
In order to do this, we need to dig a bit deeper and look at one property that we have not used so far, ScrollPhysics
.
Row vs PageView
PageView
is more intended for a set of pages that the user slides on, sort of like an onboarding. Our case is a bit different as we want a list of items but also want to keep scrolling one at a time. It makes sense to ditch PageView
and use ListView
instead.
Easy. But if you scroll right now you will see we lost the one-by-one scroll. We are dealing with items in a list and not with pages anymore, so we need to build this concept of pages ourselves, and we can do that using the property physics
in ListView
.
ScrollPhysics
There are already different subclasses of ScrollPhysics
that we can use to control how the scrolling takes place, but one of them sounds interesting: PageScrollPhysics
.
PageScrollPhysics
is the one used by PageView
internally, but sadly, if we use it with ListView
it does not work. What we can do instead is build our own version. Let’s have a look at PageScrollPhysics
first.
ScrollPhysics for PageView
The method createBallisticSimulation
is the entry to the class, and has the position in the scroll and the velocity as input parameters. Basically what this is doing is check if the user is scrolling to the right or to the left and, in that case, calculates the new position in the scroll, which will be basically the current one plus or minus the extent of the viewport, as the scroll in PageView
is one by one.
We want to do something very similar, but in our case, we are not using the viewport but some custom unit as we have more than one item per viewport.
This custom unit we can calculate ourselves, it will be the total extension of the scroll divided into the number of items in the list, minus one. Why minus one? 1 item in the list would mean no scroll, 2 items would mean scroll 1 item,… so N items mean scrolling N-1.
CustomScrollPhysics
We are overwriting getPixels()
, which returns the position based on the page number, and getPage()
, which returns the page based on the position. Everything else works fine as it was, but we still need to pass itemDimension
in the constructor.
Using CustomScrollPhysics
Luckily for us, ScrollController
can give us the maximum extent of the scroll, but that won’t be available until the widget has been built. We need to transform our page into a StatefulWidget
and listen to our controller until we know that the dimensions are available, and then initialize our CustomScrollPhysics
.
And that’s all. With this, we have our list of items with a one by one scroll.
Recap
This is a basic example that will help us customize our scroll by creating our own ScrollPhysics
class. In this case, we are using a one by one scroll with our ListView
. Check the whole code.
That’s all. Feel free to comment with your questions. Thanks for reading!