Flutter Progress Indicator: 2 Flying Dots

Two flying dots fly at opposite directions.

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