import React, { useState, useRef, useEffect } from 'react'; import { Text, SafeAreaView, StyleSheet, View, FlatList, TouchableOpacity, Dimensions, Image, NativeSyntheticEvent, NativeScrollEvent, LayoutChangeEvent } from "react-native"; const screenWidth = Dimensions.get('window').width; // 分类列表 const categoryList = [ { id: '0', title: '捕鱼', icon: '🎣' }, { id: '1', title: '真人', icon: '👤' }, { id: '2', title: '区块链', icon: '⛓️' }, { id: '3', title: '棋牌', icon: '🎴' }, { id: '4', title: '电子', icon: '🎰' }, { id: '5', title: '体育', icon: '⚽' }, { id: '6', title: '彩票', icon: '🎫' }, { id: '7', title: '电竞', icon: '🎮' }, { id: '8', title: '赛车', icon: '🏎️' }, { id: '9', title: '街机', icon: '🕹️' }, { id: '10', title: '桌游', icon: '🎲' }, { id: '11', title: '其他', icon: '⭐' }, ]; // 随机生成游戏数据 const generateRandomGames = (categoryId: string, baseName: string) => { // 调整随机数,使其更容易出现空列表或少量item,以测试优化效果 const count = Math.floor(Math.random() * 6); // 产生 0 到 5 个 const gameTypes = ['经典版', '豪华版', '至尊版', '竞技版', '休闲版', '大师版', '传奇版', '极速版', '黄金版', '钻石版', '王者版']; const colors = ['4A90E2', 'E94B3C', '50C878', 'F39C12', '9B59B6', 'E74C3C']; return Array.from({ length: count }).map((_, i) => { const color = colors[Math.floor(Math.random() * colors.length)]; return { id: `${categoryId}-${i}`, name: `${baseName}${gameTypes[i % gameTypes.length]}`, hot: Math.random() > 0.6, image: `https://via.placeholder.com/120x80/${color}/ffffff?text=Game+${i + 1}`, }; }); }; // 模拟数据 const mockData: Record> = {}; categoryList.forEach(cat => { mockData[cat.id] = generateRandomGames(cat.id, cat.title); }); // 定义左侧Item的固定高度 // 样式中: paddingVertical(18*2) + icon(24) + iconMargin(4) + title(12) 估算 // 我们在样式中直接指定为 76 const LEFT_ITEM_HEIGHT = 76; export default function ActivityScreen() { const [selectedId, setSelectedId] = useState('0'); const [isSwitching, setIsSwitching] = useState(false); const rightListRef = useRef(null); // 创建左侧列表的ref const leftListRef = useRef(null); // State,用于存储右侧列表容器的实际高度 const [rightListHeight, setRightListHeight] = useState(0); // Slogan:当selectedId变化时,自动滚动左侧列表 useEffect(() => { // 1. 找到当前 selectedId 对应的索引 const newIndex = categoryList.findIndex(item => item.id === selectedId); // 2. 如果找到了 (index > -1) 并且 ref 已经准备好 if (leftListRef.current && newIndex > -1) { // 3. 命令 FlatList 滚动到该索引 leftListRef.current.scrollToIndex({ index: newIndex, animated: true, // viewPosition: 0.5 会将item滚动到列表的中间位置,体验更好 viewPosition: 0.5 }); } }, [selectedId]); // 依赖项是 selectedId,它变化时此Slogan执行 // 支持循环切换分类 const handleCategorySwitch = (direction: 'prev' | 'next') => { const currentIndex = categoryList.findIndex(item => item.id === selectedId); const total = categoryList.length; let newIndex = direction === 'next' ? (currentIndex + 1) % total : (currentIndex - 1 + total) % total; // 切换分类 setSelectedId(categoryList[newIndex].id); // 切换时重置滚动 rightListRef.current?.scrollToOffset({ offset: 0, animated: false }); }; // 实时检测滚动边界 + 循环切换 const handleScroll = (e: NativeSyntheticEvent) => { const { contentOffset, contentSize, layoutMeasurement } = e.nativeEvent; const y = contentOffset.y; const layoutHeight = layoutMeasurement.height; const contentHeight = contentSize.height; if (isSwitching) return; // 滑到顶部并继续下拉 if (y < -40) { setIsSwitching(true); handleCategorySwitch('prev'); setTimeout(() => setIsSwitching(false), 600); } // 滑到底部并继续上推 // contentHeight <= layoutHeight 检查内容是否填满 if (contentHeight <= layoutHeight ? y > 40 : y + layoutHeight - contentHeight > 40) { setIsSwitching(true); handleCategorySwitch('next'); setTimeout(() => setIsSwitching(false), 600); } }; // onLayout 事件处理函数 const onRightListLayout = (event: LayoutChangeEvent) => { const { height } = event.nativeEvent.layout; setRightListHeight(height); }; // 左侧Item组件 const LeftItem = ({ id, title, icon, isSelected, onPress }: any) => ( // 包裹在 View 中,并精确设置高度,确保 getItemLayout 的计算正确 {icon} {title} {isSelected && } ); // 右侧Item组件 const RightItem = ({ name, hot, image }: any) => ( {name} {hot && HOT} 精彩游戏,等你来玩 ); // 为 leftListRef 的 scrollToIndex 提供性能保证 const getItemLayout = (data: any, index: number) => ({ length: LEFT_ITEM_HEIGHT, offset: LEFT_ITEM_HEIGHT * index, index }); return ( {/* 左侧分类 */} ( setSelectedId(item.id)} /> )} keyExtractor={item => item.id} showsVerticalScrollIndicator={false} getItemLayout={getItemLayout} // 提高滚动性能 /> {/* 右侧内容 */} {/* 只有在获取到高度后才渲染 FlatList,确保 minHeight 初始值正确 */} {rightListHeight > 0 && ( ( )} keyExtractor={item => item.id} showsVerticalScrollIndicator={false} bounces={true} scrollEventThrottle={16} onScroll={handleScroll} // 为 iOS 添加,使其在内容不足时也能反弹 alwaysBounceVertical={true} // 设置 contentContainerStyle contentContainerStyle={[ styles.rightListContent, { // 确保内容容器的最小高度 // 始终比 FlatList 视图本身高 1 像素 minHeight: rightListHeight + 1 } ]} /> )} ); } const styles = StyleSheet.create({ safeAreaContainer: { flex: 1, backgroundColor: '#f5f5f5' }, container: { flex: 1 }, content: { flex: 1, flexDirection: 'row' }, leftWrapper: { width: 90, backgroundColor: '#fff', borderRightWidth: 1, borderRightColor: '#e0e0e0', }, // 一个容器,严格控制Item高度 leftItemContainer: { height: LEFT_ITEM_HEIGHT, // 严格高度 width: '100%', }, leftItem: { flex: 1, // 填满父容器 paddingHorizontal: 8, alignItems: 'center', justifyContent: 'center', position: 'relative', }, leftItemActive: { backgroundColor: '#f0f0f0' }, leftIcon: { fontSize: 24, marginBottom: 4 }, leftTitle: { fontSize: 12, color: '#666', textAlign: 'center' }, leftTitleActive: { color: '#1890ff', fontWeight: '600' }, activeIndicator: { position: 'absolute', left: 0, top: '50%', marginTop: -12, // (24 / 2) width: 3, height: 24, backgroundColor: '#1890ff', borderTopRightRadius: 2, borderBottomRightRadius: 2, }, contentRight: { flex: 1, backgroundColor: '#f5f5f5' }, rightListContent: { padding: 12, paddingBottom: 20 }, rightItem: { backgroundColor: '#fff', borderRadius: 8, padding: 12, marginBottom: 12, shadowColor: '#000', shadowOffset: { width: 0, height: 1 }, shadowOpacity: 0.05, shadowRadius: 2, elevation: 2, flexDirection: 'row', alignItems: 'center', }, gameImage: { width: 120, height: 80, borderRadius: 6, marginRight: 12, backgroundColor: '#f0f0f0', }, rightItemInfo: { flex: 1, justifyContent: 'center' }, rightItemContent: { flexDirection: 'row', alignItems: 'center', marginBottom: 6 }, rightItemTitle: { fontSize: 15, fontWeight: '600', color: '#333', marginRight: 8, flex: 1 }, hotBadge: { backgroundColor: '#ff4d4f', paddingHorizontal: 6, paddingVertical: 2, borderRadius: 4 }, hotText: { color: '#fff', fontSize: 10, fontWeight: '600' }, rightItemDesc: { fontSize: 13, color: '#999' }, });