Waldo joins Tricentis, expanding mobile testing for higher-quality mobile apps – Learn more
App Development

Build advanced animations in React Native with the native Animated API

Llamdo
Llamdo
Build advanced animations in React Native with the native Animated API
December 10, 2019
12
min read

Prerequisites

To follow along in this post, here are a few prerequisites:

  • Working knowledge of React or React Native
  • Basic understanding of animation principles
  • Visual studio code installed
  • XCode or Android studio installed
  • Node 10 + Installed

What we’ll be building

To better understand these concepts and for a more practical and engaging experience, we’ll build an auto completing progress bar that fills up when a user answers some questions. This will be a mini app to help us demonstrate how the Animated API works with React Native and also touch on some very important animation features like –

  • Interpolation
  • Transformation
  • Timing
  • etc.

It is worthy to note that we will not be covering the styling for the animation examples we’ll build in this project. This will allow us focus on the more important aspect of this project i.e animations and functionality. However, all the styles will be available in the project repository for your use.

Project repository If you would like to quickly get hands-on with the code, here’s the project repository on Github

Getting started

Before we start, it is important that we explain how to set up this project from scratch for the benefit of beginners who would like to learn from this post without prior knowledge of React Native.

If you haven’t run a React Native project before or would like to be carried along, follow the steps below. Apologies to the experts, please skip to this section, we are doing this steps for the benefits of the newbies.

Setting up a React Native project

1. Install the React Native CLI tool using npm:

 
 
npm install -g react-native-cli

With the React native CLI installed globally, you can go ahead and run React Native commands to create and start new projects.

2. Create the animations project and start the server

 
 
react-native init sample

cd sample && npx react-native run-ios // Start the iOS simulator
// OR
cd sample && npx react-native run-android // Android the android emulator

If you have Xcode or Android Studio installed then the commands above will run the project and you should have the simulator show up on screen:

Open the sample project we just created in your Code editor and start editing.

Auto completing progress bar

This will be our first project. The idea is to render a number of questions to the user when they open the app, we animate out an answered question while also animating the next question into view. While they answer these questions, our progress bar will keep progressing until completion.

In the root directory of the sample project we created, create a new src/components/Progress.js file and define some sample questions in state.

 
 
import React from 'react'
import {
    View,
    Text} from 'react-native'
class Progress extends React.Component {
        state = {
            index: 0,
            questions: [
                "Are you a new React Native developer?",
                "Do you like React Native?",
                "Would you recomment it to a newbie?",
                "Is it fulfilling to work with?",
            ],
        };
    render() {
        return (
<View>
<Text> Questions coming up soon... </Text>
</View>
        )
    }
}
export default Progress;

Here, we have set up our Progress.js file where we’ll create the desired functionality. At the moment we have a state object with our sample questions and index. Next, let’s define the render() function to display our questions and the response buttons.

 
 
import {
   StyleSheet,
   Animated,
   TouchableOpacity
} from 'react-native'

//... previous code

   render() {
       const handleResponse = () => {
           // Animate the questions in and out
       }
       const { questions, index } = this.state;
       const question = questions[index]
       let nextQuestion;
       if (index < questions.length) {
           nextQuestion = questions[index + 1]
       }
       return (
<View style={styles.mainView}>
<View style={[StyleSheet.absoluteFill, styles.overlay_style]}>
<Animated.Text style={[styles.question_text_style]}>
                       {question}
</Animated.Text>
<Animated.Text style={[styles.question_text_style]}>
                       {nextQuestion}
</Animated.Text>
</View>
<View style={styles.progress}>
<Animated.View style={[styles.bar, animationStyleProgressBar]} />
</View>
<TouchableOpacity
                   style={styles.yes_choice}
                   onPress={handleResponse}
                   activeOpacity={.5}>
<Text style={{ color: "white" }}> YES </Text>
</TouchableOpacity>
<TouchableOpacity
                   style={styles.no_choice}
                   onPress={handleResponse}
                   activeOpacity={.5}>
<Text style={{ color: "white" }}> NO </Text>
</TouchableOpacity>
</View>
       )
   }

First we de-structured questions and index from the state object so we can use them subsequently as normal variables. Next, we define a condition to loop through our array of questions and display them in the order they were listed. The question variable holds the initial question at 0 index while the nextQuestion variables holds the subsequent questions in the array.

Next, we need to display the questions on screen. To do that, we wrapped the question  and nextQuestion variables with the native  Animated.Text element since we intend to animate the questions individually into the screen.

Finally we defined two response buttons to enable the users provide answers to the questions we’ve rendered above. At this point, if we run the app, we should get the below output:

As you can see, the question and the nextQuestion are jumbled up together on top of each other, making it unreadable. This is because we intend to only show one question at a time in exactly the same view. Let’s go ahead and define the animation styles for the questions.

First, we initialize our animation in state, for animating both the questions, and progress bar respectively:

 
 
state = {
  // ... previous code
 questionsAnimation: new Animated.Value(0),
  progressbarAnimation: new Animated.Value(0),
}

Interpolation
Next, we need define interpolation ranges for the questions. This is ideal for our situation since we want to map input to outputs (outgoing question and incoming questions). The interpolation values we will define here will be responsible for sliding out the initial question and then sliding in the next question when a user responds.

	
		import {Dimensions} from 'react-native' // new import
		render(){
		const {width} = Dimensions.get("window"); // get the width of the screen
				const InterpolateFirstQuestion = this.state.questionsAnimation.interpolate({
					inputRange: [0, 1],
				outputRange: [0, -width]
		});
		const InterpolateNextQuestion = this.state.questionsAnimation.interpolate({
			inputRange: [0, 1],
		outputRange: [width, 0]
		});
}
	

Here, we define the interpolation for the questions. The first question takes an inputRange of [0, 1] so that it is displayed on screen first while the rest sits off-screen. It has an outputRange of [0, -width] so it animates out of view to make room for the next question.

Same interpolation values will apply to the next questions except the outputRange value where it takes [width, 0] to animate in the next question that is currently sitting off-screen.

Transformation
Now let’s apply these interpolations to our questions. To do that, we need to define unique animation styles for our respective questions:

	
	render(){
     // .... previous code
       const animationStyleFirstQuestion = {
           transform: [{
               translateX: InterpolateFirstQuestion
           }]
       }
       const animationStyleNextQuestion = {
           transform: [{
               translateX: InterpolateNextQuestion
           }]
       }
 }
	

Since we are also animating the progress bar as our users tap on the questions, we’ll define unique interpolation and animation style for our progress bar:

 
 
const InterpolateProgressBar = this.state.progressbarAnimation.interpolate({
           inputRange: [0, questions.length],
           outputRange: ["0%", "100%"]
       })
       const animationStyleProgressBar = {
           width: InterpolateProgressBar
       }

Now we can use this styles in the Animated.Text elements that renders the questions to exert the animation effect on the questions. We also do the same to the view that renders the progress bar.

 
 
return (
<View style={styles.mainView}>
<View style={[StyleSheet.absoluteFill, styles.overlay_style]}>
<Animated.Text style={[styles.question_text_style, animationStyleFirstQuestion]}>
       {question}
</Animated.Text>
<Animated.Text style={[styles.question_text_style, animationStyleNextQuestion]}>
       {nextQuestion}
</Animated.Text>
</View>
<View style={styles.progress}>
<Animated.View style={[styles.bar, animationStyleProgressBar]} />
</View>
</View>
)

Notice that we passed an array into the style prop of the Animated.Text and Animated.View elements to accommodate the new animation styles we just created. Be sure to capture this on your end to avoid errors. Now when you run the app again, you should see the output below:

We now have only the first question showing up at a time. But what happens when a user answers this question by clicking the yes or no button? nothing happens. That is because we’ve not defined that functionality in our handleResponse() function yet.

 
 
 const handleResponse = () => {
           Animated.parallel([
               Animated.timing(this.state.progressbarAnimation, {
                   toValue: this.state.index + 1,
                   duration: 400
               }),
               Animated.timing(this.state.questionsAnimation, {
                   toValue: 1,
                   duration: 400,
               }),
           ]).start(() => {
               this.setState((state) => {
                   return {
                       index: state.index + 1
                   }
               }, () => {
                   this.state.questionsAnimation.setValue(0)
               })
           })
       }

Pay attention, the fact that we want to start two animation processes at the same means that we have to wrap both animations inside an Animated.parallel() callback to start both animations with one call.

Animated.timing()
Animated timing allows for us to define an animation that takes a certain amount of time. It takes the said animation as the first parameter and an object containing the animation configurations.

Here, we define the progressbar animation to animate to a value of this.state.index + 1 at a duration of 400milliseconds. This will ensure that the progress bar keeps receiving the animation effects for the length of the questions in our questions[] array. We also did the same for the questions animation to animate to a value of 1 over the same amount of time.

Finally we passed a callback function to the .start() function to update the state of the index variable (to ensure that we animate through all the questions in our array) and also reset the value of our questions animation. If we run the app again, we should get a more complete and engaging experience.

If you would like to dig a little deeper on React Native Animations with the native Animated API, Rissanawat Kaewsanmuang  did a great job here to explain how you can build a Medium-Style Clap Animation with React Native

Conclusion

In this post we’ve covered a number of React Native animation concepts using the native Animated API. For a more engaging and hands on approach, we’ve gone ahead to build an advanced animation example to give you a hands-on experience.

Hopefully you’ve learnt something here and you’re welcome to extend the examples to build some more awesome things.

One of the most interesting and rewarding things about working with React Native is the fact that it gives you access to a ton of native features that you would have otherwise only had access to if you were writing native code. One of such is the native Animated API that makes it possible to recreate native-like animations in our mobile apps.

According to the docs, the Animated library is designed to make animations fluid, powerful, and painless to build and maintain. It provides three types of animation types. Each animation type provides a particular animation curve that controls how your values animate from their initial value to the final value.

In this post we’ll look at how we can leverage the abilities of this library to create powerful animations in React Native.

Automated E2E tests for your mobile app

Creating tests in Waldo is as easy as using your app!
Learn more about our Automate product, or try our live testing tool Sessions today.

Reproduce, capture, and share bugs fast!

Waldo Sessions helps mobile teams reproduce bugs, while compiling detailed bug reports in real time.