React Native FlatList指定View点击增减高度及滚动位置保持
问题分析与解决方案
你的问题核心在于全局状态控制所有Item的高度和展开状态,加上FlatList缺少唯一key导致滚动位置丢失:
- 全局的
initialHeight和isViewShown会让所有Item同步变化,点击任意Item都会触发全列表高度改变 - 未设置
keyExtractor,FlatList重新渲染时无法正确识别组件,进而重置滚动位置
解决步骤
- 用独立状态记录单个Item的展开状态:放弃全局状态,改用集合存储展开的Item唯一ID,实现单个Item的独立控制
- 给FlatList添加唯一key:让FlatList能准确识别每个Item,避免组件复用错误,保留滚动位置
- 动态计算单个Item高度:每个Item根据自身展开状态计算高度,不再依赖全局统一值
- 点击事件仅切换当前Item状态:点击时传递当前Item的ID,只修改该Item的展开状态
修改后的完整代码
import { useState, useCallback } from 'react'; export default function YourComponent() { // 用Set存储展开的Item ID,方便添加/删除操作 const [expandedIds, setExpandedIds] = useState(new Set()); // 切换指定Item的展开状态 const toggleItemExpand = useCallback((itemId) => { setExpandedIds(prev => { const newSet = new Set(prev); newSet.has(itemId) ? newSet.delete(itemId) : newSet.add(itemId); return newSet; }); }, []); // 根据展开状态计算单个Item的高度 const getItemHeight = (isExpanded) => { if (windowWidth > windowHeight) { return isExpanded ? verticalScale(170) : verticalScale(140); } else { return isExpanded ? verticalScale(280) : verticalScale(150); } }; return ( <View> <FlatList data={data} // 必须设置keyExtractor,使用Item的唯一标识 keyExtractor={(item) => item.key} style={{ backgroundColor: '#fff' }} contentContainerStyle={{ paddingBottom: '5%' }} // 可选:若滚动位置仍有跳动,添加该属性强制保持可见区域位置 maintainVisibleContentPosition={{ minIndexForVisible: 0 }} renderItem={({ item }) => { const isExpanded = expandedIds.has(item.key); const itemHeight = getItemHeight(isExpanded); return ( <View style={{ flexDirection: 'column', flex: 1, width: '100%' }}> <TouchableOpacity onPress={() => toggleItemExpand(item.key)}> <View style={{ paddingLeft: 5, flexDirection: 'row', flex: 1, height: itemHeight, backgroundColor: item.Status === 'Open' ? '#B7E6E6' : '#EFA5A5', marginTop: 10, borderRadius: 5, alignContent: 'center', marginHorizontal: 10, }}> <View style={{ backgroundColor: '#fff', width: 50, height: 50, borderRadius: 27, alignSelf: 'center', justifyContent: 'center', alignItems: 'center' }}> <Text style={{ color: item.Status === 'Open' ? '#B7E6E6' : '#EFA5A5' }}>{item.Status}</Text> </View> <View style={{ flex: 1, borderTopRightRadius: 20, borderBottomRightRadius: 20, padding: windowWidth > windowHeight ? moderateScale(10) : moderateScale(15), justifyContent: 'center' }}> <View style={{ flexDirection: 'row', justifyContent: 'space-between', marginVertical: windowWidth > windowHeight ? verticalScale(5) : verticalScale(5), marginTop: 10 }}> <Text style={{ color: 'black', fontSize: windowWidth > windowHeight ? moderateScale(14) : moderateScale(12), fontFamily: 'DMSans-Bold' }}>Customer ID: {item.CusId}</Text> <Text style={{ color: 'black', fontSize: windowWidth > windowHeight ? moderateScale(14) : moderateScale(12), fontFamily: 'DMSans-Bold' }}>Customer PO: {item.CusPo}</Text> </View> <View style={{ flexDirection: 'row', justifyContent: 'space-between', marginVertical: windowWidth > windowHeight ? verticalScale(5) : verticalScale(5) }}> <Text style={{ color: 'black', fontSize: windowWidth > windowHeight ? moderateScale(13) : moderateScale(12), fontFamily: 'DMSans-Regular' }}>Date From: {item.Datefrm}</Text> <Text style={{ color: 'black', fontSize: windowWidth > windowHeight ? moderateScale(13) : moderateScale(12), fontFamily: 'DMSans-Regular' }}>Date To: {item.Dateto}</Text> </View> <View style={{ flexDirection: 'row', justifyContent: 'space-between', marginVertical: windowWidth > windowHeight ? verticalScale(5) : verticalScale(5) }}> <Text style={{ color: 'black', fontSize: windowWidth > windowHeight ? moderateScale(13) : moderateScale(12), fontFamily: 'DMSans-Regular' }}>Tracking No: {item.TrNo}</Text> <Text style={{ color: 'black', fontSize: windowWidth > windowHeight ? moderateScale(13) : moderateScale(12), fontFamily: 'DMSans-Regular' }}>Mark For: {item.Markfr}</Text> </View> {isExpanded && ( <View> <View style={{ flexDirection: 'row', justifyContent: 'space-between', marginVertical: windowWidth > windowHeight ? verticalScale(5) : verticalScale(5) }}> <Text style={{ color: 'black', fontSize: windowWidth > windowHeight ? moderateScale(13) : moderateScale(12), fontFamily: 'DMSans-Regular' }}>Item ID: {item.ItmId}</Text> <Text style={{ color: 'black', fontSize: windowWidth > windowHeight ? moderateScale(13) : moderateScale(12), fontFamily: 'DMSans-Regular' }}>Warehouse: {item.Wearh}</Text> </View> <View style={{ flexDirection: 'row', justifyContent: 'space-between', marginVertical: windowWidth > windowHeight ? verticalScale(5) : verticalScale(5) }}> <Text style={{ color: 'black', fontSize: windowWidth > windowHeight ? moderateScale(13) : moderateScale(12), fontFamily: 'DMSans-Regular' }}>Unit Price: {item.UPri}</Text> <Text style={{ color: 'black', fontSize: windowWidth > windowHeight ? moderateScale(13) : moderateScale(12), fontFamily: 'DMSans-Regular' }}>Ext Price: {item.EPri}</Text> </View> <View style={{ flexDirection: 'row', justifyContent: 'space-between', marginVertical: windowWidth > windowHeight ? verticalScale(5) : verticalScale(5) }}> <Text style={{ color: 'black', fontSize: windowWidth > windowHeight ? moderateScale(13) : moderateScale(12), fontFamily: 'DMSans-Regular' }}>Order No: {item.OrdNum}</Text> <Text style={{ color: 'black', fontSize: windowWidth > windowHeight ? moderateScale(13) : moderateScale(12), fontFamily: 'DMSans-Regular' }}>Status: {item.Status}</Text> </View> </View> )} </View> </View> </TouchableOpacity> </View> ); }} /> </View> ); }
关键修改说明
- 独立状态管理:通过
expandedIds集合控制单个Item的展开状态,不会影响其他Item - 唯一key设置:
keyExtractor让FlatList能准确识别组件,避免滚动位置丢失 - 动态高度计算:每个Item根据自身展开状态计算高度,实现局部高度变化
- 精准事件触发:点击事件仅修改当前Item的状态,减少不必要的全列表渲染
内容的提问来源于stack exchange,提问作者Junaid Anwar
相关产品推荐
相关产品推荐

