RecyclerListView和FlashList

RecyclerListView

4.2.0

RecyclerListView

使用示例

/***
 Use this component inside your React Native Application.
 A scrollable list with different item type
 */
import React, { Component } from "react";
import { View, Text, Dimensions } from "react-native";
import { RecyclerListView, DataProvider, LayoutProvider } from "recyclerlistview";

const ViewTypes = {
    FULL: 0,
    HALF_LEFT: 1,
    HALF_RIGHT: 2
};

let containerCount = 0;

class CellContainer extends React.Component {
    constructor(args) {
        super(args);
        this._containerId = containerCount++;
    }
    render() {
        return <View {...this.props}>{this.props.children}<Text>Cell Id: {this._containerId}</Text></View>;
    }
}

/***
 * To test out just copy this component and render in you root component
 */
export default class RecycleTestComponent extends React.Component {
    constructor(args) {
        super(args);

        let { width } = Dimensions.get("window");

        //Create the data provider and provide method which takes in two rows of data and return if those two are different or not.
        //THIS IS VERY IMPORTANT, FORGET PERFORMANCE IF THIS IS MESSED UP
        let dataProvider = new DataProvider((r1, r2) => {
            return r1 !== r2;
        });

        //Create the layout provider
        //First method: Given an index return the type of item e.g ListItemType1, ListItemType2 in case you have variety of items in your list/grid
        //Second: Given a type and object set the exact height and width for that type on given object, if you're using non deterministic rendering provide close estimates
        //If you need data based check you can access your data provider here
        //You'll need data in most cases, we don't provide it by default to enable things like data virtualization in the future
        //NOTE: For complex lists LayoutProvider will also be complex it would then make sense to move it to a different file
        this._layoutProvider = new LayoutProvider(
            index => {
                if (index % 3 === 0) {
                    return ViewTypes.FULL;
                } else if (index % 3 === 1) {
                    return ViewTypes.HALF_LEFT;
                } else {
                    return ViewTypes.HALF_RIGHT;
                }
            },
            (type, dim) => {
                switch (type) {
                    case ViewTypes.HALF_LEFT:
                        dim.width = width / 2;
                        dim.height = 160;
                        break;
                    case ViewTypes.HALF_RIGHT:
                        dim.width = width / 2;
                        dim.height = 160;
                        break;
                    case ViewTypes.FULL:
                        dim.width = width;
                        dim.height = 140;
                        break;
                    default:
                        dim.width = 0;
                        dim.height = 0;
                }
            }
        );

        this._rowRenderer = this._rowRenderer.bind(this);

        //Since component should always render once data has changed, make data provider part of the state
        this.state = {
            dataProvider: dataProvider.cloneWithRows(this._generateArray(300))
        };
    }

    _generateArray(n) {
        let arr = new Array(n);
        for (let i = 0; i < n; i++) {
            arr[i] = i;
        }
        return arr;
    }

    //Given type and data return the view component
    _rowRenderer(type, data) {
        //You can return any view here, CellContainer has no special significance
        switch (type) {
            case ViewTypes.HALF_LEFT:
                return (
                    <CellContainer style={styles.containerGridLeft}>
                        <Text>Data: {data}</Text>
                    </CellContainer>
                );
            case ViewTypes.HALF_RIGHT:
                return (
                    <CellContainer style={styles.containerGridRight}>
                        <Text>Data: {data}</Text>
                    </CellContainer>
                );
            case ViewTypes.FULL:
                return (
                    <CellContainer style={styles.container}>
                        <Text>Data: {data}</Text>
                    </CellContainer>
                );
            default:
                return null;
        }
    }

    render() {
        return <RecyclerListView layoutProvider={this._layoutProvider} dataProvider={this.state.dataProvider} rowRenderer={this._rowRenderer} />;
    }
}
const styles = {
    container: {
        justifyContent: "space-around",
        alignItems: "center",
        flex: 1,
        backgroundColor: "#00a1f1"
    },
    containerGridLeft: {
        justifyContent: "space-around",
        alignItems: "center",
        flex: 1,
        backgroundColor: "#ffbb00"
    },
    containerGridRight: {
        justifyContent: "space-around",
        alignItems: "center",
        flex: 1,
        backgroundColor: "#7cbb00"
    }
};

RecyclerListView继承自父类ComponentCompat(继承自React.Component),其render函数如下

public render(): React.ReactNode {
    if (!this._hasRenderedOnce) {
        this._hasRenderedOnce = true;
        this.componentWillMountCompat();
    } else {
        this.componentWillUpdateCompat();
    }
    return this.renderCompat();
}

renderCompat函数,通过ScrollComponent包裹_generateRenderStack的元素

_generateRenderStack函数,获取ViewRenderer集合

_renderRowUsingMeta函数,返回每行元素ViewRenderer

renderAheadOffset,指定视图渲染多少像素,增加该值有助于减少空白。默认是250。renderAheadOffset为ScrollComponent的属性

ScrollComponent的render函数,返回ScrollView,内层是View,View层内先调用renderContentContainer,后调用renderFooter

renderAheadOffset生效的原理是

  • 定义windowSize,假设为垂直方向,则windowSize为this._height+this.props.renderAheadOffset
  • 然后通过renderContentContainer函数,传入windowSize

ViewRenderer

ViewRenderer继承自BaseViewRenderer

renderCompat里调用的是renderItemContainer函数,为外部传入

Provider

dataProvider,类型DataProvider,定义每个元素的数据

核心方法为cloneWithRows,传入newData

  • 调用newInstance创建一个DataProvider
  • 把newData赋值给_data属性
  • 找到_firstIndexToProcess,通过rowHasChanged判断,这个rowHasChanged是DataProvider初始化的时候传入的

contextProvider,类型ContextProvider

LayoutProvider layoutProvider,类型BaseLayoutProvider,定义每个元素的布局

LayoutManager,管理Layout(x,y,width,height)数组

VirtualRenderer

RecycleItemPool

_recyclePool,RecycleItemPool类型,

RecycleItemPool

  • _recyclableObjectMap,key为对象类型,value为对象的集合,通过_getRelevantSet方法可以取出对象类型对应的集合
  • _availabilitySet,key为object,value为objectType
  • putRecycledObject方法,参数objectType,object
    • 根据objectType从_getRelevantSet方法取出objectSet
    • 如果_availabilitySet没有该object,首先objectSet[object] = null,然后_availabilitySet操作,key为object,value位objectType
  • getRecycledObject
    • 根据传入的objectType,找到objectSet
    • 从objectSet取出一个元素,作为recycledObject
    • 从objectSet中删除该自有属性recycledObject对应的内容,然后删除_availabilitySet该自有属性的对应的内容
    • 最后返回recycledObject
  • removeFromPool
    • 从_recyclableObjectMap删除该object对应的内容
    • 根据传入的object,从_availabilitySet删除对应的内容
  • 提供removeFromPool方法

_recyclePool的使用

首先看getRecycledObject的使用,具体看syncAndGetKey

syncAndGetKey

  • 根据index,从_layoutProvider调用getLayoutTypeForIndex方法获取对应的type,该方法就是Layout初始化传入的index和type的映射
  • 根据type,调用getRecycledObject获取对应的key
  • 根据key,从renderStack中取出itemMeta,itemMeta的dataIndex赋值新的传入的index
  • 最后返回key

而该key是可重用的关键,在_renderRowUsingMeta函数中(也就是渲染每一行的时候),ViewRenderer的key就是该key

onScroll的逻辑 _onScroll的逻辑,这里主要调用VirtualRenderer的updateOffset方法,后面会调用ViewabilityTracker的updateOffset

  • 根据offset,重新赋值_engagedWindow,_visibleWindow
  • 从_visibleIndexes取出第一个元素,赋值给startIndex
  • 调用_fitAndUpdate

_fitAndUpdate的逻辑

_fitAndUpdate

const newVisibleItems: number[] = [];
const newEngagedItems: number[] = [];
this._fitIndexes(newVisibleItems, newEngagedItems, startIndex, true);
this._fitIndexes(newVisibleItems, newEngagedItems, startIndex + 1, false);
this._diffUpdateOriginalIndexesAndRaiseEvents(newVisibleItems, newEngagedItems);

对于isReverse为true的时候,从startIndex到0遍历,调用_checkIntersectionAndReport,若返回true,atLeastOneLocated赋值为true,这里会接下去遍历,直到_checkIntersectionAndReport返回false退出循环。

对于isReverse为false,则从startIndex到count遍历,同样是直到_checkIntersectionAndReport返回false退出

对于_checkIntersectionAndReport方法,传入index(遍历的索引)和insertOnTop(即isReverse)。

  • _itemIntersectsVisibleWindow返回true,如果insertOnTop为true,index分别插入newVisibleIndexes、newEngagedIndexes的顶部,insertOnTop为false,则插入底部
  • 接着判断_itemIntersectsEngagedWindow,若返回true,接着如果insertOnTop为true,index插入newEngagedIndexes顶部,insertOnTop为false,插入newEngagedIndexes底部
  • _itemIntersectsVisibleWindow和_itemIntersectsEngagedWindow调用的都是_itemIntersectsWindow,传入的参数,window分别是_engagedWindow(相对于visibleWindow,前后加上了_renderAheadOffset的大小)、_visibleWindow,以及item的startBound和endBound。_itemIntersectsWindow主要是判断item是否显示在window内。

最终newVisibleItems拿到可见的项目,newEngagedItems拿到可渲染的项目(大于可见)

_diffUpdateOriginalIndexesAndRaiseEvents,这里还需进行diff操作

  • 调用_diffArraysAndCallFunc函数,对比newVisibleItems(新数据)、this._visibleIndexes(老数据)
    • 通过二分查找,找出两个数组中不同的数据,然后调用onVisibleRowsChanged函数
  • 依上的逻辑调用_diffArraysAndCallFunc,对比newEngagedItems、this._engagedIndexes

ProgressiveListView

maxRenderAhead

renderAheadStep 每一帧的增量

renderAheadOffset,初始值

FlashList中对其的使用

maxRenderAhead={3 * finalDrawDistance}
finalRenderAheadOffset={finalDrawDistance}
renderAheadStep={finalDrawDistance}

这里会重写父类的onItemLayout(每个组件的布局回调),这里会在第一个项目布局时触发updateRenderAheadProgressively

FlashList

1.4.0

GridLayoutProvider

GridLayoutProvider来自recyclerlistview

estimatedItemSize

大致的item size,用来FlashList大致推断首屏或者滑动的时候需要渲染多少个item。如果item size 不一,可以用平均值/中位数

在快速滑动的时候,如果estimatedItemSize对比实际大小过大,那么很容易出现空白。

defaultEstimatedItemSize为100

render

render

外层是一个StickyHeaderContainer,里面包裹ProgressiveListView

handleSizeChange,会调用validateListSize,并保存宽高到listFixedDimensionSize,新旧size不同会触发RecyclerListView的rerender

renderContentContainer

这里的内容依次布局PureComponentWrapper,AutoLayoutView(传入的children嵌入到AutoLayoutView内),PureComponentWrapper

renderItemContainer

外层为CellRendererComponent,内层PureComponentWrapper。

PureComponentWrapper的renderer函数,为一个View和Separator组成,View的内层通过rowRendererWithIndex函数返回,这里就是FlashList传入的renderItem

可见性

interface ViewabilityConfig: {
  minimumViewTime: number;
  viewAreaCoveragePercentThreshold: number;
  itemVisiblePercentThreshold: number;
  waitForInteraction: boolean;
}

决策view 可见性的配置

minimumViewTime:默认250

itemVisiblePercentThreshold:item可见的百分比

isItemViewable的逻辑

  • 传入可见的阈值,如果viewAreaCoveragePercentThreshold存在使用该值,否则itemVisiblePercentThreshold
  • 计算百分比
    • 如果通过viewAreaCoveragePercentThreshold判断,分母为listSize.height(如果为垂直方向),分子为pixelsVisible(即item y轴可见的高度)。viewAreaCoveragePercentThreshold为外部传入0-100
    • 如果通过itemVisiblePercentThreshold判断,分母为itemLayout.height,分子pixelsVisible
  • itemLayout的实现为getLayout(index),getLayout为外部传入,实现为this.flashListRef.recyclerlistview_unsafe?.getLayout(index)

AutoLayoutView

CellContainer,CellContainerManager,cell相关,其最重要的属性是index

AutoLayoutView,一个自动布局的UIView,外层的UIScrollView由js侧负责包裹 AutoLayoutViewManager,继承自RCTViewManager,设置相关属性

results matching ""

    No results matching ""