ホームページ > 記事 > ウェブフロントエンド > Reanimated を使用した React Native での自動スクロール、無限ループ、ページネーションを備えたカスタマイズ可能なカルーセルの構築
React Native でカスタム カルーセルを作成することは、アプリケーションに視覚的なセンスと対話性を追加する優れた方法です。このブログでは、React Native の Animated ライブラリと Reanimated ライブラリを使用して、自動スクロール機能を含むカルーセルを構築する方法を検討します。また、アニメーション化されたドットとエレガントな画像遷移効果を備えたページネーション システムも実装します。
このチュートリアルでは、次の内容について説明します:
まず、カルーセルのコア ロジックを収容する CustomCarousel コンポーネントを作成します。主な要素は次のとおりです:
/* 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, }, });
ページネーション コンポーネントには、現在アクティブなスライドを示すドットが表示されます。各ドットの不透明度は、カルーセルの現在のインデックスに応じて変化します。
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 コンポーネントは、ページネーション システム内の個々のドットの外観を処理します。ドットがアクティブ (現在のインデックス) であるかどうかに基づいてスタイルを変更します。
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 コンポーネントは各カルーセル項目を表示します。 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 秒ごとに 1 つのスライドから次のスライドに自動的に移動します。ユーザーがドラッグしてカルーセルを操作すると、自動スクロールが一時停止します。
/* 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, }, });
このチュートリアルでは、React Native の FlatList と、スムーズなアニメーションと補間のための Reanimated を使用してカスタム カルーセルを構築しました。アニメーション化されたドットと自動スクロール機能を備えたページネーション システムを追加し、ユーザー インタラクションによって自動スクロール機能が一時停止および再開されるようにしました。
これらのコンポーネントを使用すると、カルーセルを拡張して、動的コンテンツ、クリック可能な項目、より洗練されたアニメーションなどの他の機能を組み込むことができます。 Reanimated による React Native の柔軟性により、最小限のパフォーマンス コストで高度にカスタマイズ可能なカルーセルが可能になり、視覚的に魅力的なモバイル アプリの作成に最適です。
プロジェクトでこれを自由に試して、デザインのニーズに合わせてスタイルと動作をカスタマイズしてください。
以上がReanimated を使用した React Native での自動スクロール、無限ループ、ページネーションを備えたカスタマイズ可能なカルーセルの構築の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。