Banner.tsx 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. import { BannerItem } from '@/services/base';
  2. import { Image } from 'expo-image';
  3. import React, { useEffect, useRef, useState } from 'react';
  4. import { Dimensions, FlatList, StyleSheet, TouchableOpacity, View } from 'react-native';
  5. const { width: SCREEN_WIDTH } = Dimensions.get('window');
  6. // 小程序 banner 高度 432rpx,宽度100%
  7. const BANNER_HEIGHT = 216; // 432rpx / 2
  8. interface BannerProps {
  9. data: BannerItem[];
  10. onPress?: (item: BannerItem) => void;
  11. }
  12. export function Banner({ data, onPress }: BannerProps) {
  13. const [activeIndex, setActiveIndex] = useState(0);
  14. const flatListRef = useRef<FlatList<BannerItem>>(null);
  15. const autoPlayRef = useRef<ReturnType<typeof setInterval> | null>(null);
  16. useEffect(() => {
  17. if (data.length <= 1) return;
  18. autoPlayRef.current = setInterval(() => {
  19. const nextIndex = (activeIndex + 1) % data.length;
  20. flatListRef.current?.scrollToIndex({ index: nextIndex, animated: true });
  21. setActiveIndex(nextIndex);
  22. }, 5000);
  23. return () => {
  24. if (autoPlayRef.current) clearInterval(autoPlayRef.current);
  25. };
  26. }, [activeIndex, data.length]);
  27. const renderItem = ({ item }: { item: BannerItem }) => (
  28. <TouchableOpacity activeOpacity={0.9} onPress={() => onPress?.(item)}>
  29. <Image
  30. source={item.cover}
  31. style={styles.bannerImage}
  32. contentFit="cover"
  33. />
  34. </TouchableOpacity>
  35. );
  36. const onScroll = (event: any) => {
  37. const offsetX = event.nativeEvent.contentOffset.x;
  38. const index = Math.round(offsetX / SCREEN_WIDTH);
  39. if (index !== activeIndex && index >= 0 && index < data.length) {
  40. setActiveIndex(index);
  41. }
  42. };
  43. return (
  44. <View style={styles.container}>
  45. <FlatList
  46. ref={flatListRef}
  47. data={data}
  48. renderItem={renderItem}
  49. keyExtractor={(item, index) => item.id || String(index)}
  50. horizontal
  51. pagingEnabled
  52. showsHorizontalScrollIndicator={false}
  53. onScroll={onScroll}
  54. scrollEventThrottle={16}
  55. getItemLayout={(_, index) => ({
  56. length: SCREEN_WIDTH,
  57. offset: SCREEN_WIDTH * index,
  58. index,
  59. })}
  60. />
  61. <View style={styles.dots}>
  62. {data.map((_, index) => (
  63. <View key={index} style={[styles.dot, activeIndex === index && styles.dotActive]} />
  64. ))}
  65. </View>
  66. </View>
  67. );
  68. }
  69. const styles = StyleSheet.create({
  70. container: {
  71. width: '100%',
  72. height: BANNER_HEIGHT,
  73. marginTop: 11,
  74. overflow: 'hidden',
  75. },
  76. bannerImage: {
  77. width: SCREEN_WIDTH,
  78. height: BANNER_HEIGHT,
  79. },
  80. dots: {
  81. position: 'absolute',
  82. bottom: 10,
  83. left: 0,
  84. right: 0,
  85. flexDirection: 'row',
  86. justifyContent: 'center',
  87. },
  88. dot: {
  89. width: 6,
  90. height: 6,
  91. borderRadius: 3,
  92. backgroundColor: 'rgba(255,255,255,0.5)',
  93. marginHorizontal: 3,
  94. },
  95. dotActive: {
  96. backgroundColor: '#fff',
  97. },
  98. });