Как создать карусель с автоматической прокруткой, бесконечным циклом, разбивкой на страницы в React Native
Создание собственной карусели в React Native — это отличный способ сделать ваше приложение более привлекательным и интерактивным. В этом блоге мы подробно разберем, как создать карусель с функцией автоматической прокрутки, используя анимированные и реанимированные библиотеки React Native. Мы также добавим систему разбивки на страницы с анимированными точками и элегантным эффектом перехода изображений.
В этом руководстве мы рассмотрим все этапы создания карусели: от настройки пользовательского компонента до реализации плавной анимации и интерполяции с помощью Reanimated, а также добавления функции автоматической прокрутки, которая приостанавливается при взаимодействии пользователя. Мы также создадим систему разбивки на страницы с анимированными точечными индикаторами, которые будут обновляться в зависимости от текущего видимого элемента. В результате у вас будет горизонтальная карусель с анимированными переходами, автоматической прокруткой и точками разбивки на страницы, которые обеспечивают комфортный просмотр контента.
Настройка компонента Carousel (Карусель)
Сначала мы создадим компонент CustomCarousel
, который будет содержать основную логику нашей карусели. Основные элементы включают в себя:
Animated.FlatList
для отображения элементов.- Механизм автоматической прокрутки с использованием
setInterval
. scrollTo
иuseSharedValue
из Reanimated для анимации переходов.
/* eslint-disable react-native/no-inline-styles */
import React, { useEffect, useRef, useState } from 'react';
import { StyleSheet, View, useWindowDimensions } from 'react-native';
import Animated, {
scrollTo,
useAnimatedRef,
useAnimatedScrollHandler,
useDerivedValue,
useSharedValue,
} from 'react-native-reanimated';
import { hpx } from '../../helpers';
import Pagination from './Pagination';
import RenderItem from './RenderItem';
import { animals } from './constants';
const CustomCarousel = () => {
const x = useSharedValue(0);
const [data, setData] = useState(animals);
const { width } = useWindowDimensions();
const [currentIndex, setCurrentIndex] = useState(0);
const [paginationIndex, setPaginationIndex] = useState(0);
const ref = useAnimatedRef();
const [isAutoPlay, setIsAutoPlay] = useState(true);
const interval = useRef();
const offset = useSharedValue(0);
console.log('CURRENT_CAROUSEL_ITEM👉', paginationIndex);
const onViewableItemsChanged = ({ viewableItems }) => {
if (viewableItems[0].index !== undefined && viewableItems[0].index !== null) {
setCurrentIndex(viewableItems[0].index);
setPaginationIndex(viewableItems[0].index % animals.length);
}
};
const viewabilityConfig = {
itemVisiblePercentThreshold: 50,
};
const viewabilityConfigCallbackPairs = useRef([{ viewabilityConfig, onViewableItemsChanged }]);
const onScroll = useAnimatedScrollHandler({
onScroll: (e) => {
x.value = e.contentOffset.x;
},
onMomentumEnd: (e) => {
offset.value = e.contentOffset.x;
},
});
useDerivedValue(() => {
scrollTo(ref, offset.value, 0, true);
});
useEffect(() => {
if (isAutoPlay === true) {
interval.current = setInterval(() => {
offset.value += width;
}, 4000);
} else {
clearInterval(interval.current);
}
return () => {
clearInterval(interval.current);
};
}, [isAutoPlay, offset, width]);
return (
<View style={styles.container}>
<Animated.FlatList
ref={ref}
style={{ height: hpx(194), flexGrow: 0 }}
onScrollBeginDrag={() => {
setIsAutoPlay(false);
}}
onScrollEndDrag={() => {
setIsAutoPlay(true);
}}
onScroll={onScroll}
scrollEventThrottle={16}
horizontal
bounces={false}
pagingEnabled
showsHorizontalScrollIndicator={false}
viewabilityConfigCallbackPairs={viewabilityConfigCallbackPairs.current}
onEndReached={() => setData([...data, ...animals])}
onEndReachedThreshold={0.5}
data={data}
keyExtractor={(_, index) => `list_item${index}`}
renderItem={({ item, index }) => {
return <RenderItem item={item} index={index} x={x} />;
}}
/>
<Pagination paginationIndex={paginationIndex} />
</View>
);
};
export default CustomCarousel;
const styles = StyleSheet.create({
container: {
flex: 1,
},
buttonContainer: {
justifyContent: 'center',
alignItems: 'center',
flexDirection: 'row',
gap: 14,
},
});
Компонент Pagination
Компонент разбивки на страницы визуализирует точки, которые соответствуют слайдам карусели. Активная точка, соответствующая текущему слайду, выделяется более насыщенным цветом, в то время как остальные точки становятся полупрозрачными.
import React from 'react';
import { StyleSheet, View } from 'react-native';
import { hpx } from '../../helpers';
import Dot from './Dot';
import { animals } from './constants';
const Pagination = ({ paginationIndex }) => {
return (
<View style={styles.container}>
{animals.map((_, index) => {
return <Dot index={index} key={index} paginationIndex={paginationIndex} />;
})}
</View>
);
};
export default Pagination;
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
marginTop: hpx(16),
justifyContent: 'center',
alignItems: 'center',
},
});
Компонент Dot
Компонент Dot
отвечает за внешний вид каждой точки в системе разбивки на страницы. Он изменяет свой стиль в зависимости от того, активна ли точка (соответствует текущему индексу) или нет.
import React from 'react';
import { StyleSheet, View } from 'react-native';
import { Colors } from '../../assets';
import { hpx, wpx } from '../../helpers';
const Dot = ({ index, paginationIndex }) => {
return <View style={paginationIndex === index ? styles.dot : styles.dotOpacity} />;
};
export default Dot;
const styles = StyleSheet.create({
dot: {
backgroundColor: Colors.white,
height: hpx(3),
width: wpx(12),
marginHorizontal: 2,
borderRadius: 8,
},
dotOpacity: {
backgroundColor: Colors.white,
height: hpx(3),
width: wpx(12),
marginHorizontal: 2,
borderRadius: 8,
opacity: 0.5,
},
});
Компонент RenderItem
Компонент RenderItem
отвечает за отображение каждого элемента карусели. Он применяет функцию interpolate
из библиотеки Reanimated для плавной анимации прозрачности элементов во время прокрутки.
import React from 'react';
import { StyleSheet, useWindowDimensions, View } from 'react-native';
import Animated, { Extrapolation, interpolate, useAnimatedStyle } from 'react-native-reanimated';
import { hpx, nf, SCREEN_WIDTH, wpx } from '../../helpers/Scale';
const RenderItem = ({ item, index, x }) => {
const { width } = useWindowDimensions();
const animatedStyle = useAnimatedStyle(() => {
const opacityAnim = interpolate(
x.value,
[(index - 1) * width, index * width, (index + 1) * width],
[-0.3, 1, -0.3],
Extrapolation.CLAMP
);
return {
opacity: opacityAnim,
};
});
return (
<View style={{ width }}>
<Animated.Image
resizeMode="cover"
source={{ uri: item.image }}
style={[styles.titleImage, animatedStyle]}
/>
</View>
);
};
export default RenderItem;
const styles = StyleSheet.create({
titleImage: {
width: SCREEN_WIDTH - wpx(32), // adjust the width of the image and horizontal padding
height: hpx(194),
alignSelf: 'center',
borderRadius: nf(16),
},
});
Автоматическая прокрутка
Автоматическая прокрутка карусели реализована с помощью таймера setInterval. Эта функция обеспечивает плавное перемещение карусели с одного слайда на другой каждые 4 секунды. При взаимодействии пользователя с каруселью, например, путем перетаскивания, автоматическая прокрутка временно приостанавливается.
Файл Constant
export const animals = [
{ text: 'cat', image: 'https://i.imgur.com/CzXTtJV.jpg' },
{ text: 'dog', image: 'https://i.imgur.com/OB0y6MR.jpg' },
{ text: 'cheetah', image: 'https://farm2.staticflickr.com/1533/26541536141_41abe98db3_z_d.jpg' },
{ text: 'piano', image: 'https://farm4.staticflickr.com/3224/3081748027_0ee3d59fea_z_d.jpg' },
// { text: 'apple', image: 'https://farm8.staticflickr.com/7377/9359257263_81b080a039_z_d.jpg' },
// { text: 'flower', image: 'https://farm9.staticflickr.com/8295/8007075227_dc958c1fe6_z_d.jpg' },
// { text: 'mushroom', image: 'https://farm2.staticflickr.com/1449/24800673529_64272a66ec_z_d.jpg' },
// { text: 'coffee', image: 'https://farm4.staticflickr.com/3752/9684880330_9b4698f7cb_z_d.jpg' },
];
Вывод
Мы добавили систему разбивки на страницы с анимированными точками, функцию автоматической прокрутки и позаботились о том, чтобы взаимодействие пользователя приостанавливало и возобновляло автопрокрутку.
С помощью этих компонентов вы можете расширить карусель, добавив другие функции, такие как динамическое содержимое, кликабельные элементы и более сложную анимацию. Гибкость React Native с Reanimated позволяет создавать высоконастраиваемые карусели с минимальными затратами на производительность, что идеально подходит для создания визуально привлекательных мобильных приложений.
Не стесняйтесь попробовать это в своем проекте и настроить стили и поведение в соответствии с вашими дизайнерскими потребностями!