Custom scroll physics in Flutter

David Anaya
Koa Health
Published in
4 min readJul 22, 2019

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.

Default PageView

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.

PageView with viewportFraction

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?

Best of both worlds

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.

List items and viewports on each scroll

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!

--

--