Hi again! I’m coming back with another part of the implementation of Jhony Vino‘s awesome BMI Calculator design. If you didn’t see the previous part, where I implemented gender picking, you can find it here. In this post, I will focus on creating a widget for weight input. Let’s see how it goes 🙂
First, we need to update Title widget we created for the previous part, both gender and height are having a subtitle with unit displayed. In order to have two texts side by side but with different fonts, we will use
When we got the Title widget, we should create
WeightCard widget and include the title in it:
After we put the
InputPage from the previous post, we get this view:
Now let’s add a background for the picker. To draw a pointer arrow we will use provided svg asset. Like in previous part, Dan Field‘s flutter_svg package will help us with that. The background itself will be a simple container with proper decoration. I designed it to accept
child parameter which will be the actual slider.
To show that widget, we put it below the title. We simply wrapped it inside
Padding to achieve this look:
Now it’s time to get to the core of the widget, which is a slider which allows the user to pick the weight. In the design, it is shown that the user clicks on numbers to change the value but it seems reasonable to assume that user can also swipe horizontally. I think the easiest approach would be to use horizontal
ListView. The feel is similar to the Numberpicker package I created so I will base the implementation on that. For now, let’s focus on creating a simple
So, let’s break it down:
maxvalue. Based on those, we can calculate how many items there are to display.
- We add 2 extra items to the
ListViewat the start and at the end so that we can have the boundary values displayed in the middle of the widget.
- We specify scroll physics to
BouncingScrollPhysicswhich is the default iOS one (we don’t want Android’s way there).
- We also pass
widthof the widget. We always want to display only 3 items, so we need to know how much space one item needs to place (and set it in
itemExtent). Theoretically we could obtain this metric inside the widget, however, later on, we will use
ScrollControllerwith initial scroll offset which will be needed before the first build.
We’ll use this widget like this:
And this is what we get:
Highlighting selected value
Well, the ListView itself is not very impressive. I think it’s time to make it actual picker. But how are going to do that? I will use the same approach as in the
Numberpicker – let the
WeightSlider remain Stateless and just pass a value to it and also get the callback when the value in the middle changes.
Basically what we do is adding a
NotificationListener to a
ListView. It will invoke
_onNotification on every scroll event. Then, we calculate the value of the middle element based on notification’s
Offset. If the calculated value is different than the passed
value, we call
Alright, but what happens then? We will store the selected weight in
WeightCard and simply update state:
But wait, we still don’t highlight the middle element. Even though we change the value, the user will not know that he actually selected his weight. We can fix it with changing current
Now we can see what we got:
Not everything works like it should at this moment, let’s go through bugs and fix them!
In the previous gif, I intentionally didn’t show you what happens when you go over 100. Unfortunately, in those cases, numbers are being wrapped. The first idea would be to just lower the font size for the highlighted item but it will never assure us that on some specific resolution the number will look the same. To handle this problem, we need to wrap the
Making the text scale down results in such difference:
What we can also notice is a situation when after user selected value, it is not being centered. I think it should be. To achieve that we will use
ScrollController. We can detect if the user stopped scrolling and if so, we can animate to the selected value. Let’s see that in code:
And that is the difference:
The initial position and overscroll
Last two issues we need to address are initial position and overscroll. So far our listview always started at the beginning even though I set initial weight as 70. We can easily fix it by adding an initial offset to the
I’ve also noticed that when I run app in release mode LayoutBuilder is returning (0;0) constraints on the first build. I am not sure why it only happens in release mode but we can assure that only non-zero constraints are passed with this code:
Also, when the user scrolled to the edge the calculated value was higher than
maxValue. It could be easily fixed with this line:
Tap to select
The last feature we need to add for this component is handling tapping on the numbers. Since we already have
animate method, all we need to do is wrap list items with
GestureDetectors and handle taps.
What is worth noticing is that we are using
translucent hit test behavior so that both taps on texts as well as outside of them are being captured.
The final result for this part looks like this:
EDIT: You cannot see on tap animations because gif frame-rate is too low but believe me, they’re there 🙂
And that’s it!
If there is anything unclear or there is something I did wrong, please leave a comment, let’s learn together!