In this tutorial, weβll create a beautiful and optimized image gallery in React Native with a fullscreen preview and a thumbnail navigation system. π
β¨ Features
- Smooth scrolling with horizontal pagination π
- Thumbnail navigation to jump to a specific image π
- Optimized performance using useCallback and useRef β‘
- Dynamic styling for the active thumbnail π¨
Let's dive into the implementation! πββοΈ
ποΈ Setting Up the Component
First, import the necessary modules and initialize the component. We'll use React Native's FlatList, Image, TouchableOpacity, and useWindowDimensions for a responsive design.
import {
FlatList,
Image,
StyleSheet,
TouchableOpacity,
useWindowDimensions,
View,
} from 'react-native';
import React, { useCallback, useRef, useState } from 'react';
const [activeIndex, setActiveIndex] = useState(0);
const topRef = useRef(null);
const thumbRef = useRef(null);
const {width, height} = useWindowDimensions();
const IMAGE_SIZE = 80;
const SPACING = 10;
π Core Implementation
π₯ Fullscreen Image Viewer
The FlatList will render the images in full screen, allowing users to swipe through them smoothly.
const fullScreenRenderItem = useCallback(
({ item }) => (
<View style={{ width, height }}>
<Image source={{ uri: item }} style={styles.fullScreenImage} />
</View>
),
[]
);
πΌοΈ Thumbnail Navigation
We'll create another FlatList below the fullscreen view to display thumbnails. Users can tap a thumbnail to navigate to the corresponding full-sized image.
const thumbnailContentRenderItem = useCallback(
({ item, index }) => {
const isActive = activeIndex === index;
return (
<TouchableOpacity onPress={() => scrollToActiveIndex(index)}>
<Image
source={{ uri: item }}
style={[styles.thumbnailImage(IMAGE_SIZE, SPACING, isActive)]}
/>
</TouchableOpacity>
);
},
[activeIndex]
);
π Syncing Thumbnails with Fullscreen View
To ensure smooth scrolling between thumbnails and full images, we handle the onMomentumScrollEnd event and calculate the active index dynamically.
const onMomentumScrollEnd = useCallback(
event => {
const newIndex = Math.round(event.nativeEvent.contentOffset.x / width);
scrollToActiveIndex(newIndex);
},
[width]
);
The scrollToActiveIndex function keeps both lists in sync:
const scrollToActiveIndex = index => {
setActiveIndex(index);
topRef.current?.scrollToOffset?.({ offset: index * width, animated: true });
const thumbnailOffset = index * (IMAGE_SIZE + SPACING) - width / 2 + IMAGE_SIZE / 2;
thumbRef.current?.scrollToOffset?.({ offset: Math.max(thumbnailOffset, 0), animated: true });
};
ποΈ Rendering the Component
Finally, we integrate both FlatList components into our main view:
return (
<View style={styles.container}>
<FlatList
ref={topRef}
data={metaData}
renderItem={fullScreenRenderItem}
keyExtractor={keyExtractor}
horizontal
showsHorizontalScrollIndicator={false}
pagingEnabled
onMomentumScrollEnd={onMomentumScrollEnd}
/>
<FlatList
ref={thumbRef}
data={metaData}
renderItem={thumbnailContentRenderItem}
keyExtractor={keyExtractor}
horizontal
contentContainerStyle={styles.thumbnailContainer}
showsHorizontalScrollIndicator={false}
style={styles.thumbnailList(IMAGE_SIZE)}
/>
</View>
);
π¨ Styling
To make everything look sleek, we define the styles:
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#000',
},
fullScreenImage: {
...StyleSheet.absoluteFillObject,
resizeMode: 'cover',
},
thumbnailImage: (IMAGE_SIZE, SPACING, isActive) => ({
width: IMAGE_SIZE,
height: IMAGE_SIZE,
borderRadius: 12,
marginRight: SPACING,
borderWidth: 2,
resizeMode: 'cover',
borderColor: isActive ? '#fff' : 'transparent',
}),
thumbnailContainer: {
paddingHorizontal: 10,
},
thumbnailList: IMAGE_SIZE => ({
position: 'absolute',
bottom: IMAGE_SIZE,
}),
});
π― Conclusion
With this implementation, we have created an interactive and optimized image gallery π· with smooth transitions and thumbnail navigation. π
This gallery is highly performant thanks to useCallback, useRef, and proper scroll synchronization. β‘ Try customizing it further by adding animations or gestures for an even better user experience! π
Happy coding! π»π₯
Top comments (0)