Building Animated Buttons in Flutter with Lottie
Tags: flutter, ui, tutorial, ux
Published: 2024-04-29 08:48:39

In mobile app development, user interface elements play a crucial role in enhancing the user experience. Companies like Apple, Spotify, and Airbnb pay a lot of attention to small details that elevate the user experience to another level.

One of those details is animations.

Subtle, slick, and polished animations create an unmatching user experience.

There are many ways to add animations to your Flutter app, but some are more easier to do then the other. You will get the mock bang for your buck by using:

* Navigation transition animations
    
* [animations](https://pub.dev/packages/animations) package
    
* Animated buttons
    

There are two libraries that can help you create smoothly animated buttons in Flutter: [Lottie](https://pub.dev/packages/lottie) and [Rive](https://pub.dev/packages/rive).

The focus of this tutorial will be to show you how to create an animated record video button using a Lottie animation.

Here’s what are are going to build:

![Animated Button in Flutter created with Lottie](https://cdn.hashnode.com/res/hashnode/image/upload/v1714335073935/1f3ffd12-7867-4fbc-b76b-b3815be43cdc.gif)

Let’s dive in!

## Animation and the key frames

First, we have to find the animation that we are going to use. The best place to find free Lottie animations is [LottieFiles](https://lottiefiles.com/).

For this tutorial, we are going to use this nice [Record/Stop Button](https://lottiefiles.com/animations/recordstop-button-kvbEGqpwel) animation by Chris Gannon.

What I really like about LottieFiles is that it features an editor when you create an account. Inside this editor, you can play the animation, change it, and play around with it.

When open the editor, it will look something like this:

![LottieFiles Editor](https://cdn.hashnode.com/res/hashnode/image/upload/v1714335048775/4aa3df60-6a37-406f-a54c-352b61ed3566.png)

The part we are interested in is this bottom bar in the purple outline. Here you can control the playing of the video and see the frame counter, which is the most important information for controlling the animation.

When you play the animation a couple of times you can see there are 3 parts to it:

1. Intro (frames 0-60)
    
2. Start playing (frames 60-180)
    
3. Stop playing (frame 180-241)
    

We can use the starting times of the animation to control at which moments the animation needs to be started, stopped, or reset.

Let’s get to coding.

## Coding

To start off, we need to add Lottie to the `pubspec.yaml`:

```yaml
dependencies:
	lottie: ^3.1.0
```

We will also need two animation checkpoints:

```dart
const _introAnimationEnd = 60 / 240;
const _lastStopIconAnimationEnd = 180 / 240;
```

Let’s define our button:

```dart
class RecordButton extends StatefulWidget {
  const RecordButton({
    Key? key,
    required this.onStartRecording,
    required this.onStopRecording,
    this.size = 200,
  }) : super(key: key);

  final VoidCallback? onStartRecording;
  final VoidCallback? onStopRecording;
  final double size;

  @override
  State<RecordButton> createState() => _RecordButtonState();
}

class _RecordButtonState extends State<RecordButton> with SingleTickerProviderStateMixin {
  bool isRecording = false;
  late final AnimationController _controller;

  @override
  void initState() {
    _controller = AnimationController(vsync: this);
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
   return Placeholder();
  }

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

The button has 2 callbacks, `onStartRecording` and `onStopRecording` and an optional size property to change the size of the button. It needs to be a `StatefulWidget` to support the `AnimationController`.

The button has 3 states:

* initial state
    
* ready to record state
    
* recording state
    

Each of these states corresponds to one of the animation parts mentioned previously.

First, the button enters the initial state in the `onLoaded()` method of the Lottie widget and plays the intro animation using the controller:

```dart
Lottie.network(
        "<https://assets.codepen.io/35984/record_button.json>",
        controller: _controller,
        onLoaded: (composition) {
          _controller.duration = composition.duration;
          _controller.animateTo(_introAnimationEnd);
        },
```

The button is now ready to start recording. When the user presses the button it will enter the recording state and play the record state animation. During the recording state, the user can press the button again to stop the recording and play the animation to the end.

```dart
return GestureDetector(
      onTap: () {
        setState(() {
          isRecording = !isRecording;
        });
        if (isRecording) {
          widget.onStartRecording?.call();
          _controller.animateTo(_lastStopIconAnimationEnd);
        } else {
          widget.onStopRecording?.call();
          _controller.animateTo(1);
        }
      },
```

There is one last case we need to take care of. When the user presses the button to stop the recording, and animation will go to the end. The next press would start the recording, but the animation would not play because it was already at the end. To fix this, we need to add the listener to reset the animation to the correct frame (`_introAnimationEnd`) in the `onLoaded()` method:

```dart
 onLoaded: (composition) {
          _controller.addListener(() {
            if (_controller.value == 1) {
              _controller.value = _introAnimationEnd;
            }
          });
        },
```

The final result looks like this:

```dart
const _introAnimationEnd = 60 / 240;
const _lastStopIconAnimationEnd = 180 / 240;

class RecordButton extends StatefulWidget {
  const RecordButton({
    Key? key,
    required this.onStartRecording,
    required this.onStopRecording,
    this.size = 200,
  }) : super(key: key);

  final VoidCallback? onStartRecording;
  final VoidCallback? onStopRecording;
  final double size;

  @override
  State<RecordButton> createState() => _RecordButtonState();
}

class _RecordButtonState extends State<RecordButton> with SingleTickerProviderStateMixin {
  bool isRecording = false;
  late final AnimationController _controller;

  @override
  void initState() {
    _controller = AnimationController(vsync: this);
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        setState(() {
          isRecording = !isRecording;
        });
        if (isRecording) {
          widget.onStartRecording?.call();
          _controller.animateTo(_lastStopIconAnimationEnd);
        } else {
          widget.onStopRecording?.call();
          _controller.animateTo(1);
        }
      },
      child: Lottie.network(
        "<https://assets.codepen.io/35984/record_button.json>",
        controller: _controller,
        width: widget.size,
        height: widget.size,
        fit: BoxFit.fill,
        repeat: false,
        onLoaded: (composition) {
          _controller.duration = composition.duration;
          _controller.animateTo(_introAnimationEnd);
          _controller.addListener(() {
            if (_controller.value == 1) {
              _controller.value = _introAnimationEnd;
            }
          });
        },
      ),
    );
  }

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

You can also find the full code as a [gist](https://gist.github.com/dinko7/a200b7ffab188c124878889f616de247) on my Github.

## Conclusion

In this tutorial, you have learned how to find a nice Lottie animation, determine the key frames, and create a button that uses it. Hopefully, you can use this to bring your user experience to the next level with some slick animations.

If you have found this useful, make sure to like and follow for more content like this. To know when the new articles are coming out, follow me on [**Twitter**](https://twitter.com/dinkomarinac) or [**LinkedIn**](https://www.linkedin.com/in/dinko-marinac/).

Until next time, happy coding!

Reposted from [my blog](https://dinkomarinac.dev/building-animated-buttons-in-flutter-with-lottie).