| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186 |
- import { Colors } from "@/constants/Colors";
- import { Image } from "expo-image";
- import React, { useEffect, useRef, useState } from "react";
- import { Animated, Easing, StyleSheet, Text, View } from "react-native";
- 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;
- return (
- <View key={`${item.id || index}-main`} style={styles.itemContainer}>
- <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>
- </View>
- );
- };
- 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",
- },
- itemContainer: {
- paddingHorizontal: 12,
- paddingVertical: 4,
- marginRight: 10,
- justifyContent: "center",
- height: 32,
- minWidth: 150,
- backgroundColor: "rgba(0, 0, 0, 0.6)",
- borderRadius: 16,
- borderWidth: 1,
- borderColor: "rgba(0, 243, 255, 0.3)", // Neon blue border
- },
- contentRow: {
- flexDirection: "row",
- alignItems: "center",
- },
- avatarBox: {
- marginRight: 6,
- width: 24,
- height: 24,
- borderRadius: 12,
- borderWidth: 1,
- borderColor: Colors.neonBlue,
- overflow: "hidden",
- backgroundColor: "#000",
- },
- avatar: {
- width: "100%",
- height: "100%",
- },
- textContainer: {
- justifyContent: "center",
- },
- text: {
- color: Colors.textSecondary,
- fontSize: 10,
- },
- nickname: {
- fontWeight: "bold",
- color: "#fff",
- },
- poolName: {
- color: Colors.neonBlue,
- fontSize: 10,
- },
- amount: {
- color: Colors.neonPink,
- fontSize: 11,
- fontWeight: "bold",
- },
- });
|