Animations typically increase the visual appeal of an app or website and improves overall engagement of the users. According to a study by Forrester Research, websites with well-executed animations, experience an increase in user engagement by up to 400%. Engaging animations can capture users' attention and encourage them to interact more with the platform. However, there is a learning curve for developers to master animation, especially when working with more advanced animation tools and techniques.
Grasping Rive as a developer with no experience in animation can be relatively easy compared to other animation tools or frameworks. Rive (formerly known as Flare) is designed to be user-friendly and accessible to developers, even those with little or no prior animation experience. In this article, you will learn how to create simple stunning Rive animations with ease and manage them in your Flutter app.
Introduction To Rive🧙♂️
A Simple Interactive Login Animation🚀
Conclusion🏋️♀️
References🧶
Rive is a powerful and user-friendly animation tool and runtime engine that enables developers and designers to create stunning and interactive animations for various platforms, including mobile apps, web applications, and games.
Here are the key concepts:
We will go through the process of creating a simple login animation and exporting it to our Flutter app. We will use the StateMachine to manage the interactivity of this animation in the app. In the end, it should look like this 👇🏽
Follow the steps below to set up the element on the Rive artboard:
Next, we will bind our bones and weigh them. Binding ensures that when a bone moves, the corresponding parts of the character's surface move accordingly, creating the illusion of deformation. Weighting, also known as vertex weighting, involves assigning influence values (weights) to each vertex of the character's mesh based on its proximity to specific bones. We will navigate to the path of the shape we would be binding. For the neck, this is how we bind it to the neck bone.
After binding the bones, we set the vertices by assigning weights to them. Here, notice that we have put the last set of vertices on 50% because we want the two bones to have a 50% effect on them. You should use 50%, especially when the set vertices cover a section that affects the two bones. Now, we would do the same for the hair path. We will also change the left and right bones from one to two bones to help us achieve the flowy movement we want for the hair.
We would want to have a blinking effect in this animation, to achieve this, we will use the clip feature on the two eye shapes like this 👇🏽
Next, we will add head tracking using translation constraints to this element because we want to move the head during the animation. Since it is a 2d element, adding translation constraints will give it depth and some form of 3d effect. Select everything and group it. Now we have a single group.
Then, at the top left corner, select the group tool and create a group at the center of the head (at the nose area). On the toolbar on the right, change its style from group to target, name it ctrl_front, duplicate it, and name the duplicate ctrl_back.
For the target ctrl_back, select the constraints option from the toolbar on the right. Pick translation constraints from the list of constraints options available. Click on the icon before the selected constraint option to set its properties.
Set strength to -100 and set its target to ctrl front. Now when you move the ctrl front, the ctrl back moves in the opposite direction. It will help us set constraints for parts of the face that should move in an opposite direction, like the ears. It should look like this 👇🏽
Now we will set constraints for the rest of the face. We will also group the eyes (left and right) and ears (left and right) to help us manage them better. We will set constraints for the eyes like this 👇🏽
Group |
Constraint strength |
Origin position |
Target |
---|---|---|---|
glasses |
5% |
same as ctrl_front origin |
ctrl_front |
brows |
10% |
same as ctrl_front origin |
ctrl_front |
ears |
5% |
no need to set origin |
ctrl_back |
nose |
5% |
same as ctrl_front origin |
ctrl_front |
face |
5% |
same as ctrl_front origin |
ctrl_front |
We don’t need to set constraints for the lips.
This is how it looks after we finished adding all our constraints 👇🏽
💃🏽 🥳 Congratulations, we have successfully gotten our element ready for the kind of animation we want to achieve. Whew!!
On the toolbar at the right, click the Animate button to switch to the animation interface. We will create six animation timelines and tie everything up with a state machine. In the timeline, using what we have set up previously with bones and constraints, we can set keyframes to create the animation we want to achieve.
The first timeline animation is the idle animation. It will be the Idle state of the animation. We will use this when the animated element is not engaged.
For this idle animation, we will create an illusion of breathing, slight hair movement, and blinking. Using the neck bone, hair bones, and right/left eye elements, we will set the necessary keyframes in different poses, which means we can set the specific properties of the selected item on the points on the timeline. Considering the transition style from one keyframe to the next, we will choose the kind of interpolation we need. You can find it at the bottom towards the right of the Timeline section. The interpolation is either hold, linear, or curve, depending on how you want to move from one keyframe to the next. It will look like this 👇🏽
From the gif above, you can notice that on the different keyframes on the timeline, we have set different poses for the selected items. This transition from one keyframe to the other forms the animation. Using this same procedure, we will create the other five timelines. You can click here to see this animation and check out the different timelines in detail. It looks like this 👇🏽
We have come to the final part of this animation process. A state machine is a visual way to connect animation. Using the state machine, we can control which animation plays based on the input we set. We can mix or blend two or more timeline animations so that they play simultaneously. We must select the right kind of inputs in the state machine because this is what we will use to control the animation in the app.
In the state machine, we have three kinds of inputs:
On the Animation panel, click the plus button and create a State machine. We will name it Login State Machine. This name is important because that is what we will need to identify our state machine later in the code.
Follow the steps below to set up your state machine:
Now the complete animation in state machine will look like this 👇🏽
Check out the full animation and State machine here.
Congratulations 🥳, we have successfully animated our element and set it up with a state machine! However, before we export the rive file, we will change the background and the character’s shirt colors. It will look like this👇🏽
The background color is (#B581EB) and the character’s shirt color is (#BD08D7)
Here is the link to the animation to see everything in detail
We will use this animation on our Login page. Create a Flutter app project and add the Rive dependency to the pubspec.yaml
dependencies:
rive: ^0.11.12
Also, add the exported Rive file to your project assets. Now we can go ahead to create the UI based on our design. We aim to have the animation do the following:
We will first define some things before the Widget Build function.
///Login details
String emailCred = "[email protected]";
String passwordCred = "123456";
/// input form controller
FocusNode emailFocusNode = FocusNode();
TextEditingController emailCtr = TextEditingController();
FocusNode passwordFocusNode = FocusNode();
TextEditingController passwordCtr = TextEditingController();
/// rive controller and input values
StateMachineController? controller;
SMIInput<bool>? check;
SMIInput<double>? look;
SMIInput<bool>? success;
SMIInput<bool>? fail;
bool isLoading = false;
bool isError = false;
@override
void initState() {
emailFocusNode.addListener(emailFocus);
passwordFocusNode.addListener(passwordFocus);
super.initState();
}
@override
void dispose() {
emailFocusNode.removeListener(emailFocus);
passwordFocusNode.removeListener(passwordFocus);
super.dispose();
}
void emailFocus() {
check?.change(emailFocusNode.hasFocus);
}
void passwordFocus() {
check?.change(passwordFocusNode.hasFocus);
}
Here, we can note the following:
emailFocus
and passwordFocus
functions, the check input is changed based on the boolean FocusNode.hasFocus
initState
and dispose
functions, we see the Listeners are added and removed. The listeners are used to listen to focus change.
You can check out the code for UI and the rest of the code here. This piece of code shows how to add the RiveAsset:
SizedBox(
height: 250,
width: 250,
child: RiveAnimation.asset(
"assets/login_screen.riv",
fit: BoxFit.fitHeight,
stateMachines: const ["Login State Machine"],
onInit: (artboard) {
controller = StateMachineController.fromArtboard(
artboard,
"Login State Machine",
);
if (controller == null) return;
artboard.addController(controller!);
check = controller?.findInput("check");
look = controller?.findInput("look");
success = controller?.findInput("success");
fail = controller?.findInput("fail");
},
),
),
From the code above, we can note the following:
Here is the code for the login function:
void login()async{
//extract the text coming from the text fields
final email = emailCtr.text;
final password = passwordCtr.text;
//Set loading boolean to true and delay to give an illusion of loading
setState(() {
isLoading = true;
});
await Future.delayed(
const Duration(milliseconds: 2000),
);
// check if details entered is the same as the correct creditials defined
if (email == emailCred && password == passwordCred) {
//if correct trigger the success input and set error boolean to false
success?.change(true);
setState(() {
isError = false;
});
if(context.mounted){
// delay and navigate to home screen
await Future.delayed(
const Duration(seconds: 2),(){
Navigator.push(context,
MaterialPageRoute(builder: (context) =>const HomeScreen()));
});
}
} else {
// if details don't match defined credentials
// set error boolean to true and trigger the fail input
// set loading boolean to false
setState(() {
isError = true;
});
fail?.change(true);
}
setState(() {
isLoading = false;
});
}
Check out the complete code here.
By doing this, we have completed our Login animation code. Here is how everything looks:
Congratulations! We have completed this simple interactive login animation. Here is an overview of everything we were able to accomplish:
Following this tutorial step by step, you might face a few bottlenecks, but it will get easier with practice. You can reach me on Twitter or comment if you need help while following through with this tutorial.
Check out these video tutorials to get a better grasp of Rive animation
You can also check out the Rive channel for several video tutorials on Rive animations.
Also published here.