Custom Pull to Refresh Animations in React Native

Built with the Animated API

First off I’d like to make a shout out to Michael B. Myers Jr. whose pull to refresh concept I recently came across on Dribbble. Seeing this concept inspired me to try my luck at using the Animated API to recreate his design within a React Native application. You can access the full code here

I’ll admit, it’s a little on the rough side compared to Michael’s design but hopefully it does the job of illustrating how to utilise scroll position to drive animations in React Native.

Setting up the Views

To kick things off we need to build a component called PullToRefreshListView which is going to render a native ListView over the top of another view that will become our custom indicator. We will create this overlay through use of absolute positioning.

With this in place we have our ListView sitting over the top of our custom indicator and scrolling downward should reveal the text “Custom indicator goes here…”. At this point we are now ready to build our GearsIndicator component.

Seeing as we are using images of gears for this animation we will be utilising the Animated component, Animated.Image. After placing these images into a directory, we can add the Animated.Image components to the render method of our GearsIndicator component (if you are following along you can grab these images from the repo).

To simplify things for myself, I have hard coded the positions of the gears. To accomodate devices other than the iPhone 6, I recommend dynamically setting these values using the device height and width that you can pull from the Dimensions module.

Stage 1: Scroll Animation

This stage of the animation is going to be initiated when a user pulls down on the ListView. The first thing we need to do is pass an Animated.event into the onScroll method within the ListView. This will be responsible for mapping the contentOffset-y position of the ListView to an Animated.Value which we will be storing as scrollY in the components state.

With this, we have the basis for being able to drive Animations in our GearIndicator with scrolling events. Lets go ahead and create a handleRelease function which will on release, scroll to a position of contentOffset-y = -130 and after 2 seconds return to 0.

Furthering this, we only want the ListView to refresh when the content offset position has exceeded a contentOffset-y value of -140. To handle this we will add a listener to the scrollY value which will set the readyToRefresh state to true as soon as this value has been exceeded.

Seeing as these gears will be rotating in both clockwise and counter clockwise directions we will need to utilise the interpolate function that the Animated API provides us with in order to map scroll position to a value in degrees which we will be passing down to the GearsIndicator component as props.

Now that our GearsIndicator component is receiving these props we can go ahead and rotate the gear as the user scrolls down. We do this by adding a transform property to each of the gear images.

And hey, scrolling downward should now reveal the rotating gears which finishes off phase 1 of the animation 🎉.

Phase 2: Refreshing Animation

Once the ListView exceeds the threshold and is released, the refreshing state is set to true within the PullToRefreshListView component and passed down as props to the GearsIndicator component. We can now use this prop to initiate the second phase of the animation. This will involve translating 4 of the gears out of view and leaving the small gear in the middle rotating on its own until the setTimeout in the PullToRefreshListView component completes.

To set this up we need to add an animating flag to the component state and instantiate Animated.ValueXY values within the componentWillMount. The reason why we are using Animated.ValueXY is because we want these gears to translate both the X and Y positioning of the image.

We then create a function called triggerAnimation which is where all the magic happens. This function executes a set of Animated.timing functions in parallel, causing the four gears to disappear out of view over the period of 1 second.

Finally, we are using the componentWillReceiveProps lifecycle method to only call the trigger animation function when it receives the refreshing prop and is not currently animating.

The final step in this animation is setting the values of translateX and translateY transforms for each of the gears to the Animated.ValueXY values that we are animating. This looks like.

And with that in place our custom pull to refresh animation is finished. I hope this demo has been helpful. Please feel free to leave any feedback, hints or tips and of course I’m always looking for better solutions.