当行是可变高度时,FlatList 的滚动问题
Posted
技术标签:
【中文标题】当行是可变高度时,FlatList 的滚动问题【英文标题】:Scrolling issues with FlatList when rows are variable height 【发布时间】:2017-09-28 06:43:52 【问题描述】:我正在使用FlatList
,其中每一行可以有不同的高度(并且可能包含文本和来自远程服务器的零个或多个图像的混合)。
我不能使用getItemLayout
,因为我不知道能够计算的每一行(也不知道前几行)的高度。
我面临的问题是我无法滚动到列表的末尾(当我尝试时它会跳回几行)并且我在尝试使用 scrollToIndex
时遇到问题(我猜是由于事实上我错过了getItemLayout
)。
我写了一个示例项目来演示这个问题:
import React, Component from 'react';
import AppRegistry, StyleSheet, Text, View, Image, FlatList from 'react-native';
import autobind from 'autobind-decorator';
const items = count => [...Array(count)].map((v, i) => (
key: i,
index: i,
image: 'https://dummyimage.com/600x' + (((i % 4) + 1) * 50) + '/000/fff',
));
class RemoteImage extends Component
constructor(props)
super(props);
this.state =
style: flex: 1, height: 0 ,
;
componentDidMount()
Image.getSize(this.props.src, (width, height) =>
this.image = width, height ;
this.onLayout();
);
@autobind
onLayout(event)
if (event)
this.layout =
width: event.nativeEvent.layout.width,
height: event.nativeEvent.layout.height,
;
if (!this.layout || !this.image || !this.image.width)
return;
this.setState(
style:
flex: 1,
height: Math.min(this.image.height,
Math.floor(this.layout.width * this.image.height / this.image.width)),
,
);
render()
return (
<Image
onLayout=this.onLayout
source= uri: this.props.src
style=this.state.style
resizeMode='contain'
/>
);
class Row extends Component
@autobind
onLayout( nativeEvent )
let index, item, onItemLayout = this.props;
let height = Math.max(nativeEvent.layout.height, item.height || 0);
if (height != item.height)
onItemLayout(index, height );
render()
let index, image = this.props.item;
return (
<View style=[styles.row, this.props.style]>
<Text>Header index</Text>
<RemoteImage src = image />
<Text>Footer index</Text>
</View>
);
export default class FlatListTest extends Component
constructor(props)
super(props);
this.state = items: items(50) ;
@autobind
renderItem( item, index )
return <Row
item=item
style=index&1 && styles.row_alternate || null
onItemLayout=this.onItemLayout
/>;
@autobind
onItemLayout(index, props)
let items = [...this.state.items];
let item = ...items[index], ...props ;
items[index] = ...item, key: [item.height, item.index].join('_') ;
this.setState( items );
render()
return (
<FlatList
ref=ref => this.list = ref
data=this.state.items
renderItem=this.renderItem
/>
);
const styles = StyleSheet.create(
row:
padding: 5,
,
row_alternate:
backgroundColor: '#bbbbbb',
,
);
AppRegistry.registerComponent('FlatListTest', () => FlatListTest);
【问题讨论】:
Gilad Novik,你解决这个问题了吗?我也有同样的问题。 @Kholiavko 很遗憾,我还没有找到解决方案。最新的 RN 版本仍然存在这个问题 :( 【参考方案1】:改用scrollToOffset():
export default class List extends React.PureComponent
// Gets the total height of the elements that come before
// element with passed index
getOffsetByIndex(index)
let offset = 0;
for (let i = 0; i < index; i += 1)
const elementLayout = this._layouts[i];
if (elementLayout && elementLayout.height)
offset += this._layouts[i].height;
return offset;
// Gets the comment object and if it is a comment
// is in the list, then scrolls to it
scrollToComment(comment)
const list = this.props;
const commentIndex = list.findIndex(( id ) => id === comment.id);
if (commentIndex !== -1)
const offset = this.getOffsetByIndex(commentIndex);
this._flatList.current.scrollToOffset( offset, animated: true );
// Fill the list of objects with element sizes
addToLayoutsMap(layout, index)
this._layouts[index] = layout;
render()
const list = this.props;
return (
<FlatList
data=list
keyExtractor=item => item.id
renderItem=( item, index ) =>
return (
<View
onLayout=( nativeEvent: layout ) =>
this.addToLayoutsMap(layout, index);
>
<Comment id=item.id />
</View>
);
ref=this._flatList
/>
);
-
渲染时,我获取列表中每个元素的大小并将其写入数组:
onLayout=( nativeEvent: layout ) => this._layouts[index] = layout
当需要将屏幕滚动到元素时,我会汇总它前面所有元素的高度,并得到滚动屏幕的量(getOffsetByIndex方法)。
我使用的是 scrollToOffset 方法:
this._flatList.current.scrollToOffset( offset, animated: true );
(this._flatList 是 FlatList 的引用)
【讨论】:
感谢您抽出宝贵时间分享您的代码。您能否更新您的答案以更好地描述您在做什么? 这行得通!这种方式滚动从 ScrollView 内的 FlatList 工作。非常感谢您分享这个 sn-p @AndreyEremenko 如果您想要到达的项目和它之前的项目已经布置好,则此方法有效。但事实并非如此,您提出什么解决方案?【参考方案2】:当行的高度可变时,我没有找到任何使用getItemLayout
的方法,所以你不能使用initialScrollIndex
。
但我有一个可能有点慢的解决方案:
您可以使用 scrollToIndex
,但是当您的项目被渲染时。所以你需要 initialNumToRender
。
您必须等待项目被渲染并在使用 scrollToIndex
之后,所以您不能在 componentDidMount
中使用 scrollToIndex
。
我想到的唯一解决方案是在 onViewableItemsChanged
中使用 scrollToIndex
。请注意以下示例:
在此示例中,我们希望在此组件运行后立即转到项目 this.props.index
constructor(props)
this.goToIndex = true;
render()
return (
<FlatList
ref=component => this.myFlatList = component;
data=data
renderItem=(item)=>this._renderItem(item)
keyExtractor=(item,index)=>index.toString()
initialNumToRender=this.props.index+1
onViewableItemsChanged=( viewableItems ) =>
if (this.goToIndex)
this.goToIndex = false;
setTimeout(() => this.myFlatList.scrollToIndex(index:this.props.index); , 10);
/>
);
【讨论】:
【参考方案3】:所以我认为你可以做的以及你已经拥有的出口是通过行布局onLayout
的索引来存储集合。您需要存储returned by getItemLayout
:length: number, offset: number, index: number
的属性。
然后,当您实现 getItemLayout
which passes an index 时,您可以返回您存储的布局。这应该可以解决scrollToIndex
的问题。尚未对此进行测试,但这似乎是正确的方法。
【讨论】:
这也是我的想法,但我无法让它发挥作用。 GetItemLayout 在 onlayout 之前被调用,每行返回并在父组件中存储一个值【参考方案4】:你试过scrollToEnd
吗?
http://facebook.github.io/react-native/docs/flatlist.html#scrolltoend
正如文档所述,没有getItemLayout
可能会很麻烦,但对我来说,没有它它确实可以工作
【讨论】:
以上是关于当行是可变高度时,FlatList 的滚动问题的主要内容,如果未能解决你的问题,请参考以下文章
如何在带有 Flutter 的可滚动视图中拥有具有可变高度内容的 TabView?
排毒:“找不到 UI 元素。”尝试滚动 FlatList 时
当我滚动 Flatlist 时,内存使用率越来越高,当我停止滚动时内存没有释放(React Native)