From 8090c60142984779f538a0f760a6495ab14144e1 Mon Sep 17 00:00:00 2001 From: Vegas <“qazmm69@gmail.com”> Date: Tue, 11 Nov 2025 17:45:09 +0700 Subject: [PATCH] =?UTF-8?q?=E6=BB=9A=E5=8A=A8=E6=97=B6=E8=AE=A9=E5=B7=A6?= =?UTF-8?q?=E4=BE=A7=E8=8F=9C=E5=8D=95=E9=80=89=E4=B8=AD=E7=9A=84item?= =?UTF-8?q?=E5=A7=8B=E7=BB=88=E5=B1=85=E4=B8=AD=E5=B1=95=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/(tabs)/activity.tsx | 97 ++++++++++++++++++++++++++++------------- 1 file changed, 67 insertions(+), 30 deletions(-) diff --git a/app/(tabs)/activity.tsx b/app/(tabs)/activity.tsx index c9e580a..bf7211f 100644 --- a/app/(tabs)/activity.tsx +++ b/app/(tabs)/activity.tsx @@ -1,4 +1,4 @@ -import React, { useState, useRef } from 'react'; +import React, { useState, useRef, useEffect } from 'react'; import { Text, SafeAreaView, @@ -10,7 +10,7 @@ import { Image, NativeSyntheticEvent, NativeScrollEvent, - LayoutChangeEvent // 导入 LayoutChangeEvent 类型 + LayoutChangeEvent } from "react-native"; const screenWidth = Dimensions.get('window').width; @@ -34,8 +34,7 @@ const categoryList = [ // 随机生成游戏数据 const generateRandomGames = (categoryId: string, baseName: string) => { // 调整随机数,使其更容易出现空列表或少量item,以测试优化效果 - // (Math.random() * 6) + 0 意味着可能产生 0 到 5 个 - const count = Math.floor(Math.random() * 6); + const count = Math.floor(Math.random() * 6); // 产生 0 到 5 个 const gameTypes = ['经典版', '豪华版', '至尊版', '竞技版', '休闲版', '大师版', '传奇版', '极速版', '黄金版', '钻石版', '王者版']; const colors = ['4A90E2', 'E94B3C', '50C878', 'F39C12', '9B59B6', 'E74C3C']; @@ -56,14 +55,40 @@ 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); - // 【新增】State,用于存储右侧列表容器的实际高度 + // 创建左侧列表的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); @@ -72,7 +97,7 @@ export default function ActivityScreen() { // 切换分类 setSelectedId(categoryList[newIndex].id); - // 切换时重置滚动,避免新列表加载时停在奇怪的位置 + // 切换时重置滚动 rightListRef.current?.scrollToOffset({ offset: 0, animated: false }); }; @@ -83,21 +108,17 @@ export default function ActivityScreen() { const layoutHeight = layoutMeasurement.height; const contentHeight = contentSize.height; - // 如果正在切换中,则锁定,防止短时间内连续触发 if (isSwitching) return; - // 滑到顶部并继续下拉(-40 是一个触发阈值) + // 滑到顶部并继续下拉 if (y < -40) { setIsSwitching(true); handleCategorySwitch('prev'); - // 延迟重置isSwitching状态,给动画和数据加载留出时间 setTimeout(() => setIsSwitching(false), 600); } // 滑到底部并继续上推 // contentHeight <= layoutHeight 检查内容是否填满 - // (如果没填满) y > 40 检查是否在空白处上拉 - // (如果已填满) y + layoutHeight - contentHeight > 40 检查是否滚动到底部并继续上拉 if (contentHeight <= layoutHeight ? y > 40 : y + layoutHeight - contentHeight > 40) { setIsSwitching(true); handleCategorySwitch('next'); @@ -105,25 +126,29 @@ export default function ActivityScreen() { } }; - // 【新增】onLayout 事件处理函数 - // 当右侧容器布局时,获取其高度 + // onLayout 事件处理函数 const onRightListLayout = (event: LayoutChangeEvent) => { const { height } = event.nativeEvent.layout; setRightListHeight(height); }; + // 左侧Item组件 const LeftItem = ({ id, title, icon, isSelected, onPress }: any) => ( - - {icon} - {title} - {isSelected && } - + // 包裹在 View 中,并精确设置高度,确保 getItemLayout 的计算正确 + + + {icon} + {title} + {isSelected && } + + ); + // 右侧Item组件 const RightItem = ({ name, hot, image }: any) => ( @@ -137,6 +162,13 @@ export default function ActivityScreen() { ); + // 为 leftListRef 的 scrollToIndex 提供性能保证 + const getItemLayout = (data: any, index: number) => ({ + length: LEFT_ITEM_HEIGHT, + offset: LEFT_ITEM_HEIGHT * index, + index + }); + return ( @@ -144,6 +176,7 @@ export default function ActivityScreen() { {/* 左侧分类 */} ( item.id} showsVerticalScrollIndicator={false} + getItemLayout={getItemLayout} // 提高滚动性能 /> {/* 右侧内容 */} - {/* 【优化点 1】给容器添加 onLayout 来获取高度 */} {/* 只有在获取到高度后才渲染 FlatList,确保 minHeight 初始值正确 */} {rightListHeight > 0 && ( @@ -172,15 +205,14 @@ export default function ActivityScreen() { )} keyExtractor={item => item.id} showsVerticalScrollIndicator={false} - // bounces 必须为 true (默认值) bounces={true} scrollEventThrottle={16} onScroll={handleScroll} - // 【优化点 2】为 iOS 添加,使其在内容不足时也能反弹 + // 为 iOS 添加,使其在内容不足时也能反弹 alwaysBounceVertical={true} - // 【优化点 3】设置 contentContainerStyle + // 设置 contentContainerStyle contentContainerStyle={[ styles.rightListContent, { @@ -208,8 +240,13 @@ const styles = StyleSheet.create({ borderRightWidth: 1, borderRightColor: '#e0e0e0', }, + // 一个容器,严格控制Item高度 + leftItemContainer: { + height: LEFT_ITEM_HEIGHT, // 严格高度 + width: '100%', + }, leftItem: { - paddingVertical: 18, + flex: 1, // 填满父容器 paddingHorizontal: 8, alignItems: 'center', justifyContent: 'center', @@ -223,7 +260,7 @@ const styles = StyleSheet.create({ position: 'absolute', left: 0, top: '50%', - marginTop: -12, + marginTop: -12, // (24 / 2) width: 3, height: 24, backgroundColor: '#1890ff', @@ -231,9 +268,9 @@ const styles = StyleSheet.create({ borderBottomRightRadius: 2, }, contentRight: { flex: 1, backgroundColor: '#f5f5f5' }, - rightListContent: { - padding: 12, - paddingBottom: 20 + rightListContent: { + padding: 12, + paddingBottom: 20 }, rightItem: { backgroundColor: '#fff',