zbb 3 месяцев назад
Родитель
Сommit
2707fad7b8
1 измененных файлов с 175 добавлено и 0 удалено
  1. 175 0
      components/Barrage.tsx

+ 175 - 0
components/Barrage.tsx

@@ -0,0 +1,175 @@
+import { Image } from 'expo-image';
+import React, { useEffect, useRef, useState } from 'react';
+import { Animated, Easing, ImageBackground, StyleSheet, Text, View } from 'react-native';
+
+import { Images } from '@/constants/images';
+
+interface BarrageItemType {
+  id?: string;
+  poolId?: string;
+  nickname?: string;
+  poolName?: string;
+  text?: string; // amount or feedback
+  type?: string; // '奖池'
+  avatar?: string;
+}
+
+interface BarrageProps {
+  data: BarrageItemType[];
+  speed?: number; // ms per pixel? Or just a factor.
+  style?: any;
+}
+
+export const Barrage: React.FC<BarrageProps> = ({ data, speed = 30, style }) => {
+  const [contentWidth, setContentWidth] = useState(0);
+  const translateX = useRef(new Animated.Value(0)).current;
+
+  useEffect(() => {
+    if (contentWidth > 0 && data.length > 0) {
+      startAnimation();
+    }
+  }, [contentWidth, data]);
+
+  const startAnimation = () => {
+    // Reset to 0
+    translateX.setValue(0);
+    
+    // Duration: width * factor. 
+    // If width is 1000, speed 30 -> 30000ms (30s).
+    const duration = contentWidth * speed;
+
+    Animated.loop(
+      Animated.timing(translateX, {
+        toValue: -contentWidth,
+        duration: duration,
+        easing: Easing.linear,
+        useNativeDriver: true,
+      })
+    ).start();
+  };
+
+  const renderItem = (item: BarrageItemType, index: number) => {
+    const isPool = item.poolId && Number(item.text || '0') > 0;
+    // item.type == '奖池' logic from Vue
+    
+    return (
+      <ImageBackground
+        key={`${item.id || index}-main`}
+        source={{ uri: Images.box.barrageItem }}
+        style={styles.itemBg}
+        resizeMode="stretch"
+      >
+        <View style={styles.contentRow}>
+            {/* Avatar */}
+            <View style={styles.avatarBox}>
+                <Image source={{ uri: item.avatar }} style={styles.avatar} contentFit="cover" />
+            </View>
+            
+            {/* Text Content */}
+            <View style={styles.textContainer}>
+                {isPool ? (
+                    <Text style={styles.text} numberOfLines={1}>
+                        <Text style={styles.nickname}>{item.nickname?.slice(0,1) + '***' + item.nickname?.slice(-1)}</Text>
+                        <Text> 在 </Text>
+                        <Text style={styles.poolName}>{item.poolName}</Text>
+                        <Text> {item.type === '奖池' ? '消费了' : '获得'} </Text>
+                        <Text style={styles.amount}>{item.text}</Text>
+                        <Text> {item.type === '奖池' ? '元' : ''} </Text>
+                    </Text>
+                ) : (
+                   <Text style={styles.text} numberOfLines={1}>
+                       <Text style={styles.nickname}>{item.nickname?.slice(0,1) + '***' + item.nickname?.slice(-1)}</Text>
+                       <Text>: {item.text}</Text>
+                   </Text>
+                )}
+            </View>
+        </View>
+      </ImageBackground>
+    );
+  };
+
+  if (!data || data.length === 0) return null;
+
+  return (
+    <View style={[styles.container, style]}>
+      <Animated.View
+        style={[
+          styles.scrollContainer,
+          { transform: [{ translateX }] },
+        ]}
+      >
+        {/* Measure the width of the first set */}
+        <View 
+            style={styles.row} 
+            onLayout={(e) => setContentWidth(e.nativeEvent.layout.width)}
+        >
+            {data.map((item, index) => renderItem(item, index))}
+        </View>
+        {/* Duplicate set for seamless loop */}
+        <View style={styles.row}>
+            {data.map((item, index) => renderItem(item, index))}
+        </View>
+      </Animated.View>
+    </View>
+  );
+};
+
+const styles = StyleSheet.create({
+  container: {
+    width: '100%',
+    overflow: 'hidden',
+    height: 40, // Adjust based on item height
+  },
+  scrollContainer: {
+    flexDirection: 'row',
+  },
+  row: {
+    flexDirection: 'row',
+    alignItems: 'center',
+  },
+  itemBg: {
+    paddingHorizontal: 12, // Reduced padding
+    paddingVertical: 4,
+    marginRight: 10,
+    justifyContent: 'center',
+    height: 32, // Fixed height
+    minWidth: 150,
+  },
+  contentRow: {
+      flexDirection: 'row',
+      alignItems: 'center',
+  },
+  avatarBox: {
+      marginRight: 6,
+      width: 24,
+      height: 24,
+      borderRadius: 12,
+      borderWidth: 1,
+      borderColor: '#000',
+      overflow: 'hidden',
+      backgroundColor: '#fff',
+  },
+  avatar: {
+      width: '100%',
+      height: '100%',
+  },
+  textContainer: {
+      justifyContent: 'center',
+  },
+  text: {
+      color: '#fff',
+      fontSize: 10,
+  },
+  nickname: {
+      fontWeight: 'bold',
+  },
+  poolName: {
+      color: '#0084FF',
+      fontSize: 10,
+  },
+  amount: {
+      color: '#FF0000',
+      fontSize: 11,
+      fontWeight: 'bold',
+  }
+});