Flutter Developer Bootcamp is now 70% OFF!

black friday sale!

0  comments

In a world where your app is competing with millions of others, it's important for it to stand out visually as much as functionally. Sprinkling some beautiful animations across your app can really enhance its appeal and the user’s overall experience.

SVGs are incredibly versatile, customizable, and can be animated inside of your apps for a unique effect. Rive, formerly known as Flare, is an animation software that can be used to easily create animated SVGs. The Rive Flutter package can then be used to seamlessly add the animations you create into your apps.

The Finished App

In this tutorial we are going to learn several things. First, we’ll get familiarized with the essentials of the Rive editor and learn how you can create your very own animations. We will then create a UI focused app where we will include several animations created with Rive using the Flutter Rive package

Our finished app will be a minimalistic representation of a music player UI. It will have a splash screen with an animated radio that will be briefly visible before the main page loads. The main page of the app will have music player controls that will display animations when they're tapped. The play/pause button will display an animation that uses a State Machine that allows you to display a specific animation based on certain conditions. This button also plays and pauses a sound wave animation that's located below the music player controls. 

Though it is simple, going through the development of this project will give you all the tools you need to start creating your very own animations and adding them to your Flutter apps. You will also have enough confidence to explore further and master all of the advanced functionalities that Rive has to offer.

Getting Started

Formerly, Rive was known as Flare which has its own Flutter package, Flare-Flutter. We will be working with the latest version of both the software and the package. This is important as there is a lot of material online about Flare-Flutter and not so much about the new Rive package which has a different syntax. On top of this, the old Flare software doesn’t allow any new sign-ups and new users are mandated to use Rive instead. So, wether you're new to Rive or want to switch from Flare, this tutorial will help you make a seamless entrance or transition. 

Flare and Rive produce completely different file formats. Rive produces riv and Flare produces flr2d files which are then converted to flr for use with Flutter. The pub.dev packages for each are configured to work with their respective file formats. So, be aware that you cannot use them interchangeably. If you have old flr2d files, you may be able to convert them to the new format, but there are some limitations.  Check out the documentation from Rive for details on how to do the conversion. 

We will be using a starter project in this tutorial which contains all of the Rive files we will use in the app. So, be sure to grab it from the link below if you would like to follow along.

The only package we'll be using here will be Rive, so let’s go ahead and add it as a dependency to our project now. At the time of writing this tutorial the current version is 0.7.28 and it is actively maintained and managed by the Rive team.

pubspec.yaml

dependencies:
  flutter:
    sdk: flutter
  rive: ^0.7.28

Starter Project Overview

The starter project for this tutorial is very simple. It contains only three files in the lib folder - main.dart, music_player_page.dart, and splash_screen.dart. The main.dart file contains anAppWidgetwhich is configured to display the SplashScreen. The SplashScreen is a stateful widget that just has a Scaffold with a custom background color. The music_player_page.dart holds a stateful MusicPlayerPage widget which contains a Scaffold with a centered Column. Inside the Column is an image of an album cover to go with our music player theme.

As you can tell, the starter project files are pretty bare-boned. That is because the majority of our project will consist of animated SVGs which we will add during the tutorial. Rive allows you to download files in the riv format from their editor. These files contain all the data you need to add the animations to your app. Even though we will go over the basics of creating your own animations, we also included some pre-made animations in the assets folder of the starter project. These files will be used to create the finished project.

Rive Editor Fundamentals

The goal of this tutorial is to provide enough knowledge for you to be able to create your own animations and then add them into your Flutter apps. That is why before we move on to coding, we'll learn some Rive editor fundamentals. So, if you don’t already have a Rive account, go to www.rive.app and create one now.

Dashboard

Once you’re logged in you will be taken to your account dashboard. Here you can see all of your files, create new files, and folders. Go ahead and create a new file.

All of the Rive features we'll be covering in this tutorial are free. There is also a paid subscription which gives you access to some more available and upcoming features. For example, if you have other people on your team who you want to collaborate on projects with, you can do that with the Rive paid subscription.

Editor Overview - Design Mode

When you create a new file, you will be prompted to create an artboard and define its dimensions. Go ahead and click “Create”, leaving the default dimensions. Now you should have your new file opened in the editor with a new 500x500 artboard. You can also change the artboard size inside the editor.

When you create a file, you will be in Design mode. You can switch between Design and Animate modes in the editor. In Design mode you can create your graphics, import SVGs created in software such as Adobe Illustrator and edit them to your liking.

In the top bar of the editor, you can click on the “+” symbol to do things like create shapes, paths, additional artboards, bones, and groups. Let’s add a star shape to our artboard with dimensions of 200x200. Select the star shape then click and drag on the artboard until the star is of the desired dimensions. You can also adjust the dimensions in the right sidebar.

When you click on the shape, you'll see a circle with two arrows pointing to the x and y axis. You can use the arrows and the box surrounding the star to scale and rotate the shape. If you're interested in precise editing, you may find it more useful to change these and other shape properties using the sidebar on the right. If you ever used software similar to Adobe Illustrator, the settings in the sidebar should look really familiar.

Make sure the star shape is selected and go to the sidebar to change the fill color for it. We are using the following color code FFC000 to make the star yellow. Let’s also add a black-colored stroke to the star with a thickness of 4. That's all of the designing we are going to do for this simple demonstration.

Editor Overview - Animate Mode

Now, let’s move over to the Animate mode. When you switch to animate mode, the layout of the editor will stay pretty much the same, except a timeline will appear at the bottom. In this mode you create one or more animations by changing different shape/layer properties and adding keyframes for the respective changes to the timeline.

Double-click on the name of the existing animation to the left of the timeline and rename it to “Rotation”. Name your animations thoughtfully, because these will be the same names you'll use to add the animations to your code.

By clicking on the time to the left of the timeline, you can change things like the duration and playback speed of your animation. We are going to leave everything here untouched for this demonstration. 

Creating Our First Animation - Rotation

Now, let’s make our star rotate 360°. Select the star, set the playhead to the beginning of the timeline, then making sure that the rotation is at click on the rhombus next to the rotation setting.

You just created your first keyframe that will represent the starting point for the rotation. Now, move the playhead over to the very end of the timeline, double click on the rotation number, change it to 360°, and press enter. You should now see another keyframe at the end of the timeline. Now when the animation plays it will rotate the star from 0° to 360°. To see this animation, bring the playhead back to the beginning of the timeline and click the play button.  

Right now the animation only runs once. To see it again, bring the playhead to the beginning of the timeline, and then hit play again. If you click on the right pointing arrow to the right of the play button, you can make the animation loop or ping pong (go back and forth in a loop). We are going to leave this as a one shot animation.

Creating Our Second Animation - Scale

Let’s add one more animation for the star to make it scale down and then back up. Click on the “+” symbol in the Animations panel to the left to the timeline and add an animation. Rename the animation to “Scale”.

Now, select your newly created animation, then select the star and make sure the playhead is at the beginning of the timeline. Click on the rhombus next to the X axis and the one next to the Y axis of the scale setting in the sidebar to set the initial keyframe to 100% scale.

Next, move the playhead over to 30f in the timeline and add a new keyframe with scale values set to 50% for the X and Y axis. Once you've done that move the playhead again to the last frame in the timeline and change the scale values back to 100%. Now play the animation and watch as the star shrinks and grows.

Productivity tip: If you are using values for future keyframes that you already used in existing keyframes, you can just copy the existing keyframe over to another frame. 

State Machines in Rive

We have now created two animations for our star shape. Next we'll get into State Machines in Rive. State Machines provide a way to visually connect your animations.The different animations you want to connect will be considered states. You can define conditions and then trigger transitions from one animation/state to another based on the given condition. This is really going to supercharge not only your animations but also the control you have over them inside your app. Let’s create our first State Machine and see what they are all about. Click on the “+” inside the Animations panel and select “State Machine”.

State Machines Overview

You should now see a State Machine inside the Animations panel. Double click on its name and rename it to “StarAnimation”. The names of your State Machines are important, because you will use those names to add them to your Flutter apps. 

When you look inside the State Machine panel (located where the timeline used to be), you will see three states - Entry, Any State and Exit.

Entry

Transitions from the Entry state define which animation should play first

Any State 

Transitions from Any State will play the connected animation regardless of which state is currently active as long as the conditions on the transition are met. 

Exit

Transitioning to the Exit state will exit the State Machine.

As you experiment with Rive and try creating your own animations, using these states and connecting your animations in the State Machine will become increasingly more intuitive.

Connecting Animations in a State Machine

Let’s create some simple transitions for our animations in the State Machine now.

First, click and drag the Scale and Rotation animations into the State Machine panel. Then drag and arrange the states in the following order - Entry > Rotation > Scale.

When you hover near an individual state state, you will see a small dot appear. Hover around the Entry state and when you see the dot click and drag towards the Rotation state. Then let go when you see the Rotation state is highlighted blue.

You now have a forward transition from the Entry to the Rotation state. Click on the play button inside the panel to see the transition in action. You can then hit the pause button to reset the State Machine.

Now add one more transition from Rotation to Scale. If you press play now you will only see the Scale animation. That is because for the transition from Rotation to Scale, the exit time is set to 0 milliseconds.

Click on the circle with the arrow inside of it between Rotation and Scale. Then enable the Exit Time setting to the right of the State Machine panel and set the time to 1000 milliseconds. This allows the Rotation animation to play in full before transitioning to the Scale one.

Inputs and Conditions

Look immediately to the left of the State Machine panel and find where it says “Inputs”. Now click on the “+” symbol to the right of where it says “Inputs” to add an input. You will see that you can choose a Number, a Boolean, or a Trigger.

Number Input

First let's add a number input and see how it works. Then, double click on the added input name and rename it to “Level”. Just like animations and State Machines, your input names should be thoughtfully created since they will be used inside your Flutter code.

We can now use this Level input to set conditions for our transitions. Select the transition from the Entry to the Rotation state. Then add a condition by clicking on the “+” symbol located next to the where it says “Conditions”, to the right of the State Machine panel. Then, select Level from the dropdown menu for the condition you just added and change the value from 0 to 2. Do the same thing for the transition from Rotation to Scale, but this time set the value to 4.

Now, set the animation to play and change the Level value in the Inputs panel to 2. When the value is changed to 2 you will see the Rotation animation play. Now change the value to 4 and you will see the Scale animation. Later in this tutorial we will learn how to change values for an input in your code to trigger different animation transitions. 

We won’t be using the Number input in our app. If you want to try out a practical usage for this type of input, though, you can try the following. If you have a slider in your app, you can create animations based on the changing value of the slider.

Boolean Input

Now, let’s try creating a Boolean input. Add a Boolean and rename it to “Switch”. This input is exactly what it sounds like, a boolean which can only have 2 possible values - true or false. This input is represented by a checkbox in the Inputs panel.

Select the Entry to Rotation transition, delete the Level condition on it, and add a Switch condition with the value set to true. With this condition the Rotation animation will only play when the Switch input has a value of true. Go ahead press play and check the box next to the word Switch in the Inputs panel to see the animation.

You can use the Boolean input to create things like animated toggles in your app. Though, really the possibilities can extend well beyond these simple examples, as far as your imagination allows. 

Trigger Input

The final input we are going to look at will be the Trigger input. Create a new Trigger and rename it to “Tap”. The Trigger at its core is a boolean, but the difference is that it automatically resets to false once it's set to true. So, it's kind of like a button. That's exactly the type of functionality we could use it for inside an app. In the Inputs panel, the Trigger is represented by a kind of radio button. When you click on it you can see the dot inside the circle, but then when you release your mouse button, it disappears. That is because the value automatically resets to false.

Let’s delete the Level condition we have on our Rotation to Scale transition and add the Tap as a new condition instead. Now, click play then check the Switch checkbox and then trigger the Tap input. You can now see that our animation transitions based on our defined conditions.

This concludes the Rive editor walkthrough. This by no means covers all of the features of this robust editor. However, you should now have enough knowledge to dissect the animations we pre-made for this tutorial as well as start creating your own basic animations.

Check out the Rive Help Center to get a lot more information on how to use the editor and advance your Rive animation and design skills. 

Creating a Splash Screen Animation

We now know our way around the editor and how Rive animations are structured. Finally, we can start adding animations to our project.

Head over to splash_screen.dart and add a centered Container with a width of 400 to the body parameter of the Scaffold. Make sure to import the Rive package in this file. As a child of the Container, we'll add the riv file from our assets folder by providing the path to that file to the RiveAnimation.asset constructor. We are going to use the radioSplashAnimation.riv file here. If you have your file stored online, you can also add it in using the RiveAnimation.network constructor.

splash_screen.dart


  Scaffold(
      backgroundColor: Colors.yellow[600],
      body: Center(
        child: Container(
          width: 400,
          child: RiveAnimation.asset(
            'assets/radioSplashAnimation.riv',
          ),
        ),
      ),
    );

Now run the app and you should see an animation of a dancing radio with floating musical notes appear on the screen. 

If you're curious, you can go to the Rive editor and see how this animation was created. Just create a new file from the dashboard of your Rive account and when the file is open drag the riv file into the editor. You can then fully inspect and edit it as you please.

This is the absolute simplest way you can add an animation into your project. This animation doesn’t require any control over it, doesn’t have changing states, and just keeps playing in a loop.

Before we move on to the next set of animations, let’s make this splash screen a little more realistic. Since we don’t actually have much to load in this app, let’s just delay the navigation to the MusicPlayerPage inside the initState of the _SplashScrenState. We want the animation to play for 3 seconds and then move onto the next page. This obviously is just for simulation purposes only.

splash_screen.dart


 @override
  void initState() {
    super.initState();
    Future.delayed(
      Duration(seconds: 3),
      () => Navigator.pushReplacement(
        context,
        MaterialPageRoute(
          builder: (_) => MusicPlayerPage(),
        ),
      ),
    );
  }

Now let’s move on to implementing all of the animated elements for the MusicPlayerPage

One Shot Animations - Change Track Buttons

When we head over to the music_player_page.dart file we only have a single image of an album cover there. We want to add several animated elements to create a mock music player interface. In this section we are going to add the buttons for going to the previous and next track. These buttons will play the animation only once when they're pressed. This type of animation is also known as a one shot animation. So, let’s get started! 

First, let’s add two controllers for our animations to the _MusicPlayerPageState. We need two controllers, because we will have two change track buttons. To initialize the controllers correctly we need to add two late variables of type RiveAnimationController for each controller. We are marking them as late, because we will be initializing them inside of initState. Also, as you are doing this don't forget to import the Rive package into the file.

music_player_page.dart


class _MusicPlayerPageState extends State<MusicPlayerPage> {
late RiveAnimationController _prevButtonController;
late RiveAnimationController _nextButtonController;

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

  _prevButtonController = OneShotAnimation(
    'onPrev',
    autoplay: false,
  );

  _nextButtonController = OneShotAnimation(
    'onNext',
    autoplay: false,
  );
}
...

For different types of animations, we need to use different types of controllers provided by the Rive package. Here we are using the OneShotAnimation class to create the controller. Even though we are using the OneShotAnimation class in the initialization, our variables are of type RiveAnimationController. That is because the OneShotAnimation class extends the SimpleAnimation class, which extends the RiveAnimationController class. 

Even though we aren’t doing it here, you can define onStop and onStart functions for this controller. They are executed when the animation stops or starts respectively. The ‘onPrev’ and ‘onNext’ are the names of the animations which we defined in the Rive editor. So, when we assign this controller to the widget created using the RiveAnimation.asset constructor, the controller will control the animation with the name you provide here. Lastly, we set the autoplay parameter to false, because we don’t want the animation to play when the page loads.

Now, head over to the Column in the widget tree and add a SizedBox and a Row below the Container. Then add two GestureDetector widgets with RiveAnimation.asset widgets as their children inside the Row. We will provide the corresponding file paths here for our buttons along with the controllers we just created.

music_player_page.dart


SizedBox(
  height: 60,
),
Row(
  mainAxisAlignment: MainAxisAlignment.center,
  children: [
    GestureDetector(
      onTapDown: (_) {},
      child: SizedBox(
        height: 115,
        width: 115,
        child: RiveAnimation.asset(
          'assets/PrevTrackButton.riv',
          controllers: [
            _prevButtonController,
          ],
        ),
      ),
    ),
    GestureDetector(
      onTapDown: (_) {},
      child: SizedBox(
        height: 115,
        width: 115,
        child: RiveAnimation.asset(
          'assets/NextTrackButton.riv',
          controllers: [
            _nextButtonController,
          ],
        ),
      ),
    ),
  ],
),

Next, we want to be able to control the animation when the GestureDetector widget is tapped. Let’s create a separate function for this.

music_player_page.dart


void _playTrackChangeAnimation(RiveAnimationController controller) {
  if (controller.isActive == false) {
    controller.isActive = true;
  }
}

When calling this function we need to pass a controller of type RiveAnimationController to it. In this function we check to see if the animation is currently not active(playing) and then if it isn’t we set the isActive property of the controller to true.

Now go ahead and pass this function to the onTapDown parameters of the two GestureDetector widgets we created earlier. Make sure to pass the in the corresponding controller variables to the _playTrackChangeAnimation function calls.

music_player_page.dart


...
GestureDetector(
  onTapDown: (_) =>
      _playTrackChangeAnimation(_prevButtonController),
  child: SizedBox(
    height: 115,
    width: 115,
    child: RiveAnimation.asset(
      'assets/PrevTrackButton.riv',
      controllers: [
        _prevButtonController,
      ],
    ),
  ),
),
GestureDetector(
  onTapDown: (_) =>
      _playTrackChangeAnimation(_nextButtonController),
  child: SizedBox(
    height: 115,
    width: 115,
    child: RiveAnimation.asset(
      'assets/NextTrackButton.riv',
      controllers: [
        _nextButtonController,
      ],
    ),
  ),
),
...

Go ahead, save your code, hot reload the app and try pressing on the buttons to see the animation. Next, we will be adding a more complicated animation element which relies on a State Machine.

State Machine Animation - Play/Pause Button

Understanding the Animation Configuration

When we learned about the Rive editor, we covered the essentials of State Machines. Now, we are going to add a State Machine animation into our app. If you look at the finished app video in the beginning of this tutorial, you can see that we have a play/pause button there. When the page loads, the button displays the play symbol. When the button is tapped, the play symbol rotates and fades out. Then the pause symbol fades in. When the button is tapped again after the previous transition completes, the pause symbol rotates and fades out. Then the play symbol fades back in. Every time we press the button, it goes back and forth between these two animations. This is because we set up our State Machine to do just that.

You can see here we have two animations - onPlay and onPause. We also have an isPlaying boolean input. Our animation starts off in the Entry state and when we set the isPlaying input to true, the onPlay animation runs. After that the animation will stay in the onPlay state. If we then set the isPlaying input to false, the transition to the onPause animation happens.

Let’s learn one new thing about State Machines. You may have already noticed that on the transition between onPause and onPlay there are two arrows. The back arrow pointing from the onPause to onPlay is what allows us to go back to the previous animation. So, if we made it to the onPause state in our animation lifecycle and from there we set the isPlaying back to true, it will play the onPlay animation. Then we can switch between the two animations/states. This is exactly the kind of effect we want in our app since we want to be able to tap the button to play and to pause.

Adding the State Machine Animation to the App

So, let’s finally add this animated play/pause button to our app. Adding a State Machine animation to the app takes quite a bit more code than the ones we implemented previously, so let’s do this step by step.

First, we need to get the riv file from our assets in form of ByteData using rootBundle.load. To use rootBundle we need to import package:flutter/services.dart. The load method returns a Future, so we'll use .then to register a callback to get the ByteData object. Then we will provide that data to RiveFile.import constructor. After that we’ll store this data inside of a final variable. We are going to do all of this inside of initState.

music_player_page.dart


@override
void initState() {
  super.initState();
...
  rootBundle.load('assets/PlayPauseButton.riv').then(
    (data) {
      final file = RiveFile.import(data);
    },
  );
}

Now we need to find the main artboard inside the RiveFile we just created and store that in another final variable.

music_player_page.dart


rootBundle.load('assets/PlayPauseButton.riv').then(
  (data) {
    final file = RiveFile.import(data);
    final artboard = file.mainArtboard;
  },
);

Next, we need to create a StateMachineController from the artboard. Here we need the name we gave to our State Machine for this animation inside the Rive editor. To create this controller, we need to provide the artboard variable and the name of our State Machine in a form of a String to the StateMachineController.fromArtboard constructor. We will then store this inside of a controller variable.

music_player_page.dart


rootBundle.load('assets/PlayPauseButton.riv').then(
  (data) {
    final file = RiveFile.import(data);
    final artboard = file.mainArtboard;
    var controller = StateMachineController.fromArtboard(
      artboard,
      'PlayPauseButton',
    );
  },
);

It's possible for the controller variable to equal null, for example, in a case where you provide an incorrect State Machine name. So, before we proceed to the next step, we need to check if the controller variable holding the StateMachineController is null. If it isn’t, we can proceed with the next steps.

music_player_page.dart


rootBundle.load('assets/PlayPauseButton.riv').then(
  (data) {
    final file = RiveFile.import(data);
    final artboard = file.mainArtboard;
    var controller = StateMachineController.fromArtboard(
      artboard,
      'PlayPauseButton',
    );
    if (controller != null) {
    }
  },
);

For the next step we need to create two variables outside of initState. One is going to be for the input we created in the Rive editor, which is of type bool. The other is going to be for the play/pause button artboard.

music_player_page.dart


SMIInput<bool>? _playButtonInput;
Artboard? _playButtonArtboard;

Now, go back to the if statement we recently added. In here we need to provide the StateMachineController we created earlier to the main artboard that we got from the rive file. Then we need to initialize our _playButtonInput variable with the input we created in the State Machine for this animation inside the Rive editor.

music_player_page.dart


...
    if (controller != null) {
      artboard.addController(controller);
      _playButtonInput = controller.findInput('isPlaying');
    }
  },
);

Our artboard variable in this function is now loaded with the State Machine controller, so we can use setState to initialize our _playButtonArtboard variable with it below the if statement. Here's how the entire function should look when we're done with it.

music_player_page.dart


rootBundle.load('assets/PlayPauseButton.riv').then(
  (data) {
    final file = RiveFile.import(data);
    final artboard = file.mainArtboard;
    var controller = StateMachineController.fromArtboard(
      artboard,
      'PlayPauseButton',
    );
    if (controller != null) {
      artboard.addController(controller);
      _playButtonInput = controller.findInput('isPlaying');
    }
    setState(() => _playButtonArtboard = artboard);
  },
);

Before we add the animated element to our page, we need to account for a particular error. When the page initially loads, the build method will run before the _playButtonArtboard variable is initialized. That will produce a “Null check operator used on a null value” error. To avoid this, add the following code between the SizedBox and Row widgets.

music_player_page.dart


...
SizedBox(
  height: 60,
),
_playButtonArtboard == null
    ? SizedBox()
    : Row(
...

Now, let's add another GestureDetector widget between the next and previous track gesture detectors. This time the child of the GestureDetector will be a Rive widget. We don’t pass the file path to this widget, but instead pass in our _playButtonArtboard variable to the artboard parameter. We also need to add the bang operator after it to tell Dart that we're sure this variable won’t be null. Since we just accounted for the scenario where it may be null, we're safe to do this.

music_player_page.dart


GestureDetector(
  onTapDown: (_) {},
  child: SizedBox(
    height: 125,
    width: 125,
    child: Rive(
      artboard: _playButtonArtboard!,
      fit: BoxFit.fitHeight,
    ),
  ),
),

Next we want to set up the logic for controlling this animation. Let’s create a separate function to do this.

music_player_page.dart


void _playPauseButtonAnimation() {
  if (_playButtonInput?.value == false &&
      _playButtonInput?.controller.isActive == false) {
    _playButtonInput?.value = true;
  } else if (_playButtonInput?.value == true &&
      _playButtonInput?.controller.isActive == false) {
    _playButtonInput?.value = false;
  }
}

Since the execution of this animation depends on our input conditions, we are using the _playButtonInput here. First, we are checking if the input value is false and making sure the animation is not currently active. If this equates to true then we will set the input value to true. This will play the onPlay animation. We check to make sure the animation isn't currently active because we don’t want to be able to play the animation again if it is currently playing.

If the logic we just discussed equates to false we'll do another check. If the input value is true and the animation isn't currently active we will set the input value to false. Setting the input to false will cause the onPause animation to play.

Now, add this function call to the onTapDown parameter of the GestureDetector widget for the play/pause button.

music_player_page.dart


...
GestureDetector(
  onTapDown: (_) => _playPauseButtonAnimation(),
  child: SizedBox(
    height: 125,
    width: 125,
    child: Rive(
      artboard: _playButtonArtboard!,
      fit: BoxFit.fitHeight,
    ),
  ),
),
...

Go ahead, save and hot reload the app. Now when you tap the play/pause button you can see this animation in action, switching between onPause and onPlay states.

Simple Animation - Sound Wave

Since our app doesn’t actually play music, we will instead add a sound wave animation that can be paused and played based on the status of the play/pause button. This will be a looping animation. First, we are going to add a late variable for the controller for this animation and then initialize it inside of initState using the SimpleAnimation class.

music_player_page.dart


class _MusicPlayerPageState extends State<MusicPlayerPage> {
...
late RiveAnimationController _soundWaveController;
...
@override
void initState() {
  super.initState();
...
  _soundWaveController = SimpleAnimation(
    'loopingAnimation',
    autoplay: false,
  );
...

The name of our animation as defined in our Rive file is ‘loopingAnimation’. On page load the player will be in a paused state, so we set autoplay to false here.

Now, head over to the end of our Column widget and add a SizedBox and a Container below the Row. We'll pass in RiveAnimation.asset as the child of the Container and essentially do the same thing we did for the track change buttons.

music_player_page.dart


...
SizedBox(
  height: 40,
),
Container(
  height: 100,
  width: 400,
  child: RiveAnimation.asset(
    'assets/SoundWave.riv',
    fit: BoxFit.contain,
    controllers: [_soundWaveController],
  ),
),
],

Now, let’s add the logic for toggling this looping animation on and off. We are going to add a new function to do this.

music_player_page.dart


void _toggleWaveAnimation() => setState(
    () => _soundWaveController.isActive = !_soundWaveController.isActive);

When this function runs it will use setState and toggle the isActive property of the _soundWaveController. Since we want this animation to play and pause when the play/pause button is tapped, we will call this function from the existing _PlayPauseButtonAnimation function.

music_player_page.dart


void _playPauseButtonAnimation() {
  if (_playButtonInput?.value == false &&
      _playButtonInput?.controller.isActive == false) {
    _playButtonInput?.value = true;
    _toggleWaveAnimation();
  } else if (_playButtonInput?.value == true &&
      _playButtonInput?.controller.isActive == false) {
    _playButtonInput?.value = false;
    _toggleWaveAnimation();
  }
}

Now, when we tap the play button, it will play or pause the sound wave animation. Save your work and test the app. You should now see everything we implemented in this tutorial in working order.

Conclusion

Congratulations, you officially completed this tutorial! Hopefully this encourages you to start adding beautiful and functional animations into your apps. You should now have enough knowledge to do just that and enough confidence to advance your newly acquired skills.

About the author 

Ashley Novik

Ashley is a Flutter developer and tutor at Reso Coder with a passion for tech and an infinite drive to learn and teach others 😄. On her days off she enjoys exploring nature and powering through off-road trails on her mountain bike 🚵‍♀️.

You may also like

{"email":"Email address invalid","url":"Website address invalid","required":"Required field missing"}
>