React Native Animated with Hooks

No Comments

An important part of mobile applications is animations. Good animations first of all need to have a purpose. Then, they should be incorporated at the right time and in the right place. As a result, they create a great user experience. React Native Animated animation system can help us to create those animations.

 

screen with animated transition from view data to edit form

An example of the screen with animated transition from view data to edit form

 

React Native Animated API

The Animated API focuses on declarative relationships between inputs and outputs with configurable transformations in between and simple start/stop methods to control time-based animation execution.

It supports six types of components: View, Text, Image, ScrollView, FlatList, and SectionList.
Others can be created using Animated.createAnimatedComponent().

import { Animated } from "react-native";
<Animated.Image source... />
const AnimatedButton = Animated.createAnimatedComponent(TouchableOpacity);
<AnimatedButton ... />

React Native Animated API has two value types:

  • Animated.Value() for single values and
  • Animated.ValueXY() for vectors.

The animation system provides several animation types. One of them is Animated.timing() which supports animating a value over time.

We will make an element appearing over 500 ms. Initial opacity is zero, final opacity is 1.

const opacity = new Animated.Value(0);
Animated.timing(opacity, {
  toValue: 1,
  duration: 500
}).start();

Composing animations

There is an option to combine and play animations in sequence or parallel.

Animated.parallel([
  Animated.timing(opacity, {
    toValue: 1,
    duration: 500
  }),
  Animated.timing(position, {
    toValue: {x: 100, y: 100},
    duration: 500
  })
]).start();

.
It supports delay, if one animation should have a delay on the start.

Animated.delay(300);

or

Animated.timing(position, {
  toValue: {x: 100, y: 100},
  duration: 500,
  delay: 300
}).start();

Interpolation

An interpolation maps input ranges to output ranges.

We already have our opacity animated from 0 to 1. Let’s use that to move our element for 100 points at the same time.

const translateX = opacity.interpolate({
  inputRange: [0, 1],
  outputRange: [0, 100]
});

As opacity changes, translateX changes, too. We don’t have to use Animation.parallel because we have the same duration and animation type.

Native driver

By using the native driver we send everything about the animation to native before starting the animation. That way, native code will perform the animation on the UI thread without having to go through the bridge on every frame. If the JavaScript thread is blocked during the animation, it won’t affect the animation.

Only transform and opacity are supported by the native driver for now.

Animated.timing(opacity, {
  toValue: 1,
  duration: 500,
  useNativeDriver: true
}).start();

Hooks instead of Classes

Hooks are a new addition in React version 16.8. With hooks, we can use state and other React features without writing a class. We will show examples of animations by using hooks.

The hook we need in our example to perform side effects is the Effect hook.

function Animation() {
  const opacity = new Animated.Value(0);

  useEffect(() => {
    Animated.timing(opacity, {
      toValue: 1,
      duration: 500
    }).start();
  }, []);

  return (
    <Animated.Text style={{ opacity }}>Example text</Animated.Text>
  );
}

To make the Text component support animation (animated values), we need to use Animated.Text instead.

The second parameter in useEffect hook should contain an array with variables. React will skip applying an effect if those values haven’t changed between re-renders. An empty array means that effect will run only when the component mounts and unmounts.

If we want something to appear by button press and disappear by another press, we need to make some changes. For example, we have a screen with some data and when we press the Edit button, we want to show the Save button. We could do that with conditional rendering depending on state value. On the other hand, if we want to have animated transition, we need to use a different solution.

useRef

useRef returns a mutable ref object whose .current property is initialized to the passed argument (initialValue). The returned object will persist for the full lifetime of the component.

const opacity = useRef(new Animated.Value(0)).current;

const saveButtonOpacity = opacity.interpolate({
  inputRange: [0, 0.8, 1],
  outputRange: [0, 0, 1]
});

We want the Save button to appear after 80% of animation time. The button will disappear after 20% of the time from pressing the Save button because animation is going in the opposite direction.

useEffect(() => {
  Animated.timing(opacity, {
    toValue: isEditing ? 1 : 0,
    duration: 400,
    useNativeDriver: true
  }).start();
}, [isEditing]);

An effect should be applied when isEditing changes. That variable is added to the array as the second parameter in useEffect hook. We need to keep that value in the state. It’s different, comparing the way used in classes.

const [isEditing, setIsEditing] = useState(false);

Save button will change the opacity to 1 when we press the Edit button. Also we want to move it from outside into the screen.

const saveButtonTranslationX = opacity.interpolate({
  inputRange: [0, 1],
  outputRange: [100, 0]
});

While opacity goes from 0 to 1, x coordinate will go from 100 to 0. We need to set absolute position of our button on x = 100. After saving, the opposite will happen: the button will move to 100 again (outside of the screen).

<Animated.View
  style={{
    opacity: saveButtonOpacity,
    transform: [{ translateX: saveButtonTranslationX }]
  }}>
  <TouchableOpacity onPress={() => setIsEditing(false)}>
    <Text>Save</Text>
  </TouchableOpacity>
</Animated.View>

Slowed down animation for demonstration purpose

Slowed down animation for demonstration purpose

Conclusion

Animations bring user interfaces to life. The user interface needs to be intuitive and responsive. For example, a button should provide feedback on the press as if we are interacting with a real element on the screen.

Animations should always serve a purpose. It can be annoying if it is slowing down a process that would be faster without an animation. Animations should have a meaning and shouldn’t distract the user from completing actions.

Milan Susnjar

Software developer at codecentric since August 2015.
Member of the codecentric Digitization Labs team, an initiative within codecentric that focuses on digital prototyping. From the very first day, we implement our customers’ ideas and help validate them both internally and on the market. Any insights gained are immediately incorporated into the further development of the product – without tedious planning or detailed requirements.

Comment

Your email address will not be published. Required fields are marked *