How to Animate the Items of a React Native FlatList

Author

Spencer Carli

Developer, cat dad, and devout pizza lover. Teaching at React Native School and building apps with Handlebar Labs.

0

I was recently asked about animating items in a React Native list — specifically animating the item when added and removed. That’s what we’ll cover today! To accomplish this we’ll use the FlatList component and the Animated library.

Full source code is available on GitHub. The starting point for the List Row is available in ListRow-start.js the finished code can be seen in ListRow-finished.js.

Starting Point

Right now, when pressing the “Add Person” button, we make a request to the Random User API to fetch a user. We then append that to the people array on the component state. Check out the video below to see where we start.

Animating Entry

To easily demo our animation we’ll animated to insertion of the list item. When a new item is added to the people array we’ll change the opacity of the row and, via transform, the scale and rotation.

It’s important to note that inside the TouchableOpacity we’re using an Animated.View which allows us to modify that component using animated values.

First we’ll create a new animated value and, when the component mounts, we’ll change that value from 0 to 1 using Animated.timing.

ListRow-start.js

const ANIMATION_DURATION = 250;

class ListRow extends Component {
  constructor(props) {
    super(props);

    this._animated = new Animated.Value(0);
  }

  componentDidMount() {
    Animated.timing(this._animated, {
      toValue: 1,
      duration: ANIMATION_DURATION,
    }).start();
  }

  ...
}

Then we need to drive our UI from these values. Inside the render function we use the animated value to adjust the opacity and scale (directly, because 0 to 1 is what we’re looking for) and the rotation. We interpolate the rotate property so that we can turn the 0 to 1 into useful data for that property.

ListRow-start.js

const rowStyles = [
  styles.row,
  { opacity: this._animated },
  {
    transform: [
      { scale: this._animated },
      {
        rotate: this._animated.interpolate({
          inputRange: [0, 1],
          outputRange: ['35deg', '0deg'],
          extrapolate: 'clamp',
        }),
      },
    ],
  },
];

This leaves us something like this (change the ANIMATION_DURATION variable to slow down or speed up the animation).

null

Animating Removal

If you look at the starter code you can see that we’ve got a function on our component called onRemove which calls this.props.onRemove(). Useless, right? Well it won’t be for long! What we want to do now is essentially undo everything we did in componentDidMount. We can’t do that in componentWillUnmout because the component will be gone before any animations can run!

ListRow-start.js

onRemove = () => {
  const { onRemove } = this.props;
  if (onRemove) {
    Animated.timing(this._animated, {
      toValue: 0,
      duration: ANIMATION_DURATION,
    }).start();
    onRemove();
  }
};

If you test this out though you’ll see that it doesn’t actually do anything — the row just disappears the same as before.

null

If you comment out the onRemove function you can see that the animation is happening.

What we can do is call the onRemove function when the animation completes. The .start() function accepts a callback which will be called when the animation completes.

ListRow-start.js

onRemove = () => {
  const { onRemove } = this.props;
  if (onRemove) {
    Animated.timing(this._animated, {
      toValue: 0,
      duration: ANIMATION_DURATION,
    }).start(() => onRemove());
  }
};
null

Better! Still not great though, if you remove an item between two other items then once the row is remove the next row jumps up into the new spot. That’s not as smoooth as we’re going for.

We’ll again use interpolation to adjust the height of the row. That way, as the element animates out (fade, scale, rotate) the row below it will start moving into place. That way when the element is actually removed from the array there isn’t a user-perceivable-jump.

ListRow-start.js

const rowStyles = [
  styles.row,
  { opacity: this._animated },
  {
    transform: [
      { scale: this._animated },
      {
        rotate: this._animated.interpolate({
          inputRange: [0, 1],
          outputRange: ['35deg', '0deg'],
          extrapolate: 'clamp',
        }),
      },
    ],
  },
  {
    // NEW CODE
    height: this._animated.interpolate({
      inputRange: [0, 1],
      outputRange: [0, ROW_HEIGHT],
      extrapolate: 'clamp',
    }),
  },
];

BOOM! We’re now left with some subtle, yet slick, animations for each row item for whenever they’re added or removed. Check out the video below for a demo of the final product. Play with the ANIMATION_DURATION so that you can fully see what’s going on.