Skip to main content

How to Create a Custom Progress Indicator with Animation in Flutter?

In this post, we’re going to be creating a custom progress indicator in Flutter. We’ll be using the animation controller from the Animation package. We’ll also be adding an underlying animation that will make our progress indicator look like it’s animating smoothly.

import 'package:flutter/material.dart';

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  @override
  void initState() {
    super.initState();
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Progress Indicator'),
      ),
      body: SafeArea(
        child: Center(
          child: TwoFlyingDots(),
        ),
      ),
    );
  }
}

class TwoFlyingDots extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Stack(children: [
      FlyingDot(color: Colors.blue),
      FlyingDot(color: Colors.red, reverse: true)
    ]);
  }
}

class FlyingDot extends StatefulWidget {
  final Color? color;
  final bool? reverse;

  const FlyingDot({Key? key, this.color, this.reverse}) : super(key: key);

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

class _FlyingDotState extends State<FlyingDot>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<Offset> _position;
  late Animation<Offset> _position2;
  late Animation<Offset> _position3;
  late bool _reverse;
  double rangeX = 20.0;
  double rangeY = 10.0;

  @override
  void initState() {
    _reverse = widget.reverse ?? false;

    _controller =
        AnimationController(vsync: this, duration: Duration(seconds: 1));

    Tween<Offset> start = _reverse
        ? Tween<Offset>(begin: Offset(-rangeX, 0), end: Offset(0, rangeY))
        : Tween<Offset>(begin: Offset(rangeX, 0), end: Offset(0, rangeY));
    Tween<Offset> middle = _reverse
        ? Tween<Offset>(begin: Offset(0, rangeY), end: Offset(rangeX, 0))
        : Tween<Offset>(begin: Offset(0, rangeY), end: Offset(-rangeX, 0));
    Tween<Offset> end = _reverse
        ? Tween<Offset>(begin: Offset(rangeX, 0), end: Offset(-rangeX, 0))
        : Tween<Offset>(begin: Offset(-rangeX, 0), end: Offset(rangeX, 0));

    _position = start.animate(
      CurvedAnimation(
        parent: _controller,
        curve: Interval(0, 0.25),
      ),
    );
    _position2 = middle.animate(
      CurvedAnimation(
        parent: _controller,
        curve: Interval(0.25, 0.5),
      ),
    );

    _position3 = end.animate(
      CurvedAnimation(
        parent: _controller,
        curve: Interval(0.5, 1),
      ),
    );

    //run animation
    _controller.repeat();

    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
        animation: _controller,
        builder: (context, child) {
          Offset offset = Offset.zero;
          if (_controller.value <= 0.25) {
            offset = _position.value;
          } else if (_controller.value <= 0.5) {
            offset = _position2.value;
          } else {
            offset = _position3.value;
          }
          return Transform.translate(
              offset: offset, child: Icon(Icons.circle, color: widget.color));
        });
  }

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

By continuing to use the site, you agree to the use of cookies.