问题
I have a FlatList which fills with about 18000 text rows. Because the text in each row is of variable length, I cannot provide a static height for use in getItemLayout.
What I am attempting to do is first iterate through the entire dataset using a dummy view, from which I collect the height dimension via onLayout. My issue is that currently, the view is only re-rendering around 10 times before it just stops. As far as I can tell, onLayout simply isn't called for the last view that ends up appearing on the screen -- which is again only the 10th (or so, depending on the interval I iterate through the data at).
Below is my code which I've attempted to simplify somewhat here. Currently just trying to find a way to iterate through and precalculate view heights for the entire data set ahead of rendering in a separate component.
import React, { Component } from 'react';
import { AppRegistry, Text, View, TouchableOpacity, StyleSheet } from 'react-native';
export default class DummyMainItem extends Component {
constructor(props) {
super(props);
this.state = {
codeData: [],
currentItem: -1,
};
}
componentWillReceiveProps(nextProps) {
// Get data from MainFlatList
if (nextProps.codeData != '') {
console.log("PROPS in DummyMainItem are codeData of length: " + nextProps.codeData.length);
this.setState({ codeData: nextProps.codeData });
}
}
getItemDimensions(layout) {
const { x, y, width, height } = layout;
curItem = this.state.currentItem;
console.log("getItemDimensions of #" + curItem + " (width, height) : (" + width + ", " + height + ")");
if (curItem <= this.state.codeData.length) {
this.setState({ currentItem: curItem + 1 });
}
}
render() {
console.log("Dummy Rendering item #" + this.state.currentItem);
if (this.state.codeData != null && this.state.codeData.length > 0) {
var item = this.state.codeData[this.state.currentItem];
switch (item.type) {
case '0':
console.log("Dummy Case 0 FULLTEXT: " + item.fulltext);
return (
<TouchableOpacity>
<View onLayout={(event) => { this.getItemDimensions(event.nativeEvent.layout) }}>
<View>
<Text>{item.fulltext}</Text>
</View>
</View>
</TouchableOpacity>
);
case '1':
console.log("Dummy Case 1 FULLTEXT: " + item.fulltext);
return (
<TouchableOpacity>
<View onLayout={(event) => { this.getItemDimensions(event.nativeEvent.layout) }}>
<View>
<Text>{item.fulltext}</Text>
</View>
</View>
</TouchableOpacity>
);
case '2':
console.log("Dummy Case 2 FULLTEXT: " + item.fulltext);
return (
<TouchableOpacity>
<View onLayout={(event) => { this.getItemDimensions(event.nativeEvent.layout) }}>
<View style={{ flexDirection: 'row' }}>
<Text>{item.section}</Text>
<Text>{item.fulltext}</Text>
</View>
</View>
</TouchableOpacity>
);
default:
return (
<TouchableOpacity>
<View>
<View onLayout={(event) => { this.getItemDimensions(event.nativeEvent.layout) }}>
<Text>{item.type} {item.fulltext} </Text>
</View>
</View>
</TouchableOpacity>
);
}
} else {
console.log("Dummy Case Blank Render");
return (
<TouchableOpacity>
<View onLayout={(event) => { this.getItemDimensions(event.nativeEvent.layout) }}>
<View>
<Text> </Text>
</View>
</View>
</TouchableOpacity>
)
}
}
}
AppRegistry.registerComponent('DummyMainItem', () => DummyMainItem);
I would expect from my code that each time a view was rendered, the getItemDimensions() function would be called, which itself adjusts the state of this.state.currentItem, this state change causing the render() function to be triggered again.
The actual behaviour is that it does the above, but only a limited number of times (closer to about 10 than the 18000 I need). getItemDimensions() is not called by onLayout, so dimensions are not obtained and the state of currentItem does not get advanced - therefore the rendering stops. What I do not understand is why for whatever last view appears on the screen, onLayout is not calling getItemDimensions.
Does anyone have any insight on why the iteration stops, or on a more effective way to achieve my goal? I need to provide getItemLayout to my FlatList as the app needs to be able to ScrollToIndex before a user manually scrolls through the entire FlatList.
回答1:
The issue with onLayout not being called resulted from the fact that onLayout will not happen unless there is a difference between the previous layout and the one replacing it. In my case, when 2 views with identical heights followed one another, the iteration would stop due to onLayout not being called again.
The simple solution is to give each view a unique id per the response on this question: Forcing onLayout on React Native view. ie:
<View onLayout={(event) => { this.getItemDimensions(event.nativeEvent.layout) }}
key={this.state.currentItem}>
By doing so, the view is layed out each render call, and so onLayout is called each time as well.
It's unfortunate that this seems to be about the only way to generate the needed height and offset information that getItemLayout() requires to allow ScrollTo operations on FlatLists with variable heights. This solution, while functional, is ultimately far too slow to be useful for me with 18000 list items and given my research hasn't found any alternatives to this point for React-Native, I'm now strongly considering returning my app to pure native development.
来源:https://stackoverflow.com/questions/54561979/problem-iterating-through-onlayout-calls-for-providing-row-heights-of-items-to-f