【问题标题】:Problem iterating through onLayout calls for providing row heights of items to FlatList's getItemLayout遍历 onLayout 调用以向 FlatList 的 getItemLayout 提供项目的行高时出现问题
【发布时间】:2019-02-06 20:24:36
【问题描述】:

我有一个 FlatList,其中填充了大约 18000 个文本行。因为每一行中的文本是可变长度的,所以我无法提供静态高度供 getItemLayout 使用。

我正在尝试做的是首先使用一个虚拟视图遍历整个数据集,我通过 onLayout 从中收集高度维度。我的问题是,目前,视图在停止之前只重新渲染了大约 10 次。据我所知,对于最终出现在屏幕上的最后一个视图,根本不会调用 onLayout —— 这又是第 10 个(或者大约,取决于我遍历数据的时间间隔)。

下面是我的代码,我在这里尝试对其进行了一些简化。目前只是试图找到一种方法来迭代并预先计算整个数据集的视图高度,然后在单独的组件中进行渲染。

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);

我希望我的代码在每次渲染视图时都会调用 getItemDimensions() 函数,该函数本身会调整 this.state.currentItem 的状态,这种状态变化会导致触发 render() 函数再次。

实际行为是它执行上述操作,但次数有限(比我需要的 18000 更接近 10 次)。 onLayout 不会调用 getItemDimensions(),因此不会获得尺寸,并且 currentItem 的状态不会得到高级 - 因此渲染停止。我不明白为什么对于屏幕上显示的最后一个视图,onLayout 没有调用 getItemDimensions。

是否有人对迭代停止的原因或实现目标的更有效方法有任何见解?我需要向我的 FlatList 提供 getItemLayout,因为在用户手动滚动整个 FlatList 之前,应用程序需要能够 ScrollToIndex。

【问题讨论】:

    标签: react-native react-native-flatlist


    【解决方案1】:

    没有调用 onLayout 的问题是因为 onLayout 不会发生,除非之前的布局和替换它的布局之间存在差异。在我的例子中,当两个具有相同高度的视图一个接一个时,由于 onLayout 不再被调用,迭代将停止。

    简单的解决方案是根据对此问题的回复为每个视图提供一个唯一的 ID:Forcing onLayout on React Native view。即:

    <View onLayout={(event) => { this.getItemDimensions(event.nativeEvent.layout) }} 
        key={this.state.currentItem}>
    

    通过这样做,每次渲染调用都会对视图进行布局,因此每次也会调用 onLayout。

    不幸的是,这似乎是生成 getItemLayout() 所需的高度和偏移信息的唯一方法,以允许在具有可变高度的 FlatLists 上进行 ScrollTo 操作。这个解决方案虽然功能强大,但最终对我来说太慢了,无法使用 18000 个列表项,并且鉴于我的研究还没有找到 React-Native 的任何替代方案,我现在强烈考虑将我的应用程序恢复为纯原生发展。

    【讨论】:

    • 你好? 非常有趣的调查。虽然我认为去本地不会有帮助。例如,测量某些文本的边界框(坐标、宽度、高度)的唯一方法是渲染它。
    • 你的问题终于解决了吗?以及如何?
    • 当时我在这里用RN无法解决性能问题,并确实回到了Android原生开发。虽然边界框很可能只能通过渲染来测量,但我认为受限于 RN 的单个 JavaScript 线程,这在当时对我来说是不可用的。我很好奇 RN 是否在这方面有所改进,或者其他人是否找到了提高具有不规则大小项目的 FlatLists 性能的方法。
    • 一个有趣的讨论:RN中的线程:react-native.canny.io/feature-requests/p/…
    • 你可以试试这个新颖的多线程库github.com/mrousavy/react-native-multithreading
    猜你喜欢
    • 2018-07-18
    • 2019-12-10
    • 1970-01-01
    • 1970-01-01
    • 2019-04-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多