Skip to content

Instantly share code, notes, and snippets.

@contactjavas
Last active July 14, 2022 23:51

Revisions

  1. contactjavas revised this gist Jul 14, 2022. 1 changed file with 1 addition and 5 deletions.
    6 changes: 1 addition & 5 deletions main.dart
    Original file line number Diff line number Diff line change
    @@ -1,8 +1,4 @@
    /// Note:
    /// In the DartPad version, our use of `CircularProgressIndicator` as the `loadingStateWidget` is not well-displayed.
    /// But it's fine when using it in mobile phone (Android, in our test).
    import 'package:flutter/material.dart';
    import 'package:flutter/material.dart';

    /// begin_library_part
    ///
  2. contactjavas revised this gist Jul 12, 2022. No changes.
  3. contactjavas revised this gist Jul 12, 2022. 1 changed file with 280 additions and 0 deletions.
    280 changes: 280 additions & 0 deletions main.dart
    Original file line number Diff line number Diff line change
    @@ -305,3 +305,283 @@ class _EasyButtonState extends State<EasyButton> with TickerProviderStateMixin {
    _animController!.reverse();
    }
    }

    /// end_library_part
    /// begin_example_part
    void main() {
    runApp(const MyApp());
    }

    class MyApp extends StatelessWidget {
    const MyApp({Key? key}) : super(key: key);

    // This widget is the root of your application.
    @override
    Widget build(BuildContext context) {
    return MaterialApp(
    title: 'Easy Loading Button',
    debugShowCheckedModeBanner: false,
    theme: ThemeData(
    primarySwatch: Colors.blue,
    ),
    home: const ExamplePage(title: 'Easy Loading Button'),
    );
    }
    }

    class ExamplePage extends StatefulWidget {
    const ExamplePage({Key? key, required this.title}) : super(key: key);

    final String title;

    @override
    State<ExamplePage> createState() => _ExamplePageState();
    }

    class _ExamplePageState extends State<ExamplePage> {
    @override
    Widget build(BuildContext context) {
    onButtonPressed() async {
    await Future.delayed(const Duration(milliseconds: 3000), () => 42);

    // After [onPressed], it will trigger animation running backwards, from end to beginning
    return () {
    // Optional returns is returning a VoidCallback that will be called
    // after the animation is stopped at the beginning.
    // A best practice would be to do time-consuming task in [onPressed],
    // and do page navigation in the returned VoidCallback.
    // So that user won't missed out the reverse animation.
    };
    }

    return Scaffold(
    appBar: AppBar(
    title: Text(widget.title),
    ),
    body: Center(
    child: Column(
    mainAxisAlignment: MainAxisAlignment.center,
    children: <Widget>[
    const SizedBox(
    height: 15,
    ),
    const Text(
    'Elevated button',
    ),
    const SizedBox(
    height: 5,
    ),
    EasyButton(
    idleStateWidget: const Text(
    'Elevated button',
    style: TextStyle(
    color: Colors.white,
    ),
    ),
    loadingStateWidget: const CircularProgressIndicator(
    strokeWidth: 3.0,
    valueColor: AlwaysStoppedAnimation<Color>(
    Colors.white,
    ),
    ),
    useEqualLoadingStateWidgetDimension: true,
    useWidthAnimation: false,
    width: 150.0,
    height: 40.0,
    borderRadius: 4.0,
    elevation: 2.0,
    contentGap: 6.0,
    buttonColor: Colors.blueAccent,
    onPressed: onButtonPressed,
    ),
    const SizedBox(
    height: 15,
    ),
    const Text(
    'Elevated button (width animated)',
    ),
    const SizedBox(
    height: 5,
    ),
    EasyButton(
    idleStateWidget: const Text(
    'Elevated button',
    style: TextStyle(
    color: Colors.white,
    ),
    ),
    loadingStateWidget: const CircularProgressIndicator(
    strokeWidth: 3.0,
    valueColor: AlwaysStoppedAnimation<Color>(
    Colors.white,
    ),
    ),
    useWidthAnimation: true,
    useEqualLoadingStateWidgetDimension: true,
    width: 150.0,
    height: 40.0,
    borderRadius: 4.0,
    contentGap: 6.0,
    buttonColor: Colors.blueAccent,
    onPressed: onButtonPressed,
    ),
    const SizedBox(
    height: 15,
    ),
    const Text(
    'Outlined button',
    ),
    const SizedBox(
    height: 5,
    ),
    EasyButton(
    type: EasyButtonType.outlined,
    idleStateWidget: const Text(
    'Outlined button',
    style: TextStyle(
    color: Colors.blueAccent,
    ),
    ),
    loadingStateWidget: const CircularProgressIndicator(
    strokeWidth: 3.0,
    valueColor: AlwaysStoppedAnimation<Color>(
    Colors.blueAccent,
    ),
    ),
    useEqualLoadingStateWidgetDimension: true,
    useWidthAnimation: false,
    width: 150.0,
    height: 40.0,
    borderRadius: 4.0,
    contentGap: 6.0,
    onPressed: onButtonPressed,
    ),
    const SizedBox(
    height: 15,
    ),
    const Text(
    'Outlined button (width animated)',
    ),
    const SizedBox(
    height: 5,
    ),
    EasyButton(
    type: EasyButtonType.outlined,
    idleStateWidget: const Text(
    'Outlined button',
    style: TextStyle(
    color: Colors.blueAccent,
    ),
    ),
    loadingStateWidget: const CircularProgressIndicator(
    strokeWidth: 3.0,
    valueColor: AlwaysStoppedAnimation<Color>(
    Colors.blueAccent,
    ),
    ),
    useWidthAnimation: true,
    useEqualLoadingStateWidgetDimension: true,
    width: 150.0,
    height: 40.0,
    borderRadius: 4.0,
    contentGap: 6.0,
    onPressed: onButtonPressed,
    ),
    const SizedBox(
    height: 15,
    ),
    const Text(
    'Text button',
    ),
    const SizedBox(
    height: 5,
    ),
    EasyButton(
    type: EasyButtonType.text,
    idleStateWidget: const Text(
    'Text button',
    style: TextStyle(
    color: Colors.blueAccent,
    ),
    ),
    loadingStateWidget: const CircularProgressIndicator(
    strokeWidth: 3.0,
    valueColor: AlwaysStoppedAnimation<Color>(
    Colors.blueAccent,
    ),
    ),
    useEqualLoadingStateWidgetDimension: true,
    useWidthAnimation: false,
    width: 150.0,
    height: 28.0,
    borderRadius: 4.0,
    onPressed: onButtonPressed,
    ),
    const SizedBox(
    height: 15,
    ),
    const Text(
    'Fullwidth elevated button',
    ),
    const SizedBox(
    height: 5,
    ),
    EasyButton(
    idleStateWidget: const Text(
    'Fullwidth elevated button',
    style: TextStyle(
    color: Colors.white,
    ),
    ),
    loadingStateWidget: const CircularProgressIndicator(
    strokeWidth: 3.0,
    valueColor: AlwaysStoppedAnimation<Color>(
    Colors.white,
    ),
    ),
    useEqualLoadingStateWidgetDimension: true,
    useWidthAnimation: false,
    width: double.infinity,
    height: 40.0,
    contentGap: 6.0,
    buttonColor: Colors.blueAccent,
    onPressed: onButtonPressed,
    ),
    const SizedBox(
    height: 15,
    ),
    const Text(
    'Fullwidth elevated button (width animated)',
    ),
    const SizedBox(
    height: 5,
    ),
    EasyButton(
    idleStateWidget: const Text(
    'Fullwidth elevated button',
    style: TextStyle(
    color: Colors.white,
    ),
    ),
    loadingStateWidget: const CircularProgressIndicator(
    strokeWidth: 3.0,
    valueColor: AlwaysStoppedAnimation<Color>(
    Colors.white,
    ),
    ),
    useWidthAnimation: true,
    useEqualLoadingStateWidgetDimension: true,
    width: double.infinity,
    height: 40.0,
    contentGap: 6.0,
    buttonColor: Colors.blueAccent,
    onPressed: onButtonPressed,
    ),
    ],
    ),
    ), // This trailing comma makes auto-formatting nicer for build methods.
    );
    }
    }
  4. contactjavas revised this gist Jul 12, 2022. 1 changed file with 12 additions and 0 deletions.
    12 changes: 12 additions & 0 deletions main.dart
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,15 @@
    /// Note:
    /// In the DartPad version, our use of `CircularProgressIndicator` as the `loadingStateWidget` is not well-displayed.
    /// But it's fine when using it in mobile phone (Android, in our test).
    import 'package:flutter/material.dart';

    /// begin_library_part
    ///
    /// When writing this example, DartPad didn't support (many/almost all) custom packages
    /// If you want to directly check for the related code (not the library),
    /// then please search (in this DartPad) for this keyword: begin_example_part
    enum EasyButtonState {
    idle,
    loading,
  5. contactjavas revised this gist Jul 12, 2022. 1 changed file with 11 additions and 302 deletions.
    313 changes: 11 additions & 302 deletions main.dart
    Original file line number Diff line number Diff line change
    @@ -1,15 +1,3 @@
    /// Note:
    /// In the DartPad version, our use of `CircularProgressIndicator` as the `loadingStateWidget` is not well-displayed.
    /// But it's fine when using it in mobile phone (Android, in our test).
    import 'package:flutter/material.dart';

    /// begin_library_part
    ///
    /// When writing this example, DartPad didn't support (many/almost all) custom packages
    /// If you want to directly check for the related code (not the library),
    /// then please search (in this DartPad) for this keyword: begin_example_part
    enum EasyButtonState {
    idle,
    loading,
    @@ -31,16 +19,16 @@ class EasyButton extends StatefulWidget {
    /// The button type.
    final EasyButtonType type;

    /// Whether or not to animate the width of the button.
    /// Whether or not to animate the width of the button. Default is `true`.
    ///
    /// If this is set to `false`, you might want to also check the `useEqualLoadingStateWidgetDimension` parameter and set it to `true`.
    /// If this is set to `false`, you might want to set the `useEqualLoadingStateWidgetDimension` parameter to `true`.
    final bool useWidthAnimation;

    /// Whether or not to force the `loadingStateWidget` to have equal dimension when `useWidthAnimation` is set to false.
    /// Whether or not to force the `loadingStateWidget` to have equal dimension.
    ///
    /// This is useful when you are using `CircularProgressIndicator` as the `loadingStateWidget`.
    ///
    /// This parameter will be ignored when `useWidthAnimation` value is `true`.
    /// This parameter might also be useful when you set the `useWidthAnimation` parameter to `true` combined with `CircularProgressIndicator` as the value for `loadingStateWidget`.
    final bool useEqualLoadingStateWidgetDimension;

    /// The button width.
    @@ -91,7 +79,7 @@ class EasyButton extends StatefulWidget {
    }) : super(key: key);

    @override
    _EasyButtonState createState() => _EasyButtonState();
    State createState() => _EasyButtonState();
    }

    class _EasyButtonState extends State<EasyButton> with TickerProviderStateMixin {
    @@ -181,20 +169,20 @@ class _EasyButtonState extends State<EasyButton> with TickerProviderStateMixin {
    case EasyButtonType.elevated:
    return ElevatedButton(
    style: elevatedButtonStyle,
    child: _buildChildren(context),
    onPressed: _onButtonPressed(),
    child: _buildChildren(context),
    );
    case EasyButtonType.outlined:
    return TextButton(
    style: outlinedButtonStyle,
    child: _buildChildren(context),
    onPressed: _onButtonPressed(),
    child: _buildChildren(context),
    );
    case EasyButtonType.text:
    return TextButton(
    style: textButtonStyle,
    child: _buildChildren(context),
    onPressed: _onButtonPressed(),
    child: _buildChildren(context),
    );
    }
    }
    @@ -210,14 +198,13 @@ class _EasyButtonState extends State<EasyButton> with TickerProviderStateMixin {

    break;
    case EasyButtonState.loading:
    if (!widget.useWidthAnimation &&
    widget.useEqualLoadingStateWidgetDimension) {
    contentWidget = widget.loadingStateWidget;

    if (widget.useEqualLoadingStateWidgetDimension) {
    contentWidget = SizedBox.square(
    dimension: widget.height - (contentGap * 2),
    child: widget.loadingStateWidget,
    );
    } else {
    contentWidget = widget.loadingStateWidget;
    }

    break;
    @@ -306,281 +293,3 @@ class _EasyButtonState extends State<EasyButton> with TickerProviderStateMixin {
    _animController!.reverse();
    }
    }

    /// end_library_part
    /// begin_example_part
    void main() {
    runApp(const MyApp());
    }

    class MyApp extends StatelessWidget {
    const MyApp({Key? key}) : super(key: key);

    // This widget is the root of your application.
    @override
    Widget build(BuildContext context) {
    return MaterialApp(
    title: 'Flutter Loading Button',
    debugShowCheckedModeBanner: false,
    theme: ThemeData(
    primarySwatch: Colors.blue,
    ),
    home: const MyHomePage(title: 'Flutter Loading Button'),
    );
    }
    }

    class MyHomePage extends StatefulWidget {
    const MyHomePage({Key? key, required this.title}) : super(key: key);

    final String title;

    @override
    State<MyHomePage> createState() => _MyHomePageState();
    }

    class _MyHomePageState extends State<MyHomePage> {
    @override
    Widget build(BuildContext context) {
    onButtonPressed() async {
    await Future.delayed(const Duration(milliseconds: 3000), () => 42);

    // After [onPressed], it will trigger animation running backwards, from end to beginning
    return () {
    // Optional returns is returning a VoidCallback that will be called
    // after the animation is stopped at the beginning.
    // A best practice would be to do time-consuming task in [onPressed],
    // and do page navigation in the returned VoidCallback.
    // So that user won't missed out the reverse animation.
    };
    }

    return Scaffold(
    appBar: AppBar(
    title: Text(widget.title),
    ),
    body: Center(
    child: Column(
    mainAxisAlignment: MainAxisAlignment.center,
    children: <Widget>[
    const SizedBox(
    height: 15,
    ),
    const Text(
    'Elevated Button',
    ),
    const SizedBox(
    height: 5,
    ),
    EasyButton(
    idleStateWidget: const Text(
    'Elevated Button',
    style: TextStyle(
    color: Colors.white,
    ),
    ),
    loadingStateWidget: const CircularProgressIndicator(
    strokeWidth: 3.0,
    valueColor: AlwaysStoppedAnimation<Color>(
    Colors.white,
    ),
    ),
    useEqualLoadingStateWidgetDimension: true,
    useWidthAnimation: false,
    width: 150.0,
    height: 40.0,
    borderRadius: 4.0,
    elevation: 2.0,
    contentGap: 6.0,
    buttonColor: Colors.blueAccent,
    onPressed: onButtonPressed,
    ),
    const SizedBox(
    height: 15,
    ),
    const Text(
    'Elevated Button - Animated',
    ),
    const SizedBox(
    height: 5,
    ),
    EasyButton(
    idleStateWidget: const Text(
    'Elevated Button',
    style: TextStyle(
    color: Colors.white,
    ),
    ),
    loadingStateWidget: const CircularProgressIndicator(
    strokeWidth: 3.0,
    valueColor: AlwaysStoppedAnimation<Color>(
    Colors.white,
    ),
    ),
    useWidthAnimation: true,
    width: 150.0,
    height: 40.0,
    borderRadius: 4.0,
    contentGap: 6.0,
    buttonColor: Colors.blueAccent,
    onPressed: onButtonPressed,
    ),
    const SizedBox(
    height: 15,
    ),
    const Text(
    'Outlined Button',
    ),
    const SizedBox(
    height: 5,
    ),
    EasyButton(
    type: EasyButtonType.outlined,
    idleStateWidget: const Text(
    'Outlined Button',
    style: TextStyle(
    color: Colors.blueAccent,
    ),
    ),
    loadingStateWidget: const CircularProgressIndicator(
    strokeWidth: 3.0,
    valueColor: AlwaysStoppedAnimation<Color>(
    Colors.blueAccent,
    ),
    ),
    useEqualLoadingStateWidgetDimension: true,
    useWidthAnimation: false,
    width: 150.0,
    height: 40.0,
    borderRadius: 4.0,
    contentGap: 6.0,
    onPressed: onButtonPressed,
    ),
    const SizedBox(
    height: 15,
    ),
    const Text(
    'Outlined Button - Animated',
    ),
    const SizedBox(
    height: 5,
    ),
    EasyButton(
    type: EasyButtonType.outlined,
    idleStateWidget: const Text(
    'Outlined Button',
    style: TextStyle(
    color: Colors.blueAccent,
    ),
    ),
    loadingStateWidget: const CircularProgressIndicator(
    strokeWidth: 3.0,
    valueColor: AlwaysStoppedAnimation<Color>(
    Colors.blueAccent,
    ),
    ),
    useWidthAnimation: true,
    width: 150.0,
    height: 40.0,
    borderRadius: 4.0,
    contentGap: 6.0,
    onPressed: onButtonPressed,
    ),
    const SizedBox(
    height: 15,
    ),
    const Text(
    'Text Button',
    ),
    const SizedBox(
    height: 5,
    ),
    EasyButton(
    type: EasyButtonType.text,
    idleStateWidget: const Text(
    'Text Button',
    style: TextStyle(
    color: Colors.blueAccent,
    ),
    ),
    loadingStateWidget: const CircularProgressIndicator(
    strokeWidth: 3.0,
    valueColor: AlwaysStoppedAnimation<Color>(
    Colors.blueAccent,
    ),
    ),
    useEqualLoadingStateWidgetDimension: true,
    useWidthAnimation: false,
    width: 150.0,
    height: 28.0,
    borderRadius: 4.0,
    onPressed: onButtonPressed,
    ),
    const SizedBox(
    height: 15,
    ),
    const Text(
    'Elevated Button Fullwidth',
    ),
    const SizedBox(
    height: 5,
    ),
    EasyButton(
    idleStateWidget: const Text(
    'Elevated Button Fullwidth',
    style: TextStyle(
    color: Colors.white,
    ),
    ),
    loadingStateWidget: const CircularProgressIndicator(
    strokeWidth: 3.0,
    valueColor: AlwaysStoppedAnimation<Color>(
    Colors.white,
    ),
    ),
    useEqualLoadingStateWidgetDimension: true,
    useWidthAnimation: false,
    width: double.infinity,
    height: 40.0,
    contentGap: 6.0,
    buttonColor: Colors.blueAccent,
    onPressed: onButtonPressed,
    ),
    const SizedBox(
    height: 15,
    ),
    const Text(
    'Elevated Button Fullwidth - Animated',
    ),
    const SizedBox(
    height: 5,
    ),
    EasyButton(
    idleStateWidget: const Text(
    'Elevated Button Fullwidth',
    style: TextStyle(
    color: Colors.white,
    ),
    ),
    loadingStateWidget: const CircularProgressIndicator(
    strokeWidth: 3.0,
    valueColor: AlwaysStoppedAnimation<Color>(
    Colors.white,
    ),
    ),
    useWidthAnimation: true,
    width: double.infinity,
    height: 40.0,
    contentGap: 6.0,
    buttonColor: Colors.blueAccent,
    onPressed: onButtonPressed,
    ),
    ],
    ),
    ), // This trailing comma makes auto-formatting nicer for build methods.
    );
    }
    }

    /// end_example_part
  6. contactjavas revised this gist Apr 16, 2022. 1 changed file with 17 additions and 5 deletions.
    22 changes: 17 additions & 5 deletions main.dart
    Original file line number Diff line number Diff line change
    @@ -28,6 +28,7 @@ class EasyButton extends StatefulWidget {
    /// Content inside the button when the button state is loading.
    final Widget loadingStateWidget;

    /// The button type.
    final EasyButtonType type;

    /// Whether or not to animate the width of the button.
    @@ -41,13 +42,19 @@ class EasyButton extends StatefulWidget {
    ///
    /// This parameter will be ignored when `useWidthAnimation` value is `true`.
    final bool useEqualLoadingStateWidgetDimension;

    /// The button width.
    final double width;

    /// The button height.
    final double height;

    /// The gap between button and it's content.
    ///
    /// This will be ignored when the `type` parameter value is set to `EasyButtonType.text`
    final double contentGap;

    /// The visual border radius of the button.
    final double borderRadius;

    /// The elevation of the button.
    @@ -63,6 +70,8 @@ class EasyButton extends StatefulWidget {
    ///
    /// For [`EasyButtonType.text`]: This will be the text color.
    final Color buttonColor;

    /// Function to run when button is pressed.
    final Function? onPressed;

    const EasyButton({
    @@ -89,7 +98,7 @@ class _EasyButtonState extends State<EasyButton> with TickerProviderStateMixin {
    final GlobalKey _globalKey = GlobalKey();

    Animation? _anim;
    late AnimationController _animController;
    AnimationController? _animController;
    final Duration _duration = const Duration(
    milliseconds: 250,
    );
    @@ -100,7 +109,10 @@ class _EasyButtonState extends State<EasyButton> with TickerProviderStateMixin {

    @override
    dispose() {
    _animController.dispose();
    if (_animController != null) {
    _animController!.dispose();
    }

    super.dispose();
    }

    @@ -277,7 +289,7 @@ class _EasyButtonState extends State<EasyButton> with TickerProviderStateMixin {
    double targetBorderRadius = _height / 2;

    _animController = AnimationController(duration: _duration, vsync: this);
    _anim = Tween(begin: 0.0, end: 1.0).animate(_animController)
    _anim = Tween(begin: 0.0, end: 1.0).animate(_animController!)
    ..addListener(() {
    setState(() {
    _width = initialWidth - ((initialWidth - targetWidth) * _anim!.value);
    @@ -287,11 +299,11 @@ class _EasyButtonState extends State<EasyButton> with TickerProviderStateMixin {
    })
    ..addStatusListener(stateListener);

    _animController.forward();
    _animController!.forward();
    }

    void _reverse() {
    _animController.reverse();
    _animController!.reverse();
    }
    }

  7. contactjavas revised this gist Apr 11, 2022. 1 changed file with 4 additions and 0 deletions.
    4 changes: 4 additions & 0 deletions main.dart
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,7 @@
    /// Note:
    /// In the DartPad version, our use of `CircularProgressIndicator` as the `loadingStateWidget` is not well-displayed.
    /// But it's fine when using it in mobile phone (Android, in our test).
    import 'package:flutter/material.dart';

    /// begin_library_part
  8. contactjavas created this gist Apr 11, 2022.
    570 changes: 570 additions & 0 deletions main.dart
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,570 @@
    import 'package:flutter/material.dart';

    /// begin_library_part
    ///
    /// When writing this example, DartPad didn't support (many/almost all) custom packages
    /// If you want to directly check for the related code (not the library),
    /// then please search (in this DartPad) for this keyword: begin_example_part
    enum EasyButtonState {
    idle,
    loading,
    }

    enum EasyButtonType {
    elevated,
    outlined,
    text,
    }

    class EasyButton extends StatefulWidget {
    /// Content inside the button when the button state is idle.
    final Widget idleStateWidget;

    /// Content inside the button when the button state is loading.
    final Widget loadingStateWidget;

    final EasyButtonType type;

    /// Whether or not to animate the width of the button.
    ///
    /// If this is set to `false`, you might want to also check the `useEqualLoadingStateWidgetDimension` parameter and set it to `true`.
    final bool useWidthAnimation;

    /// Whether or not to force the `loadingStateWidget` to have equal dimension when `useWidthAnimation` is set to false.
    ///
    /// This is useful when you are using `CircularProgressIndicator` as the `loadingStateWidget`.
    ///
    /// This parameter will be ignored when `useWidthAnimation` value is `true`.
    final bool useEqualLoadingStateWidgetDimension;
    final double width;
    final double height;

    /// The gap between button and it's content.
    ///
    /// This will be ignored when the `type` parameter value is set to `EasyButtonType.text`
    final double contentGap;
    final double borderRadius;

    /// The elevation of the button.
    ///
    /// This will only be applied when the `type` parameter value is `EasyButtonType.elevated`
    final double elevation;

    /// Color for the button.
    ///
    /// For [`EasyButtonType.elevated`]: This will be the background color.
    ///
    /// For [`EasyButtonType.outlined`]: This will be the border color.
    ///
    /// For [`EasyButtonType.text`]: This will be the text color.
    final Color buttonColor;
    final Function? onPressed;

    const EasyButton({
    Key? key,
    required this.idleStateWidget,
    required this.loadingStateWidget,
    this.type = EasyButtonType.elevated,
    this.useWidthAnimation = true,
    this.useEqualLoadingStateWidgetDimension = true,
    this.width = double.infinity,
    this.height = 40.0,
    this.contentGap = 0.0,
    this.borderRadius = 0.0,
    this.elevation = 0.0,
    this.buttonColor = Colors.blueAccent,
    this.onPressed,
    }) : super(key: key);

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

    class _EasyButtonState extends State<EasyButton> with TickerProviderStateMixin {
    final GlobalKey _globalKey = GlobalKey();

    Animation? _anim;
    late AnimationController _animController;
    final Duration _duration = const Duration(
    milliseconds: 250,
    );
    EasyButtonState _state = EasyButtonState.idle;
    late double _width;
    late double _height;
    late double _borderRadius;

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

    @override
    void deactivate() {
    _reset();
    super.deactivate();
    }

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

    void _reset() {
    _state = EasyButtonState.idle;
    _width = widget.width;
    _height = widget.height;
    _borderRadius = widget.borderRadius;
    }

    @override
    Widget build(BuildContext context) {
    return PhysicalModel(
    color: Colors.transparent,
    borderRadius: BorderRadius.circular(_borderRadius),
    child: SizedBox(
    key: _globalKey,
    height: _height,
    width: _width,
    child: _buildChild(context),
    ),
    );
    }

    Widget _buildChild(BuildContext context) {
    var padding = EdgeInsets.all(
    widget.contentGap,
    );
    var buttonColor = widget.buttonColor;
    var shape = RoundedRectangleBorder(
    borderRadius: BorderRadius.circular(_borderRadius),
    );

    final ButtonStyle elevatedButtonStyle = ElevatedButton.styleFrom(
    padding: padding,
    primary: buttonColor,
    elevation: widget.elevation,
    shape: shape,
    );

    final ButtonStyle outlinedButtonStyle = OutlinedButton.styleFrom(
    padding: padding,
    shape: shape,
    side: BorderSide(
    color: buttonColor,
    ),
    );

    final ButtonStyle textButtonStyle = TextButton.styleFrom(
    padding: padding,
    );

    switch (widget.type) {
    case EasyButtonType.elevated:
    return ElevatedButton(
    style: elevatedButtonStyle,
    child: _buildChildren(context),
    onPressed: _onButtonPressed(),
    );
    case EasyButtonType.outlined:
    return TextButton(
    style: outlinedButtonStyle,
    child: _buildChildren(context),
    onPressed: _onButtonPressed(),
    );
    case EasyButtonType.text:
    return TextButton(
    style: textButtonStyle,
    child: _buildChildren(context),
    onPressed: _onButtonPressed(),
    );
    }
    }

    Widget _buildChildren(BuildContext context) {
    double contentGap =
    widget.type == EasyButtonType.text ? 0.0 : widget.contentGap;
    Widget contentWidget;

    switch (_state) {
    case EasyButtonState.idle:
    contentWidget = widget.idleStateWidget;

    break;
    case EasyButtonState.loading:
    if (!widget.useWidthAnimation &&
    widget.useEqualLoadingStateWidgetDimension) {
    contentWidget = SizedBox.square(
    dimension: widget.height - (contentGap * 2),
    child: widget.loadingStateWidget,
    );
    } else {
    contentWidget = widget.loadingStateWidget;
    }

    break;
    }

    return contentWidget;
    }

    VoidCallback _onButtonPressed() {
    if (widget.onPressed == null) {
    return () {};
    }

    return _manageLoadingState;
    }

    Future _manageLoadingState() async {
    if (_state != EasyButtonState.idle) {
    return;
    }

    // The result of widget.onPressed() will be called as VoidCallback after button status is back to default.
    dynamic onIdle;

    if (widget.useWidthAnimation) {
    _toProcessing();
    _forward((status) {
    if (status == AnimationStatus.dismissed) {
    _toDefault();
    if (onIdle != null &&
    (onIdle is VoidCallback || onIdle is FormFieldValidator)) {
    onIdle();
    }
    }
    });
    onIdle = await widget.onPressed!();
    _reverse();
    } else {
    _toProcessing();
    onIdle = await widget.onPressed!();
    _toDefault();
    if (onIdle != null &&
    (onIdle is VoidCallback || onIdle is FormFieldValidator)) {
    onIdle();
    }
    }
    }

    void _toProcessing() {
    setState(() {
    _state = EasyButtonState.loading;
    });
    }

    void _toDefault() {
    if (mounted) {
    setState(() {
    _state = EasyButtonState.idle;
    });
    } else {
    _state = EasyButtonState.idle;
    }
    }

    void _forward(AnimationStatusListener stateListener) {
    double initialWidth = _globalKey.currentContext!.size!.width;
    double initialBorderRadius = widget.borderRadius;
    double targetWidth = _height;
    double targetBorderRadius = _height / 2;

    _animController = AnimationController(duration: _duration, vsync: this);
    _anim = Tween(begin: 0.0, end: 1.0).animate(_animController)
    ..addListener(() {
    setState(() {
    _width = initialWidth - ((initialWidth - targetWidth) * _anim!.value);
    _borderRadius = initialBorderRadius -
    ((initialBorderRadius - targetBorderRadius) * _anim!.value);
    });
    })
    ..addStatusListener(stateListener);

    _animController.forward();
    }

    void _reverse() {
    _animController.reverse();
    }
    }

    /// end_library_part
    /// begin_example_part
    void main() {
    runApp(const MyApp());
    }

    class MyApp extends StatelessWidget {
    const MyApp({Key? key}) : super(key: key);

    // This widget is the root of your application.
    @override
    Widget build(BuildContext context) {
    return MaterialApp(
    title: 'Flutter Loading Button',
    debugShowCheckedModeBanner: false,
    theme: ThemeData(
    primarySwatch: Colors.blue,
    ),
    home: const MyHomePage(title: 'Flutter Loading Button'),
    );
    }
    }

    class MyHomePage extends StatefulWidget {
    const MyHomePage({Key? key, required this.title}) : super(key: key);

    final String title;

    @override
    State<MyHomePage> createState() => _MyHomePageState();
    }

    class _MyHomePageState extends State<MyHomePage> {
    @override
    Widget build(BuildContext context) {
    onButtonPressed() async {
    await Future.delayed(const Duration(milliseconds: 3000), () => 42);

    // After [onPressed], it will trigger animation running backwards, from end to beginning
    return () {
    // Optional returns is returning a VoidCallback that will be called
    // after the animation is stopped at the beginning.
    // A best practice would be to do time-consuming task in [onPressed],
    // and do page navigation in the returned VoidCallback.
    // So that user won't missed out the reverse animation.
    };
    }

    return Scaffold(
    appBar: AppBar(
    title: Text(widget.title),
    ),
    body: Center(
    child: Column(
    mainAxisAlignment: MainAxisAlignment.center,
    children: <Widget>[
    const SizedBox(
    height: 15,
    ),
    const Text(
    'Elevated Button',
    ),
    const SizedBox(
    height: 5,
    ),
    EasyButton(
    idleStateWidget: const Text(
    'Elevated Button',
    style: TextStyle(
    color: Colors.white,
    ),
    ),
    loadingStateWidget: const CircularProgressIndicator(
    strokeWidth: 3.0,
    valueColor: AlwaysStoppedAnimation<Color>(
    Colors.white,
    ),
    ),
    useEqualLoadingStateWidgetDimension: true,
    useWidthAnimation: false,
    width: 150.0,
    height: 40.0,
    borderRadius: 4.0,
    elevation: 2.0,
    contentGap: 6.0,
    buttonColor: Colors.blueAccent,
    onPressed: onButtonPressed,
    ),
    const SizedBox(
    height: 15,
    ),
    const Text(
    'Elevated Button - Animated',
    ),
    const SizedBox(
    height: 5,
    ),
    EasyButton(
    idleStateWidget: const Text(
    'Elevated Button',
    style: TextStyle(
    color: Colors.white,
    ),
    ),
    loadingStateWidget: const CircularProgressIndicator(
    strokeWidth: 3.0,
    valueColor: AlwaysStoppedAnimation<Color>(
    Colors.white,
    ),
    ),
    useWidthAnimation: true,
    width: 150.0,
    height: 40.0,
    borderRadius: 4.0,
    contentGap: 6.0,
    buttonColor: Colors.blueAccent,
    onPressed: onButtonPressed,
    ),
    const SizedBox(
    height: 15,
    ),
    const Text(
    'Outlined Button',
    ),
    const SizedBox(
    height: 5,
    ),
    EasyButton(
    type: EasyButtonType.outlined,
    idleStateWidget: const Text(
    'Outlined Button',
    style: TextStyle(
    color: Colors.blueAccent,
    ),
    ),
    loadingStateWidget: const CircularProgressIndicator(
    strokeWidth: 3.0,
    valueColor: AlwaysStoppedAnimation<Color>(
    Colors.blueAccent,
    ),
    ),
    useEqualLoadingStateWidgetDimension: true,
    useWidthAnimation: false,
    width: 150.0,
    height: 40.0,
    borderRadius: 4.0,
    contentGap: 6.0,
    onPressed: onButtonPressed,
    ),
    const SizedBox(
    height: 15,
    ),
    const Text(
    'Outlined Button - Animated',
    ),
    const SizedBox(
    height: 5,
    ),
    EasyButton(
    type: EasyButtonType.outlined,
    idleStateWidget: const Text(
    'Outlined Button',
    style: TextStyle(
    color: Colors.blueAccent,
    ),
    ),
    loadingStateWidget: const CircularProgressIndicator(
    strokeWidth: 3.0,
    valueColor: AlwaysStoppedAnimation<Color>(
    Colors.blueAccent,
    ),
    ),
    useWidthAnimation: true,
    width: 150.0,
    height: 40.0,
    borderRadius: 4.0,
    contentGap: 6.0,
    onPressed: onButtonPressed,
    ),
    const SizedBox(
    height: 15,
    ),
    const Text(
    'Text Button',
    ),
    const SizedBox(
    height: 5,
    ),
    EasyButton(
    type: EasyButtonType.text,
    idleStateWidget: const Text(
    'Text Button',
    style: TextStyle(
    color: Colors.blueAccent,
    ),
    ),
    loadingStateWidget: const CircularProgressIndicator(
    strokeWidth: 3.0,
    valueColor: AlwaysStoppedAnimation<Color>(
    Colors.blueAccent,
    ),
    ),
    useEqualLoadingStateWidgetDimension: true,
    useWidthAnimation: false,
    width: 150.0,
    height: 28.0,
    borderRadius: 4.0,
    onPressed: onButtonPressed,
    ),
    const SizedBox(
    height: 15,
    ),
    const Text(
    'Elevated Button Fullwidth',
    ),
    const SizedBox(
    height: 5,
    ),
    EasyButton(
    idleStateWidget: const Text(
    'Elevated Button Fullwidth',
    style: TextStyle(
    color: Colors.white,
    ),
    ),
    loadingStateWidget: const CircularProgressIndicator(
    strokeWidth: 3.0,
    valueColor: AlwaysStoppedAnimation<Color>(
    Colors.white,
    ),
    ),
    useEqualLoadingStateWidgetDimension: true,
    useWidthAnimation: false,
    width: double.infinity,
    height: 40.0,
    contentGap: 6.0,
    buttonColor: Colors.blueAccent,
    onPressed: onButtonPressed,
    ),
    const SizedBox(
    height: 15,
    ),
    const Text(
    'Elevated Button Fullwidth - Animated',
    ),
    const SizedBox(
    height: 5,
    ),
    EasyButton(
    idleStateWidget: const Text(
    'Elevated Button Fullwidth',
    style: TextStyle(
    color: Colors.white,
    ),
    ),
    loadingStateWidget: const CircularProgressIndicator(
    strokeWidth: 3.0,
    valueColor: AlwaysStoppedAnimation<Color>(
    Colors.white,
    ),
    ),
    useWidthAnimation: true,
    width: double.infinity,
    height: 40.0,
    contentGap: 6.0,
    buttonColor: Colors.blueAccent,
    onPressed: onButtonPressed,
    ),
    ],
    ),
    ), // This trailing comma makes auto-formatting nicer for build methods.
    );
    }
    }

    /// end_example_part