import React, { useState, useRef, useEffect } from 'react'; import { Text, SafeAreaView, StyleSheet, View, FlatList, TouchableOpacity, Dimensions, Image, NativeSyntheticEvent, NativeScrollEvent, LayoutChangeEvent, LayoutAnimation, // 动画 UIManager, // 动画 Platform // 动画 } from "react-native"; const screenWidth = Dimensions.get('window').width; // 为 Android 启用 LayoutAnimation (必须的样板代码) if ( Platform.OS === 'android' && UIManager.setLayoutAnimationEnabledExperimental ) { UIManager.setLayoutAnimationEnabledExperimental(true); } // --- 数据模型和常量 --- // 分类列表 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) => { // 产生 0 到 5 个 Item,用于测试空白区域滚动 const count = Math.floor(Math.random() * 6); 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的固定高度,用于 getItemLayout const LEFT_ITEM_HEIGHT = 76; export default function ActivityScreen() { const [selectedId, setSelectedId] = useState('0'); const [isSwitching, setIsSwitching] = useState(false); const rightListRef = useRef(null); const leftListRef = useRef(null); const [rightListHeight, setRightListHeight] = useState(0); // 优化 1: 左侧列表自动滚动,确保选中项可见 useEffect(() => { const newIndex = categoryList.findIndex(item => item.id === selectedId); if (leftListRef.current && newIndex > -1) { leftListRef.current.scrollToIndex({ index: newIndex, animated: true, viewPosition: 0.5 // 滚动到居中位置 }); } }, [selectedId]); // 处理分类切换的核心逻辑 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; // 优化 3: 在 state 改变之前,配置下一次布局动画 LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut); // 切换分类 setSelectedId(categoryList[newIndex].id); rightListRef.current?.scrollToOffset({ offset: 0, animated: false }); }; // 优化 2: 实时检测滚动边界 + 循环切换 (处理空白区域滚动) 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); } // 滑到底部并继续上推 (包含内容不足以填满屏幕的情况) if (contentHeight <= layoutHeight ? y > 40 : y + layoutHeight - contentHeight > 40) { setIsSwitching(true); handleCategorySwitch('next'); setTimeout(() => setIsSwitching(false), 600); } }; // 获取右侧列表容器的高度 (用于 minHeight 优化) const onRightListLayout = (event: LayoutChangeEvent) => { const { height } = event.nativeEvent.layout; setRightListHeight(height); }; // 左侧Item组件 const LeftItem = ({ id, title, icon, isSelected, onPress }: any) => ( {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 ( {/* 左侧分类 */} ( { // 优化 3: 左侧点击时也触发动画 LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut); setSelectedId(item.id); }} /> )} keyExtractor={item => item.id} showsVerticalScrollIndicator={false} getItemLayout={getItemLayout} // 提高滚动性能 /> {/* 右侧内容 */} {/* 只有在获取到高度后才渲染 FlatList */} {rightListHeight > 0 && ( ( )} keyExtractor={item => item.id} showsVerticalScrollIndicator={false} bounces={true} scrollEventThrottle={16} onScroll={handleScroll} alwaysBounceVertical={true} // 确保内容不足时也能滚动 (iOS) contentContainerStyle={[ styles.rightListContent, { // 优化 2: 确保内容容器的最小高度,实现空白区域滚动 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高度,用于 getItemLayout 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' }, });