BMI Calculator in Flutter – Part 2 – Weight

BMI Calculator in Flutter – Part 2 – Weight

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 🙂

The design
Starting point

Title widget

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 RichText widget:

When we got the Title widget, we should create WeightCard widget and include the title in it:

After we put the WeightCard inside InputPage from the previous post, we get this view:

Card title

Background widget

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 ExpandedCenter and Padding to achieve this look:

Slider background


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 ListView:

So, let’s break it down:

  • The WeightSlider accepts min and max value. Based on those, we can calculate how many items there are to display.
  • We add 2 extra items to the ListView at 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 BouncingScrollPhysics which is the default iOS one (we don’t want Android’s way there).
  • We also pass width of 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 ScrollController with initial scroll offset which will be needed before the first build.

We’ll use this widget like this:

And this is what we get:

Slider listview

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 onChanged method.

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 _getTextStyle method:

Highlighting slider

Now we can see what we got:


Not everything works like it should at this moment, let’s go through bugs and fix them!

Number wrapping

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 Text inside FittedBox:

Making the text scale down results in such difference:


Centering position

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 ScrollController:

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 ScrollController and 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!

You can find full code for this stage in here and other posts related to this project in here.

Cheers 🙂

13 thoughts on “BMI Calculator in Flutter – Part 2 – Weight

  1. Hello, thank you very much for your tutorial! I tried to do it and found that on the left or right side, there was an anomaly when I clicked quickly. In rolling, click on the left or right side will also have exceptions.

    1. Yeah, sometimes there are some weird behaviors when clicked quickly, that’s why I tried to make the duration of the animation really small.
      I also saw the second thing, however, I assumed that it will not be the frequent usecase because usually when a user is scrolling, he won’t be tapping to adjust.
      I you have any idea how to fix any of those issues I would gladly hear about it!

      1. I added a timer at the right time to detect and process such an exception.

        It may not be a good solution, but it can solve the problem.

        By the way, my English is poor. I may not be very right.

        bool isStop = true;

        timerEvent() {
        if (isStop) {
        if (_useTimerEvent()) {
        _animateTo(value, durationMillis: 80);
        print(“timer animate.”);
        isStop = false;

        bool _useTimerEvent() {
        double targetExtent = (value – minValue) * itemExtent;
        return (scrollController.positions.isNotEmpty && abs(scrollController.position.pixels – targetExtent) > 0.0001);

        bool _onNotification(Notification ntf) {
        if (ntf is ScrollNotification) {
        int middleValue = _offsetToMiddleValue(ntf.metrics.pixels);

        if(_userStoppedScrolling(ntf)) {
        isStop = true;
        } else if (ntf is ScrollEndNotification) {
        if (!isStop) isStop = true;
        if (isStop && _useTimerEvent())
        Timer(Duration(milliseconds: 300), timerEvent);
        } else if (ntf is ScrollStartNotification) {
        isStop = false;

        if (middleValue != value) {
        if (onChanged != null)
        onChanged(middleValue); // update selection
        return true;

    2. To fix the issue with clicking during scroll there is a better solution. We need to check end scroll and add Future.delayed to animateTo method:

      bool _userClickedDuringScroll(Notification notification) {
      return notification is ScrollEndNotification &&
      notification.dragDetails != null &&
      notification.dragDetails.velocity ==;

      _animateTo(int valueToSelect, {int durationMillis = 200}) {
      double targetExtent = (valueToSelect – minValue) * itemExtent;

      Future.delayed(, ()
      duration: Duration(milliseconds: durationMillis),
      curve: Curves.decelerate);

      bool _onNotification(Notification notification) {
      if (notification is ScrollNotification) {

      int middleValue = _offsetToMiddleValue(notification.metrics.pixels);

      if (_userStoppedScrolling(notification) ||
      _userClickedDuringScroll(notification)) {
      if (middleValue != value) {

      return true;

  2. Hello, love to read your article and get so much information through your blog and learn new things. You write very well, am amazed by your blogging, you will definitely achieve success.

Leave a Reply

Your email address will not be published.