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();
}
}