How to Create Different Types of Progress Indicators in Flutter

Flutter provides several built-in widgets for displaying progress indicators that can be used to show the status of an ongoing operation to the user. In this tutorial, we will learn how to create different types of progress indicators in Flutter, including:

  • LinearProgressIndicator,
  • CircularProgressIndicator,
  • Dot Grow,
  • Dot Bounce,
  • Dot Fade.

  • A determinate progress bar or indicator shows progress that can be measured and calculated, such as the percentage of a task that has been completed. 

For example, a file download progress bar can be determinate because the size of the file being downloaded is known, and the progress can be calculated based on the amount of data that has been downloaded so far.


  • An indeterminate progress bar or indicator shows that progress is being made, but the exact amount of progress cannot be measured or calculated. 

For example, an indeterminate progress bar may be used when waiting for a response from a server, where the amount of time needed to complete the task is unknown. In this case, the progress bar may spin continuously or display a repeating animation to indicate that something is happening, but the progress cannot be measured in terms of a percentage or other quantifiable metric.


Linear Progress Indicator

A LinearProgressIndicator is a horizontal progress bar that displays progress in a linear manner, indicating how much of a task has been completed. 

It can be used to indicate the progress of file uploads, downloads, or any other tasks that take a specific amount of time. Here's how to create one:

 const Text("Determine "),
              SizedBox(
                height: 5,
                child: LinearProgressIndicator(
                  value: progressValue,
                  valueColor:  AlwaysStoppedAnimation<Color>(Theme.of(context).colorScheme.primary),
                  backgroundColor: Colors.grey[300],
                ),
              ),
              Container(height: 20),
              const Text("Indeterminate "),
              Container(height: 5),
              SizedBox(
                height: 5,
                child: LinearProgressIndicator(
                  valueColor:  AlwaysStoppedAnimation<Color>(Theme.of(context).colorScheme.primary),
                  backgroundColor: Colors.grey[300],
                ),
              ),

..

Circular Progress Indicator

A CircularProgressIndicator is a circular progress bar that indicates how much of a task has been completed. 

It is typically used for tasks where the amount of time needed to complete the task is unknown. 

Here's how to create one:

 Container(height: 20),
              Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  Column(
                    children: <Widget>[
                      const Text("Determine"),
                      Container(height: 20),
                      CircularProgressIndicator(
                        valueColor: AlwaysStoppedAnimation<Color>(Theme.of(context).colorScheme.primary),
                        backgroundColor: Colors.grey[300],
                        value: progressValue2,
                      ),
                    ],
                  ),
                  Container(width: 20),
                  Column(
                    children: <Widget>[
                      const Text("Indeterminate"),
                      Container(height: 20),
                       CircularProgressIndicator(
                        valueColor: AlwaysStoppedAnimation<Color>(Theme.of(context).colorScheme.primary),
                      ),
                    ],
                  ),
                ],
              ),

..

Dot Grow Animation

Dot Grow animation is a simple animation that makes dots grow in size, one after the other. 

This animation is often used to indicate loading or progress in a process. 

 const Text("Dot Grow"),
              Align(
                alignment: Alignment.center,
                child: Container(
                  width: 105,
                  height: 100,
                  alignment: Alignment.center,
                  child: WidgetDotGrow(color: Colors.pink[300], size: 20.0),
                ),
              ),

..

Dot Bounce Animation

Dot Bounce animation is a simple animation that makes dots bounce up and down, one after the other. 

This animation is often used to indicate loading or progress in a process.

const Text("Dot Bounce"),
              Align(
                alignment: Alignment.center,
                child: Container(
                  width: 105,
                  height: 100,
                  alignment: Alignment.center,
                  child: WidgetDotBounce(color: Colors.pink[300], size: 20.0),
                ),
              ),

..

Dot Fade Animation

Dot Fade animation is a simple animation that makes dots fade in and out, one after the other. 

This animation is often used to indicate loading or progress in a process.

const Text("Dot Fade"),
              Align(
                alignment: Alignment.center,
                child: Container(
                  width: 105,
                  height: 100,
                  alignment: Alignment.center,
                  child: WidgetDotFade(color: Colors.pink[300], size: 20.0),
                ),
              )

..

Full source code:

progress_basic.dart

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_uix/layouts/progress/progress_dot.dart';

class ProgressBasicRoute extends StatefulWidget {

  const ProgressBasicRoute({super.key});

  @override
  ProgressBasicRouteState createState() => ProgressBasicRouteState();
}


class ProgressBasicRouteState extends State<ProgressBasicRoute> {

  double progressValue = 0.0;
  double progressValue2 = 0.0;

  @override
  void initState() {
    super.initState();
    updateProgress();
  }

  @override
  Widget build(BuildContext context) {

    return Scaffold(
      body:

      SingleChildScrollView(
        padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 20),
        child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: <Widget>[

              const Text("Determine "),
              SizedBox(
                height: 5,
                child: LinearProgressIndicator(
                  value: progressValue,
                  valueColor:  AlwaysStoppedAnimation<Color>(Theme.of(context).colorScheme.primary),
                  backgroundColor: Colors.grey[300],
                ),
              ),
              Container(height: 20),
              const Text("Indeterminate "),
              Container(height: 5),
              SizedBox(
                height: 5,
                child: LinearProgressIndicator(
                  valueColor:  AlwaysStoppedAnimation<Color>(Theme.of(context).colorScheme.primary),
                  backgroundColor: Colors.grey[300],
                ),
              ),

              Container(height: 20),
              Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  Column(
                    children: <Widget>[
                      const Text("Determine"),
                      Container(height: 20),
                      CircularProgressIndicator(
                        valueColor: AlwaysStoppedAnimation<Color>(Theme.of(context).colorScheme.primary),
                        backgroundColor: Colors.grey[300],
                        value: progressValue2,
                      ),
                    ],
                  ),
                  Container(width: 20),
                  Column(
                    children: <Widget>[
                      const Text("Indeterminate"),
                      Container(height: 20),
                       CircularProgressIndicator(
                        valueColor: AlwaysStoppedAnimation<Color>(Theme.of(context).colorScheme.primary),
                      ),
                    ],
                  ),
                ],
              ),


              Container(height: 10),
              const Text("Dot Grow"),
              Align(
                alignment: Alignment.center,
                child: Container(
                  width: 105,
                  height: 100,
                  alignment: Alignment.center,
                  child: WidgetDotGrow(color: Colors.pink[300], size: 20.0),
                ),
              ),

              const Text("Dot Bounce"),
              Align(
                alignment: Alignment.center,
                child: Container(
                  width: 105,
                  height: 100,
                  alignment: Alignment.center,
                  child: WidgetDotBounce(color: Colors.pink[300], size: 20.0),
                ),
              ),

              const Text("Dot Fade"),
              Align(
                alignment: Alignment.center,
                child: Container(
                  width: 105,
                  height: 100,
                  alignment: Alignment.center,
                  child: WidgetDotFade(color: Colors.pink[300], size: 20.0),
                ),
              )








            ]
        )
      )



    );
  }

  void updateProgress() {
    Timer.periodic(const Duration(milliseconds: 500), (Timer t) {
      setState(() {
        progressValue += 0.2;
        // we "finish" downloading here
        if (progressValue > 1.0) {
          progressValue = 0.0;
        }
      });
    });

    Timer.periodic(const Duration(seconds: 1), (Timer t) {
      setState(() {
        progressValue2 += 0.1;
        // we "finish" downloading here
        if (progressValue2 > 1.0) {
          progressValue2 = 0.0;
        }
      });
    });
  }
}

..

Get this helper class for custom progress:

delay_tween.dart

// Import the sin and pi functions from the math library with the name "math"
import 'dart:math' as math show sin, pi;

// Import the Animation class from the flutter/animation library
import 'package:flutter/animation.dart';

// Define a DelayTween class that extends the Tween class for double values
class DelayTween extends Tween<double> {

  // The constructor for the DelayTween class
  DelayTween({double? begin, double? end, required this.delay}) : super(begin: begin, end: end);

  // The delay value for the animation
  final double delay;

  // Override the lerp method to adjust the animation's progress based on the delay value
  @override
  double lerp(double t) {
    // Use the sin function from the math library to calculate a sine wave based on the current time and delay
    // Add 1 to the result to make sure it's always positive, then divide by 2 to get a value between 0 and 1
    return super.lerp((math.sin((t - delay) * 2 * math.pi) + 1) / 2);
  }

  // Override the evaluate method to call the lerp method with the animation's current value
  @override
  double evaluate(Animation<double> animation) => lerp(animation.value);
}

// Define a DelayTweenOffset class that extends the Tween class for Offset values
class DelayTweenOffset extends Tween<Offset> {

  // The constructor for the DelayTweenOffset class
  DelayTweenOffset({Offset? begin, Offset? end, required this.delay}) : super(begin: begin, end: end);

  // The delay value for the animation
  final double delay;

  // Override the lerp method to adjust the animation's progress based on the delay value
  @override
  Offset lerp(double t) {
    // Use the sin function from the math library to calculate a sine wave based on the current time and delay
    // Add 1 to the result to make sure it's always positive, then divide by 2 to get a value between 0 and 1
    return super.lerp((math.sin((t - delay) * 2 * math.pi) + 1) / 2);
  }

  // Override the evaluate method to call the lerp method with the animation's current value
  @override
  Offset evaluate(Animation<double> animation)  => lerp(animation.value);

}

.. 

progress_dot.dart

import 'package:flutter/widgets.dart';
import 'delay_tween.dart';

/*
 Dot bounce progress animation
 */

class WidgetDotBounce extends StatefulWidget {

  const WidgetDotBounce({
    Key? key,
    this.color,
    this.size = 18.0, this.count = 3,
    this.itemBuilder,
    this.duration = const Duration(milliseconds: 300),
    this.controller,
  })  : assert(!(itemBuilder is IndexedWidgetBuilder && color is Color) && !(itemBuilder == null && color == null),
  'You should specify either a itemBuilder or a color'),
        super(key: key);

  final Color? color;
  final double size;
  final int count;
  final IndexedWidgetBuilder? itemBuilder;
  final Duration duration;
  final AnimationController? controller;

  @override
  WidgetDotBounceState createState() => WidgetDotBounceState();

}

class WidgetDotBounceState extends State<WidgetDotBounce> with SingleTickerProviderStateMixin {
  late AnimationController _controller;

  @override
  void initState() {
    super.initState();

    _controller = (widget.controller ?? AnimationController(
        vsync: this, duration: Duration(milliseconds: widget.count * widget.duration.inMilliseconds))
    )..repeat();
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Center(
      child: SizedBox.fromSize(
        size: Size(widget.size * 3.2, widget.size * 2),
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: List.generate(widget.count, (i) {
            return SlideTransition(
              position: DelayTweenOffset(begin: const Offset(0, 0.5), end: const Offset(0, -0.5), delay: i * 0.2).animate(_controller),
              child: SizedBox.fromSize(size: Size.square(widget.size * 0.5), child: _itemBuilder(i)),
            );
          }),
        ),
      ),
    );
  }

  Widget _itemBuilder(int index) => widget.itemBuilder != null
      ? widget.itemBuilder!(context, index)
      : DecoratedBox(decoration: BoxDecoration(color: widget.color, shape: BoxShape.circle));
}

/*
 Dot grow progress animation
 */

class WidgetDotGrow extends StatefulWidget {

  const WidgetDotGrow({
    Key? key,
    this.color,
    this.size = 18.0, this.count = 3,
    this.itemBuilder,
    this.duration = const Duration(milliseconds: 300),
    this.controller,
  })  : assert(!(itemBuilder is IndexedWidgetBuilder && color is Color) && !(itemBuilder == null && color == null),
  'You should specify either a itemBuilder or a color'),
        super(key: key);

  final Color? color;
  final double size;
  final int count;
  final IndexedWidgetBuilder? itemBuilder;
  final Duration duration;
  final AnimationController? controller;

  @override
  WidgetDotGrowState createState() => WidgetDotGrowState();

}

class WidgetDotGrowState extends State<WidgetDotGrow> with SingleTickerProviderStateMixin {
  late AnimationController _controller;

  @override
  void initState() {
    super.initState();

    _controller = (widget.controller ?? AnimationController(
        vsync: this, duration: Duration(milliseconds: widget.count * widget.duration.inMilliseconds))
    )..repeat();
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Center(
      child: SizedBox.fromSize(
        size: Size(widget.size * 3.5, widget.size * 2),
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: List.generate(widget.count, (i) {
            return ScaleTransition(
              scale: DelayTween(begin: 1.0, end: 1.3, delay: i * 0.2).animate(_controller),
              child: SizedBox.fromSize(size: Size.square(widget.size * 0.5), child: _itemBuilder(i)),
            );
          }),
        ),
      ),
    );
  }

  Widget _itemBuilder(int index) => widget.itemBuilder != null
      ? widget.itemBuilder!(context, index)
      : DecoratedBox(decoration: BoxDecoration(color: widget.color, shape: BoxShape.circle));
}

/*
 Dot fade progress animation
 */

class WidgetDotFade extends StatefulWidget {

  const WidgetDotFade({
    Key? key,
    this.color,
    this.size = 18.0,
    this.count = 3,
    this.itemBuilder,
    this.duration = const Duration(milliseconds: 500),
    this.controller,
  })  : assert(!(itemBuilder is IndexedWidgetBuilder && color is Color) && !(itemBuilder == null && color == null),
  'You should specify either a itemBuilder or a color'),
        super(key: key);

  final Color? color;
  final double size;
  final int count;
  final IndexedWidgetBuilder? itemBuilder;
  final Duration duration;
  final AnimationController? controller;

  @override
  WidgetDotFadeState createState() => WidgetDotFadeState();

}

class WidgetDotFadeState extends State<WidgetDotFade> with SingleTickerProviderStateMixin {
  late AnimationController _controller;

  @override
  void initState() {
    super.initState();

    _controller = (widget.controller ?? AnimationController(
        vsync: this, duration: Duration(milliseconds: widget.count * widget.duration.inMilliseconds))
    )..repeat();
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Center(
      child: SizedBox.fromSize(
        size: Size(widget.size * 3.2, widget.size * 2),
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: List.generate(widget.count, (i) {
            return FadeTransition(
              opacity: DelayTween(begin: 0.1, end: 1.0, delay: i * 0.2).animate(_controller),
              child: SizedBox.fromSize(size: Size.square(widget.size * 0.5), child: _itemBuilder(i)),
            );
          }),
        ),
      ),
    );
  }

  Widget _itemBuilder(int index) => widget.itemBuilder != null
      ? widget.itemBuilder!(context, index)
      : DecoratedBox(decoration: BoxDecoration(color: widget.color, shape: BoxShape.circle));
}

,,

Comments