Custom scroll dots indicator

Andi
3 min readMay 2, 2024

--

I recently published a dots indicator package and it made for an interesting point one may need to know if they want to make a scroll reading widget of their own.

You can find the published Pub package here and the GitHub repository here.

The Why:

I was looking at some of the other published packages on pub.dev and I found that almost all of them were using these 2 parameters for their calculation:

  1. current index
  2. total count

A very easy and straightforward way of handling the UI. It’s simple enough.

But I recently came across a UI Design which was using only a small number of dots for a list that would be seemingly infinite. Of course, the list will be handled well with Pagination and we can just add more dots to our UI with the number of pages.

But what if we don’t?

We know that we can’t really keep adding infinite dots to the widget without making the indicator itself a scrollable. What if we need to use a fixed number of dots to display the indices…

The Solution:

When it came to implementing the paginated list with a static number of dots, the first idea that came to my mind was to somehow use the scroll of the list itself.

We can get a handle on the scroll by providing a ScrollController. The same instance that is attached to the scrollable widget can be used in another widget to get the scroll information. The ScrollController itself can only provide us the information about the Scrollable widget’s scroll, so we do need a parameter to tell our widget how many items the Scrollable widget has.

We can get the current scroll position with this: scrollController.offset

We can get the maximum scroll extent with this: scrollController.position.maxScrollExtent

We can derive the item extent by: maxScrollExtent/itemCount

Now, we can derive the current visible item’s index by comparing the scroll offset with item extent as: currentItemIndex = currentScrollOffset ~/ itemExtent ( ~/ operator gives us the closest int value when performing operations involving double values.)

Since the count of our dots is limited, we have to give our dots a range for which they switch between the states of active and inactive. We can run a loop over the dot count as this to calculate range:


for (int dotIndex = 1; dotIndex <= dotsCount; dotIndex++) {
int rangeStart = 1 + range * (dotIndex — 1);
int rangeEnd = (dotIndex == dotsCount)
? listLength
: dotIndex * range;
...
}

Note that, in the example, our range starts with 1.

In the loop, we have:

  1. index of each dot
  2. the range of each dot
  3. the current item selected

We can use this data to determine the kind of Dot we can put on each dot index.

Should be finished now. Right?

Rein in your hubris…

We need the ScrollController to be attached to the scrollable widget to be able to read values from it. When we boot up our app, it will render with an error.

We need a dots indicator to be rendered while the ScrollController is still attaching itself to the view. The constructor offers a onAttach callback that we can use to refresh our view and display our non-default indicator widget. For the purposes of building a package and separating it’s function and code, we need to attach a listener and refresh the indicator widget when attached.

Still not done…

After adding this part of code, our dots indicator widget will start working and, probably, you’ll not come across this issue. But… what happens when the maxScrollExtent is 0 ?

maxScrollExtent, Inifnity, NaN

I hope this diagram helps understand why this issue can come up. If the scrollable widget doesn’t have enough items to go beyond the assigned viewport, there will not be any scroll. And thus, the maxScrollExtent will come up as 0 .

So, if we want to avoid the case of Infinites and NaNs, we need to add a check for the 0.

Remember to also assert that the number of dots in our indicator widget should not be greater than the list length.

Now we’re good to go!

--

--

Andi
Andi

Written by Andi

Tell me what I should do next…

No responses yet