ホームページ  >  記事  >  ウェブフロントエンド  >  Reanimated を使用した React Native での自動スクロール、無限ループ、ページネーションを備えたカスタマイズ可能なカルーセルの構築

Reanimated を使用した React Native での自動スクロール、無限ループ、ページネーションを備えたカスタマイズ可能なカルーセルの構築

Patricia Arquette
Patricia Arquetteオリジナル
2024-10-23 06:21:29325ブラウズ

Building a Customisable Carousel with Auto-Scroll, Infinite Loop, Pagination in React Native using Reanimated

React Native でカスタム カルーセルを作成することは、アプリケーションに視覚的なセンスと対話性を追加する優れた方法です。このブログでは、React Native の Animated ライブラリと Reanimated ライブラリを使用して、自動スクロール機能を含むカルーセルを構築する方法を検討します。また、アニメーション化されたドットとエレガントな画像遷移効果を備えたページネーション システムも実装します。

概要

このチュートリアルでは、次の内容について説明します:

  • カスタム カルーセル コンポーネントをセットアップします。
  • スムーズなアニメーションと補間のために Reanimated を使用します。
  • 画像間を自動的に回転する自動スクロール機能。
  • アニメーション化されたドットインジケーターを使用した ページネーション システムを構築します。

構築するもの:

  • アニメーショントランジションを備えた水平スクロールカルーセル。
  • ユーザーがカルーセルを操作すると一時停止する自動スクロール。
  • 現在表示されているアイテムに基づいて更新されるページ分割ドット。

始めましょう!


1. カルーセルコンポーネントのセットアップ

まず、カルーセルのコア ロジックを収容する CustomCarousel コンポーネントを作成します。主な要素は次のとおりです:

  • アイテムをレンダリングするための Animated.FlatList。
  • setInterval を使用した自動スクロールメカニズム。
  • Reanimated のスクロールToとトランジションをアニメーション化するための useSharedValue。
/* 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,
  },
});

2. ページネーションコンポーネント

ページネーション コンポーネントには、現在アクティブなスライドを示すドットが表示されます。各ドットの不透明度は、カルーセルの現在のインデックスに応じて変化します。

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',
  },
});

3. ドットコンポーネント

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,
  },
});

4. RenderItem コンポーネント

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),
  },
});

5. 自動スクロール

自動スクロール機能は、setInterval を使用して実装されます。この方法では、カルーセルが 4 秒ごとに 1 つのスライドから次のスライドに自動的に移動します。ユーザーがドラッグしてカルーセルを操作すると、自動スクロールが一時停止します。

6. 定数ファイル

/* 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,
  },
});

7. 結論

このチュートリアルでは、React Native の FlatList と、スムーズなアニメーションと補間のための Reanimated を使用してカスタム カルーセルを構築しました。アニメーション化されたドットと自動スクロール機能を備えたページネーション システムを追加し、ユーザー インタラクションによって自動スクロール機能が一時停止および再開されるようにしました。

これらのコンポーネントを使用すると、カルーセルを拡張して、動的コンテンツ、クリック可能な項目、より洗練されたアニメーションなどの他の機能を組み込むことができます。 Reanimated による React Native の柔軟性により、最小限のパフォーマンス コストで高度にカスタマイズ可能なカルーセルが可能になり、視覚的に魅力的なモバイル アプリの作成に最適です。

プロジェクトでこれを自由に試して、デザインのニーズに合わせてスタイルと動作をカスタマイズしてください。

以上がReanimated を使用した React Native での自動スクロール、無限ループ、ページネーションを備えたカスタマイズ可能なカルーセルの構築の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。