Files
rn-app1/app/(tabs)/activity.tsx
2025-11-11 16:58:37 +07:00

225 lines
7.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useState, useRef } from 'react';
import {
Text,
SafeAreaView,
StyleSheet,
View,
FlatList,
TouchableOpacity,
Dimensions,
Image,
NativeSyntheticEvent,
NativeScrollEvent
} 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) => {
const count = Math.floor(Math.random() * 11) + 1;
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<string, Array<{id: string, name: string, hot: boolean, image: string}>> = {};
categoryList.forEach(cat => {
mockData[cat.id] = generateRandomGames(cat.id, cat.title);
});
export default function ActivityScreen() {
const [selectedId, setSelectedId] = useState('0');
const [isSwitching, setIsSwitching] = useState(false);
const rightListRef = useRef<FlatList>(null);
// 支持循环切换分类
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<NativeScrollEvent>) => {
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);
}
};
const LeftItem = ({ id, title, icon, isSelected, onPress }: any) => (
<TouchableOpacity
style={[styles.leftItem, isSelected && styles.leftItemActive]}
onPress={onPress}
>
<Text style={styles.leftIcon}>{icon}</Text>
<Text style={[styles.leftTitle, isSelected && styles.leftTitleActive]}>{title}</Text>
{isSelected && <View style={styles.activeIndicator} />}
</TouchableOpacity>
);
const RightItem = ({ name, hot, image }: any) => (
<View style={styles.rightItem}>
<Image source={{ uri: image }} style={styles.gameImage} resizeMode="cover" />
<View style={styles.rightItemInfo}>
<View style={styles.rightItemContent}>
<Text style={styles.rightItemTitle} numberOfLines={1}>{name}</Text>
{hot && <View style={styles.hotBadge}><Text style={styles.hotText}>HOT</Text></View>}
</View>
<Text style={styles.rightItemDesc}></Text>
</View>
</View>
);
return (
<SafeAreaView style={styles.safeAreaContainer}>
<View style={styles.container}>
<View style={styles.content}>
{/* 左侧分类 */}
<View style={styles.leftWrapper}>
<FlatList
data={categoryList}
renderItem={({ item }) => (
<LeftItem
id={item.id}
title={item.title}
icon={item.icon}
isSelected={selectedId === item.id}
onPress={() => setSelectedId(item.id)}
/>
)}
keyExtractor={item => item.id}
showsVerticalScrollIndicator={false}
/>
</View>
{/* 右侧内容 */}
<View style={styles.contentRight}>
<FlatList
ref={rightListRef}
data={mockData[selectedId] || []}
renderItem={({ item }) => (
<RightItem name={item.name} hot={item.hot} image={item.image} />
)}
keyExtractor={item => item.id}
showsVerticalScrollIndicator={false}
bounces={true}
scrollEventThrottle={16}
onScroll={handleScroll}
contentContainerStyle={styles.rightListContent}
/>
</View>
</View>
</View>
</SafeAreaView>
);
}
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',
},
leftItem: {
paddingVertical: 18,
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,
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' },
});