ReactNative【实战】轮播图(含组件封装 ImageSlider)
最终效果
点击可全屏预览图片
核心组件 ImageSlider
components/slidePager/index.tsx
import AntDesign from "@expo/vector-icons/AntDesign";
import React, { useCallback, useEffect, useRef, useState } from "react";
import {Animated,Dimensions,Image,ImageSourcePropType,LayoutAnimation,Modal,Platform,StyleSheet,TouchableOpacity,View,
} from "react-native";
import { PropsTypes } from "./PropsTypes";
import { Indicator } from "./indicator";
import { styles } from "./style";
const { width } = Dimensions.get("screen");
const Os = Platform.OS;
export type DataType = { img: ImageSourcePropType };
export const ImageSlider = ({data = [],previewImageContainerStyle = {},previewImageStyle = {},caroselImageStyle = {},caroselImageContainerStyle = {},timer = 2000,autoPlay = false,showIndicator = true,activeIndicatorStyle = {},inActiveIndicatorStyle = {},indicatorContainerStyle = {},onItemChanged = (itemData) => {},localImg = false,onClick = (item: DataType, index: number) => {},preview = true,children,blurRadius = 50,
}: PropsTypes) => {const scrollX = React.useRef(new Animated.Value(0)).current;const imageW = width * 0.7;const [selectedIndex, setSelectedIndex] = useState(0);const [imageViewer, setImageViewer] = useState(false);const [currentIndex, setCurrentIndex] = useState(0);const slider = useRef(null);const timerRef = useRef<any>(null);const onViewRef = React.useRef(({ viewableItems }: any) => {// Use viewable items in state or as intendedif (viewableItems.length > 0) {let index = viewableItems[0].index;onItemChanged(viewableItems[0].item);setSelectedIndex(index);}});const viewConfigRef = React.useRef({ viewAreaCoveragePercentThreshold: 50 });useEffect(() => {if (autoPlay) {if (data.length > 0) startAutoPlay(imageViewer ? true : false);}}, []);useEffect(() => {if (!imageViewer) {if (autoPlay) {if (data.length > 0) startAutoPlay(imageViewer ? true : false);}} else {clearTimeout(timerRef?.current);}}, [currentIndex, imageViewer]);const switchViewer = useCallback(() => {LayoutAnimation.easeInEaseOut();setImageViewer(!imageViewer);}, [imageViewer]);const changeSliderListIndex = () => {if (slider.current) {if (currentIndex == data.length - 1) {setCurrentIndex(0);// @ts-ignoreslider.current.scrollToIndex({index: currentIndex,animated: true,});} else {setCurrentIndex(currentIndex + 1);// @ts-ignoreslider.current.scrollToIndex({index: currentIndex,animated: true,});}}};const startAutoPlay = (isViewer: boolean) => {if (!imageViewer) {((viewer) => {let viewBool = viewer;timerRef.current = setTimeout(() => {if (!viewBool) {changeSliderListIndex();}}, timer);})(isViewer);}};const previewImage = () => {return (<Modalvisible={imageViewer}onDismiss={switchViewer}onRequestClose={switchViewer}><View style={StyleSheet.absoluteFillObject}>{data.map((val, ind) => {const inputRange = [(ind - 1) * width,ind * width,(ind + 1) * width,];const opacity = scrollX.interpolate({inputRange,outputRange: [0, 1, 0],});return (<Animated.Imagekey={`image-${ind}`}// @ts-ignoresource={localImg ? val.img : { uri: val.img }}style={[StyleSheet.absoluteFillObject, { opacity }]}blurRadius={blurRadius}/>);})}</View><Animated.FlatListdata={data}keyExtractor={(_, index) => index.toString()}onScroll={Animated.event([{ nativeEvent: { contentOffset: { x: scrollX } } }],{ useNativeDriver: true })}horizontalpagingEnabledinitialScrollIndex={selectedIndex}pinchGestureEnabled={true}onScrollToIndexFailed={(info) => {const wait = new Promise((resolve) => setTimeout(resolve, 500));wait.then(() => {// @ts-ignoreslider.current?.scrollToIndex({index: info.index,animated: true,});});}}showsHorizontalScrollIndicator={false}renderItem={({ item, index }) => {return (<Viewstyle={[styles.previewImageContainerStyle,previewImageContainerStyle,]}><Image// @ts-ignoresource={localImg ? item.img : { uri: item.img }}style={[styles.previewImageStyle, previewImageStyle]}/><TouchableOpacityonPress={() => {setSelectedIndex(index);switchViewer();}}style={{position: "absolute",top: Os == "ios" ? 30 : 12,left: 12,}}><AntDesign name="close" size={24} color="white" /></TouchableOpacity></View>);}}/></Modal>);};return (<View>{imageViewer && previewImage()}<Animated.FlatListref={slider}data={data}keyExtractor={(_, index) => index.toString()}onScroll={Animated.event([{ nativeEvent: { contentOffset: { x: scrollX } } }],{ useNativeDriver: true })}horizontalpagingEnabledsnapToInterval={width}decelerationRate="fast"pinchGestureEnabled={true}showsHorizontalScrollIndicator={false}onViewableItemsChanged={onViewRef.current}viewabilityConfig={viewConfigRef.current}initialScrollIndex={selectedIndex}onScrollToIndexFailed={(info) => {const wait = new Promise((resolve) => setTimeout(resolve, 500));wait.then(() => {// flatList.current?.scrollToIndex({ index: info.index, animated: true });});}}renderItem={({ item, index }) => {return (<View style={[caroselImageContainerStyle]}><><TouchableOpacityactiveOpacity={0.9}onPress={() => {if (!preview) {onClick(item, index);} else {setSelectedIndex(index);switchViewer();}}}><Image// @ts-ignoresource={localImg ? item.img : { uri: item.img }}style={[styles.caroselImageStyle, caroselImageStyle]}/></TouchableOpacity>{children}</></View>);}}/><Viewstyle={{width: "100%",position: "absolute",alignSelf: "center",backgroundColor: "red",bottom: 20,}}>{showIndicator && (<Indicatordata={data}currenIndex={selectedIndex}indicatorContainerStyle={indicatorContainerStyle}activeIndicatorStyle={activeIndicatorStyle}inActiveIndicatorStyle={inActiveIndicatorStyle}/>)}</View></View>);
};
components/slidePager/indicator.tsx
import React from 'react';
import {StyleSheet, View, ViewStyle} from 'react-native';
import {DataType} from '.';export const Indicator = ({data = [],currenIndex = 0,activeIndicatorStyle = {},inActiveIndicatorStyle = {},indicatorContainerStyle = {},
}: {data: DataType[];currenIndex: number;activeIndicatorStyle: ViewStyle;inActiveIndicatorStyle: ViewStyle;indicatorContainerStyle: ViewStyle;
}) => {return (<View style={[styles.main, indicatorContainerStyle]}>{data.map((value, index) => {if (index == currenIndex) {return (<Viewkey={index}style={[styles.activeIndicatorStyle,activeIndicatorStyle,]}/>);} else {return (<Viewkey={index}style={[styles.inActiveIndicatorStyle,inActiveIndicatorStyle,]}/>);}})}</View>);
};const styles = StyleSheet.create({main: {flexDirection: 'row',alignItems: 'center',alignSelf: 'center',justifyContent: 'center',position: 'absolute',},activeIndicatorStyle: {height: 5,width: 20,borderRadius: 100,backgroundColor: '#62b5f0',margin: 5,},inActiveIndicatorStyle: {height: 10,width: 10,borderRadius: 100,backgroundColor: 'rgb(223, 231, 245)',margin: 5,},
});
components/slidePager/PropsTypes.ts
import { ReactNode } from "react";
import { ImageSourcePropType, ImageStyle, ViewStyle } from 'react-native';export interface DataType { img: ImageSourcePropType }export interface PropsTypes {/*** Set array of images path- these paths can contain http url link or local images path using require('./pathOfImage')*/data: DataType[];/*** its define whats type image urls you provide if its true it means you provide local images path* @default: false*/localImg?: boolean;/*** if its false its not show preview now its click listner of onClick function props* @default: false*/preview?: boolean/*** if its true its shows a header on slider* @default false*/showHeader?: boolean;/*** for displaying right component in header* @default null*/children?: React.ReactNode | React.ReactNode[];/*** for displaying right component in header* @default null*/headerRightComponent?: React.ReactNode;/*** for displaying left component in header* @default null*/headerLeftComponent?: React.ReactNode;/*** for displaying center component in header* @default null*/headerCenterComponent?: ReactNode;/*** for change style of header* @default {}*/headerStyle?: ViewStyle;/*** for change style of previewImageContainer* @default {}*/previewImageContainerStyle?: ViewStyle;/*** for change style of previewImage* @default {}*/previewImageStyle?: ImageStyle;/*** for change style of caroselImageContainerStyle* @default {}*/caroselImageContainerStyle?: ViewStyle;/*** for change style of caroselImageStyle* @default {}*/caroselImageStyle?: ImageStyle;/*** for auto scrolling* @default false*/autoPlay?: boolean;/*** timeinterval for changing slider* @default 2000*/timer?: number;/*** for Showing indicator* @default false*/showIndicator?: boolean;/*** for change style of activeIndicator* @default {}*/activeIndicatorStyle?: ViewStyle;/*** for change style of inActiveIndicatorStyle* @default {}*/inActiveIndicatorStyle?: ViewStyle;/*** for change style of indicatorContainerStyle* @default {}*/indicatorContainerStyle?: ViewStyle;/*** when item changed its give item data in parameter* @default (itemData) => {}*/onItemChanged?: (itemData: DataType) => void;/*** when click on any item its give item data in parameter and when onClick Present so slider not show IMAGE PREVIEW on Click* @default (item, index) => {},*/onClick?: (item: DataType, index: number) => void;/*** Image Preview cross icon color* @default #000*/closeIconColor?: string;/*** Image Preview Background Blur Radius* @default 50*/blurRadius?: number;
}
components/slidePager/style.tsx
import {StyleSheet, Dimensions} from 'react-native';const { width, height } = Dimensions.get('screen');
export const styles = StyleSheet.create({caroselImageStyle: {width: width,resizeMode: 'contain',height: 300,},previewImageContainerStyle: {width,justifyContent: 'center',alignItems: 'center',},previewImageStyle: {width: width - 32,resizeMode: 'contain',height: height - 72,},
});
页面使用
import { ImageSlider } from "@/components/slidePager";
<ImageSliderdata={data}autoPlay={false}closeIconColor="white"caroselImageStyle={{ height }}indicatorContainerStyle={{ bottom: -40 }}activeIndicatorStyle={styles.activeDot}inActiveIndicatorStyle={styles.inActiveDot}/>
const data: any[] = detail.images.map((i) => {return { img: i };});
images: ["https://img0.baidu.com/it/u=1168912228,741609775&fm=253&fmt=auto&app=120&f=JPEG?w=1422&h=800","https://gips3.baidu.com/it/u=1014935733,598223672&fm=3074&app=3074&f=PNG?w=1440&h=2560","https://img0.baidu.com/it/u=2779316202,721068756&fm=253&app=120&f=JPEG?w=1422&h=800",],
高度随第一张图片动态变化
const [height, setHeight] = useState<number>(300);
useEffect(() => {if (!detail?.images) {return;}const firstImg = detail?.images[0];Image.getSize(firstImg, (width: number, height: number) => {const showHeight = (SCREEN_WIDTH * height) / width;setHeight(showHeight);});// eslint-disable-next-line react-hooks/exhaustive-deps}, []);
指示点的样式
activeDot: {width: 6,height: 6,backgroundColor: "#ff2442",borderRadius: 3,},inActiveDot: {width: 6,height: 6,backgroundColor: "#c0c0c0",borderRadius: 3,},