FlatList
高性能的简单列表组件,支持下面这些常用的功能:
- 完全跨平台。
- 支持水平布局模式。
- 行组件显示或隐藏时可配置回调事件。
- 支持单独的头部组件。
- 支持单独的尾部组件。
- 支持自定义行间分隔线。
- 支持下拉刷新。
- 支持上拉加载。
- 支持跳转到指定行(ScrollToIndex)。
如果需要分组/类/区(section),请使用<SectionList>
。
一个最简单的例子:
<FlatList
data={[{key: 'a'}, {key: 'b'}]}
renderItem={({item}) => <Text>{item.key}</Text>}
/>
本组件实质是基于<VirtualizedList>
组件的封装,因此也有下面这些需要注意的事项:
- 当某行滑出渲染区域之外后,其内部状态将不会保留。请确保你在行组件以外的地方保留了数据。
- 为了优化内存占用同时保持滑动的流畅,列表内容会在屏幕外异步绘制。这意味着如果用户滑动的速度超过渲染的速度,则会先看到空白的内容。这是为了优化不得不作出的妥协,而我们也在设法持续改进。
- 本组件继承自
PureComponent
而非通常的Component
,这意味着如果其props
在浅比较
中是相等的,则不会重新渲染。所以请先检查你的renderItem
函数所依赖的props
数据(包括data
属性以及可能用到的父组件的state),如果是一个引用类型(Object或者数组都是引用类型),则需要先修改其引用地址(比如先复制到一个新的Object或者数组中),然后再修改其值,否则界面很可能不会刷新。(译注:这一段不了解的朋友建议先学习下js中的基本类型和引用类型。) - 默认情况下每行都需要提供一个不重复的key属性。你也可以提供一个
keyExtractor
函数来生成key。
注意:removeClippedSubviews
属性目前是不必要的,而且可能会引起问题。如果你在某些场景碰到内容不渲染的情况(比如使用LayoutAnimation
时),尝试设置removeClippedSubviews={false}
。我们可能会在将来的版本中修改此属性的默认值。
属性
extraData: any
如果有除data
以外的数据用在列表中(不论是用在renderItem
还是Header或者Footer中),请在此属性中指定。同时此数据在修改时也需要先修改其引用地址(比如先复制到一个新的Object或者数组中),然后再修改其值,否则界面很可能不会刷新。
getItemLayout: (data: ?Array<ItemT>, index: number) => {length: number, offset: number, index: number}
getItemLayout
是一个可选的优化,用于避免动态测量内容尺寸的开销,不过前提是你可以提前知道内容的高度。如果你的行高是固定的,getItemLayout
用起来就既高效又简单,类似下面这样:
注意如果你指定了SeparatorComponent
,请把分隔线的尺寸也考虑到offset的计算之中。
initialNumToRender: number
指定一开始渲染的元素数量,最好刚刚够填满一个屏幕,这样保证了用最短的时间给用户呈现可见的内容。注意这第一批次渲染的元素不会在滑动过程中被卸载,这样是为了保证用户执行返回顶部的操作时,不需要重新渲染首批元素。
keyExtractor: (item: ItemT, index: number) => string
此函数用于为给定的item生成一个不重复的key。Key的作用是使React能够区分同类元素的不同个体,以便在刷新时能够确定其变化的位置,减少重新渲染的开销。若不指定此函数,则默认抽取item.key
作为key值。若item.key
也不存在,则使用数组下标。
numColumns: number
多列布局只能在非水平模式下使用,即必须是horizontal={false}
。此时组件内元素会从左到右从上到下按Z字形排列,类似启用了flexWrap
的布局。组件内元素必须是等高的——暂时还无法支持瀑布流布局。
onEndReached?: ?(info: {distanceFromEnd: number}) => void
当列表被滚动到距离内容最底部不足onEndReachedThreshold
的距离时调用。
onEndReachedThreshold?: ?number
决定当距离内容最底部还有多远时触发onEndReached
回调。注意此参数是一个比值而非像素单位。比如,0.5表示距离内容最底部的距离为当前列表可见长度的一半时触发。
onRefresh?: ?() => void
如果设置了此选项,则会在列表头部添加一个标准的RefreshControl
控件,以便实现“下拉刷新”的功能。同时你需要正确设置refreshing
属性。
onViewableItemsChanged?: ?(info: {viewableItems: Array<ViewToken>, changed: Array<ViewToken>}) => void
在可见行元素变化时调用。可见范围和变化频率等参数的配置请设置viewabilityconfig
属性
renderItem: (info: {item: ItemT, index: number}) => ?React.Element<any>
根据行数据data
渲染每一行的组件。典型用法:
除data
外还有第二个参数index
可供使用。
viewabilityConfig: ViewabilityConfig
请参考ViewabilityHelper
的源码来了解具体的配置。
方法
scrollToIndex(params: object)
Scrolls to the item at a the specified index such that it is positioned in the viewable area such that viewPosition
0 places it at the top, 1 at the bottom, and 0.5 centered in the middle.
如果不设置getItemLayout
属性的话,可能会比较卡。
特别注意:
onEndReached:在Android环境下是当滑动到距离底部(xx)距离时触发的,xx就是onEndReachedThreshold:x的设定值。已经滑动到底部后已经不满足距离底部xx的条件,不会再次触发onEndReached()事件的。
因为苹果的可滑动组件可以拉到底部之后让组件距离底部再拉伸一段距离,因此在苹果上可以将xx设置为负数,实现滑动到底部后上拉仍然触发onEndReached();
但是安卓上可滑动组件上拉倒底部之后不能再拉动,因此滑动到底部后再次上拉不会触发onEndReached()。
以下为一个小demo可以直接复制粘贴使用:
import React, {Component} from 'react';
import {
StyleSheet,
View,
FlatList,
Text,
Button,
Dimensions,
TouchableOpacity
} from 'react-native';
const { height, width } = Dimensions.get('window');
var ITEM_HEIGHT = 100;
export default class FlatListExample extends Component {
constructor(props) {
super(props);
this.state = {
refreshing: false
};
let item1 = item;
var txt = '第' + item1.index + '个' + ' title=' + item1.item.title;
var bgColor = item1.index % 2 == 0 ? 'red' : 'blue';
return (
<TouchableOpacity onPress={() => {
alert(txt);
} }>
<Text style={[{ flex: 1, height: ITEM_HEIGHT, backgroundColor: bgColor, width: width / 2 }, styles.txt]}>{txt}</Text>
</TouchableOpacity>
)
}
_header = () => {
return <Text style={[styles.txt, { backgroundColor: 'black' }]}>这是头部</Text>;
}
_footer = () => {
return <Text style={[styles.txt, { backgroundColor: 'black' }]}>这是尾部</Text>;
}
_separator = () => {
return <View style={{ height: 2, backgroundColor: 'yellow' }}/>;
}
_onRefresh() {
alert('正在刷新中.... ');
}
render() {
var data = [];
for (var i = 0; i < 31; i++) {
data.push({ key: i, title: i + '' });
}
return (
<View style={{ flex: 1 }}>
<Button title='滚动到指定位置' onPress={() => {
//this._flatList.scrollToEnd();
//this._flatList.scrollToIndex({viewPosition:0,index:8});
this._flatList.scrollToOffset({ animated: true, offset: 2000 });
} }/>
<View style={{ flex: 1 }}>
<FlatList
ref={(flatList) => this._flatList = flatList}
ListHeaderComponent={this._header}
ListFooterComponent={this._footer}
ItemSeparatorComponent={this._separator}
renderItem={this._renderItem}
numColumns ={2}
columnWrapperStyle={{ borderWidth: 2, borderColor: 'black' }}
refreshing={this.state.refreshing}
getItemLayout={(data, index) => (
{ length: ITEM_HEIGHT, offset: (ITEM_HEIGHT + 2) * index, index }
) }
onRefresh={this._onRefresh}
onEndReachedThreshold={0.1}
onEndReached={(info) => {
alert("滑动到底部了");
} }
onViewableItemsChanged={(info) => {
// alert("可见不可见触发");
} }
data={data}>
</FlatList>
</View>
</View>
);
}
}
const styles = StyleSheet.create({
txt: {
textAlign: 'center',
textAlignVertical: 'center',
color: 'white',
fontSize: 30,
}
});
module.exports = FlatListExample;