For this example, I am using Cupertino icons, so if you want to use these, make sure the dependency is added in your projec’s pubspec.yaml file.

cupertino_icons: ^0.1.2

Create a widget for the step indicator view in a new file and declare the required variables.

class StepProgressView extends StatelessWidget {

  final double _width;
  final List<IconData> _icons;
  final List<String> _titles;
  final int _curStep;
  final Color _activeColor;
  final Color _inactiveColor = Colors.grey[100];
  final double lineWidth = 4.0;

}

Inside the class declare a constructor with the relevant parameters.

StepProgressView({Key key,
  @required List<IconData> icons,
  @required int curStep,
  List<String> titles,
  @required double width,
  @required Color color}) :
      _icons = icons,
      _titles = titles,
      _curStep = curStep,
      _width = width,
      _activeColor = color,
      assert(curStep > 0 == true && curStep <= icons.length),
      assert(width > 0),
      super(key: key);

Let us build the base layout of the widget now. Here we are creating a row for the icons, and a row for the titles are created only when the titles data are passed to the widget; otherwise, only icons will be shown.

Widget build (BuildContext context) {
  return Container(
      padding: EdgeInsets.only(top: 32.0, left: 24.0, right: 24.0,),
      width: this._width,
      child: Column(
        children: <Widget>[

          Row(
            children: _iconViews(),
          ),

          SizedBox(height: 10,),

          if (_titles != null)
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: _titleViews(),
            ),

        ],
      ));
}

Now for the main view, we create a widget list for the icons row. We will loop through the icons list and create circular containers with icons and then a line.

List<Widget> _iconViews() {
  var list = <Widget>[];
  _icons.asMap().forEach((i, icon) {

    //colors according to state
    var circleColor = (i == 0 || _curStep > i + 1)
        ? _activeColor
        : _inactiveColor;

    var lineColor = _curStep > i + 1
        ? _activeColor
        : _inactiveColor;

    var iconColor = (i == 0 || _curStep > i + 1)
        ? _inactiveColor
        : _activeColor;

    list.add(

      //dot with icon view
      Container(
        width: 30.0,
        height: 30.0,
        padding: EdgeInsets.all(0),
        child: Icon(icon, color: iconColor,size: 15.0,),
        decoration: new BoxDecoration(
          color: circleColor,
          borderRadius: new BorderRadius.all(new Radius.circular(25.0)),
          border: new Border.all(
            color: _activeColor,
            width: 2.0,
          ),
        ),
      ),
    );

    //line between icons
    if (i != _icons.length - 1) {
      list.add(
          Expanded(
              child: Container(height: lineWidth, color: lineColor,)
          )
      );
    }
  });

  return list;
}

Create the optional titles view.

List<Widget> _titleViews() {
  var list = <Widget>[];
  _titles.asMap().forEach((i, text) {
    list.add(Text(text, style: TextStyle(color: _activeColor)));
  });
  return list;
}

That’s it. Let us create some icons for testing

const stepIcons = [Ionicons. ios_person, Ionicons.ios_home, Ionicons.ios_car, Ionicons.ios_card,Ionicons.ios_airplane];
final List<String> titles = ["step1", "step2", "step3","step4","step5"];
int _curStep = 2;

Finally, in your scaffold/column, add this:

StepProgressView(icons: stepIcons,width: MediaQuery.of(context).size.width,curStep: _curStep,color: Color(0xffe5649e),),

For updating the view, change _curStep and cal setState().