Search code examples
flutterflutter-layoutprogress-barflutter-animation

How to make a linear progress indicator with swing effect?


I want to make a progress bar that looks like this. If you look closely, the length of the indicator varies when it swings.

Text

I tried to reuse the linear progress bar that flutter provides.

import 'package:flutter/material.dart';

class MyStatefulWidget extends StatefulWidget {
  const MyStatefulWidget({Key? key}) : super(key: key);

  @override
  State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}

class _MyStatefulWidgetState extends State<MyStatefulWidget>
    with TickerProviderStateMixin {
  late AnimationController controller;

  @override
  void initState() {
    controller = AnimationController(
      vsync: this,
      duration: const Duration(seconds: 5),
    )..addListener(() {
        setState(() {});
      });
    controller.repeat(reverse: true);
    super.initState();
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Padding(
        padding: const EdgeInsets.all(20.0),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: <Widget>[
            LinearProgressIndicator(
              value: controller.value,
            ),
          ],
        ),
      ),
    );
  }
}

And it looks like this.

Text


Solution

  • You can follow this snippet. Run on dartPad

    ///  create ---- ProgressIndicator
    ///
    /// ````
    /// child: SizedBox(
    ///    height: 12,
    ///    child: CustomLinearProgressIndicator(
    ///      backgroundColor: Colors.white,
    ///      color: Colors.blue,
    ///      maxProgressWidth: 100,
    ///    ),
    ///  ),
    /// ````
    class CustomLinearProgressIndicator extends StatefulWidget {
      const CustomLinearProgressIndicator({
        Key? key,
        this.color = Colors.blue,
        this.backgroundColor = Colors.white,
        this.maxProgressWidth = 100,
      }) : super(key: key);
    
      /// max width in center progress
      final double maxProgressWidth;
    
      final Color color;
      final Color backgroundColor;
      @override
      State<CustomLinearProgressIndicator> createState() =>
          _CustomLinearProgressIndicatorState();
    }
    
    class _CustomLinearProgressIndicatorState
        extends State<CustomLinearProgressIndicator>
        with SingleTickerProviderStateMixin {
      late AnimationController controller =
          AnimationController(vsync: this, duration: const Duration(seconds: 1))
            ..addListener(() {
              setState(() {});
            })
            ..repeat(reverse: true);
    
      late Animation animation =
          Tween<double>(begin: -1, end: 1).animate(controller);
    
      @override
      Widget build(BuildContext context) {
        return ColoredBox(
          color: widget.backgroundColor,
          child: Align(
            alignment: Alignment(animation.value, 0),
            child: Container(
              decoration: ShapeDecoration(
                // play with BoxDecoration if you feel it is needed
                color: widget.color,
                shape: const StadiumBorder(),
              ),
              // you can use animatedContainer, seems not needed
              width: widget.maxProgressWidth -
                  widget.maxProgressWidth * (animation.value as double).abs(),
              height: double.infinity,
            ),
          ),
        );
      }
    }