Creating draggable scrollbar in Flutter

implementing a new UI component

A few month ago I discovered Flutter and start to write app. The idea is to show images, so it’s kind of gallery. Have you seen Google Photo app or Samsung Gallery? You can drag scroll thumb along the right side of the screen and also it shows creation date.

my inspiration — Samsung Gallery and Google Photos

I took a look at default Scrollbar widget — it’s even not possible to increase the thickness! But in Flutter everything is Widget. Let’s build new scrollbar!

Package with Draggable Scrollbar is published here. And if you want to follow its implementation continue reading.

Scrollbar is just some shape on right edge of a scrollable list, right? This means we need to build a stack with 2 children — list and container.

Screenshot of code execution

Scrollbar should be draggable. So we need to wrap ScrollThumb to GestureDetector. To simplifying solution let’s support only vertical scrollable list. So we need to implement only onVerticalDragUpdate. In parameter of this callback function we can get delta and update offset. It’s time to make Scrollbar a stateful widget. And as we are making a new UI element, it needs to have a scrollable as child. Put our grid to constructor. Run the code and drag scroll thumb up and down you notice a bug — it goes off the screen.

Screen capture of code execution

So let’s constraint it — minimum offset is 0.0 and maximum — is list height minus scrollthumb height. List height is a height of context. And scrollthumb height depends on what developer will build for scrollthumb. So let’s put this value to constructor also. Make changes in the code and see — now it’s dragged along the screen without overscrolling.

When scrollthumb is dragged, list offset should be updated. Let’s do it. We need ScrollController as well, put in in constructor. In _onVerticalDragUpdate we know delta for thumb. What is delta for list? It is a proportion — ratio of scrollbar delta to scrollbar length is the same as ratio scroll list delta to list length.

proportion of ScrollBar to scrollable List

Screen capture of code execution

As we see on screen capture when list is scrolled scrollthumb is not moving. Let’s fix it. We wrap stack to NotificationListener and calculate scrollbar delta knowing scroll list delta. Run code and see that it’s not working right. You drag scrollthumb, offset of scroll list is changed. It fires notification, function changes offset for scrollthumb. But we don’t need it. How to resolve this? Let’s add variable in state to check is dragging in process. And set this variable to true when drag is started and to false when drag is finished.

Yeeeaho! It works as it should. Now let’s make it more fancy. Let’s allow package user (=developer) to configure shape of scrollthumb. I think that even image possible to be as scrollthumb. And a few predefined scrollthumbs would be good, isn’t it?

I’ve made scrollthumb with rectangle shape with rounded corners. Then enhance it with cutting arrows. It was fun to write ArrowClipper. Take a look at implementation ;D Now it looks as in Samsung Gallery app. Right screenshot at bottom image.

Third implementation is semicircle with arrows. (Working name was “asGooglePhotos”). In this case arrows are not cut, but drawn on foreground of round shape. So CustomPainter helped to achieve this.

And one more feature that I need from draggable scrollbar is to show label with additional information depending on current position of scrollthumb. We need to provide a callback function with offset in parameter. Developer can calculate which row is shown on given offset and return Text widget. If developer does not know exact height of the row, he can’t provide additional info.

labelTextBuilder: (double offset) => Text("${offset ~/ _itemExtent}",

The offset for this label is sum of listview offset, scrollbar offset and half of thumb height.

Full demo of draggable scrollbar

I am sure that with this Draggable Scrollbar it’s possible to implement contacts app with showing first letter in a box.

As addition I’ve added fading animation, the same as built-in Flutter scrollbar has. And thanks to Brian Egan who made code more beautiful and added slide animation as well.