Refreshing content seamlessly enhances user experience in mobile applications. In this blog, we'll explore how to implement an animated pull-to-refresh feature in React Native using react-native-reanimated
and Lottie animations. 🌟
📌 Key Features
✅ Smooth pull-down gesture for refreshing
✅ Animated loader using Lottie
✅ Optimized performance with Reanimated
✅ Works seamlessly with FlatList
🔧 Setting Up the Project
To get started, install the required dependencies:
npm install react-native-reanimated lottie-react-native react-native-safe-area-context
🏗️ Implementing the Pull-to-Refresh Component
import React, { useRef, useCallback, memo } from 'react';
import {
StyleSheet,
Text,
View,
Image,
PanResponder,
Dimensions,
StatusBar,
} from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import Animated, {
useAnimatedScrollHandler,
useAnimatedStyle,
useSharedValue,
withTiming,
} from 'react-native-reanimated';
import LottieView from 'lottie-react-native';
import data from './data';
const { width } = Dimensions.get('screen');
const AnimatedPullToRefresh = () => {
const scrollPosition = useSharedValue(0);
const insets = useSafeAreaInsets();
const pullDownPosition = useSharedValue(0);
const isReadyToRefresh = useSharedValue(false);
const isLoaderActive = useSharedValue(false);
const onRefresh = useCallback((done) => {
isLoaderActive.value = true;
setTimeout(() => {
isLoaderActive.value = false;
isReadyToRefresh.value = false;
done();
}, 5000);
}, []);
const onPanRelease = () => {
pullDownPosition.value = withTiming(isReadyToRefresh.value ? 120 : 0, {
duration: 180,
});
if (isReadyToRefresh.value) {
isReadyToRefresh.value = false;
onRefresh(() => {
pullDownPosition.value = withTiming(0, { duration: 180 });
});
}
};
const panResponderRef = useRef(
PanResponder.create({
onStartShouldSetPanResponderCapture: (_, gestureState) => {
return scrollPosition.value <= 0 && gestureState.dy > 0;
},
onMoveShouldSetPanResponderCapture: (_, gestureState) => {
return scrollPosition.value <= 0 && gestureState.dy > 0;
},
onPanResponderMove: (_, gestureState) => {
const maxPullDistance = 150;
pullDownPosition.value = Math.min(
maxPullDistance,
Math.max(0, gestureState.dy),
);
isReadyToRefresh.value = pullDownPosition.value >= maxPullDistance / 2;
},
onPanResponderRelease: onPanRelease,
onPanResponderTerminate: onPanRelease,
}),
);
const scrollHandler = useAnimatedScrollHandler({
onScroll: (event) => {
scrollPosition.value = event.contentOffset.y;
},
});
const pullDownStyle = useAnimatedStyle(() => ({
transform: [{ translateY: pullDownPosition.value }],
}));
const refreshContainerStyle = useAnimatedStyle(() => ({
height: pullDownPosition.value,
opacity: 1,
top: pullDownPosition.value - 200,
}));
const renderItem = useCallback(
({ item }) => (
{item.title}
{`${item.director} | ${item.year}`}
),
[],
);
return (
index.toString()}
ItemSeparatorComponent={() => (
)}
onScroll={scrollHandler}
numColumns={2}
showsVerticalScrollIndicator={false}
overScrollMode="never"
/>
);
};
export default memo(AnimatedPullToRefresh);
const styles = StyleSheet.create({
container: {
backgroundColor: '#111',
flex: 1,
},
pullDownStyles: {
backgroundColor: '#0A0A0A',
flex: 1,
paddingHorizontal: 5,
},
itemSeparatorStyle: {
margin: 6,
},
image: {
width: 200,
height: 300,
marginRight: 10,
borderRadius: 8,
},
loader: {
width,
height: 300,
},
loaderContainer: {
alignItems: 'center',
width,
position: 'absolute',
},
title: {
width: 180,
color: '#fff',
fontSize: 16,
fontWeight: '600',
marginTop: 15,
marginBottom: 5,
},
subTitle: {
width: 180,
color: '#888',
fontSize: 12,
fontWeight: '600',
marginBottom: 10,
},
});
🎯 Final Thoughts
This implementation provides a smooth and visually appealing pull-to-refresh experience in React Native. With Reanimated and Lottie, you can create highly interactive and engaging UI components. Try this out in your projects and elevate your app’s user experience! 🚀🔥
Let me know in the comments if you have any questions or suggestions! 💬😊