当行的高度可变时使用 FlatList 滚动问题 [英] Scrolling issues with FlatList when rows are variable height
问题描述
我使用的是 FlatList
,其中每一行可以有不同的高度(并且可能包含文本和来自远程服务器的零个或多个图像的混合).
I'm using a FlatList
where each row can be of different height (and may contain a mix of both text and zero or more images from a remote server).
我不能使用 getItemLayout
因为我不知道每行(也不知道前几行)的高度来计算.
I cannot use getItemLayout
because I don't know the height of each row (nor the previous ones) to be able to calculate.
我面临的问题是我无法滚动到列表的末尾(当我尝试时它跳回几行)并且我在尝试使用 scrollToIndex
时遇到问题(我我猜是因为我缺少 getItemLayout
).
The problem I'm facing is that I cannot scroll to the end of the list (it jumps back few rows when I try) and I'm having issues when trying to use scrollToIndex
(I'm guessing due to the fact I'm missing getItemLayout
).
我写了一个示例项目来演示这个问题:
I wrote a sample project to demonstrate the problem:
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);
推荐答案
使用 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方法).
When it is necessary to scroll the screen to the element, I summarize the heights of all the elements in front of it and get the amount to which to scroll the screen (getOffsetByIndex method).
我使用了 scrollToOffset 方法:
I use the scrollToOffset method:
this._flatList.current.scrollToOffset({ offset, animation: true });
(this._flatList 是 FlatList 的引用)
(this._flatList is ref of FlatList)
这篇关于当行的高度可变时使用 FlatList 滚动问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!