React Native之组件FlatList

FlatList是一个高性能的列表组件,它是ListView组件的升级版,性能方面有了很大的提升,当然也就建议大家在实现列表功能时使用FlatList,尽量不要使用ListView,更不要使用ScrollView。既然说到FlatList,那就先温习一下它支持的功能。高性能的简单列表组件,支持下面这些常用的功能:

完全跨平台。
支持水平布局模式。
行组件显示或隐藏时可配置回调事件。
支持单独的头部组件。
支持单独的尾部组件。
支持自定义行间分隔线。
支持下拉刷新。
支持上拉加载。
支持跳转到指定行(ScrollToIndex)

一、FlatList的使用方法

最简单的例子:


this.state = {
error: false,
page: 1,
data:[],
refreshing: false,
loading: false,
};
}

requestData = () => {
const url = 'https://api.github.com/users/futurechallenger/repos';
fetch(url).then(res => {
console.log('started fetch');
return res.json()
}).then(res => {
this.setState({
data: [...this.state.data, ...res],
error: res.error || null,
laoding: false,
refreshing: false,
});
}).catch(err => {
console.log('==> fetch error', err);
this.setState({ error: err, loading: false, refreshing: false});
});
};

handleRefresh = () => {
this.setState({
page: 1,
refreshing: true,
loading: false,
data: [],
}, () => {
this.requestData();
});
};

handleLoadMore = () => {
this.setState({
page: this.state.page + 1,
}, () => {
this.requestData();
});
};

renderItem = (item) => (
<MessageCell item={item} />
)

render() {
return (
<View style={styles.container}>
<Text>Message</Text>
<FlatList
data={this.state.data || []}
renderItem={this.renderItem}
keyExtractor={item => item.id}
refreshing={this.state.refreshing}
onRefresh={this.handleRefresh}
onEndReached={this.handleLoadMore}
onEndReachedThreshold={0} />
</View>
);
}
}


二、FlatList常用的属性

ItemSeparatorComponent?: ?ReactClass<any>

行与行之间的分隔线组件。 不会出现在第一行之前和最后一行之后。

ListEmptyComponent?: ?ReactClass<any> | React.Element<any>

列表为空时渲染该组件。 可以是React Component, 也可以是一个render函数, 或者渲染好的element。

ListFooterComponent?: ?ReactClass<any>

尾部组件

ListHeaderComponent?: ?ReactClass<any>

头部组件
columnWrapperStyle?: StyleObj

如果设置了多列布局(即将numColumns值设为大于1的整数),则可以额外指定此样式作用在每行容器上。

data: ?Array<ItemT>

为了简化起见,data属性目前只支持普通数组。 如果需要使用其他特殊数据结构,例如immutable数组,请直接使用更底层的VirtualizedList组件。

extraData?: any

如果有除data以外的数据用在列表中(不论是用在renderItem还是Header或者Footer中),请在此属性中指定。 同时此数据在修改时也需要先修改其引用地址(比如先复制到一个新的Object或者数组中),然后再修改其值,否则界面很可能不会刷新。

getItem?:

getItemCount?:

getItemLayout?: (data: ?Array<ItemT>, index: number) => {length: number, offset: number, index: number}

getItemLayout是一个可选的优化,用于避免动态测量内容尺寸的开销,不过前提是你可以提前知道内容的高度。 如果你的行高是固定的,getItemLayout用起来就既高效又简单,类似下面这样:

getItemLayout={(data, index) => ( {length: 行高, offset: 行高 * index, index} )}
注意如果你指定了SeparatorComponent,请把分隔线的尺寸也考虑到offset的计算之中。

horizontal?: ?boolean

设置为true则变为水平布局模式。

initialNumToRender: number

指定一开始渲染的元素数量,最好刚刚够填满一个屏幕,这样保证了用最短的时间给用户呈现可见的内容。 注意这第一批次渲染的元素不会在滑动过程中被卸载,这样是为了保证用户执行返回顶部的操作时,不需要重新渲染首批元素。

initialScrollIndex?: ?number

Instead of starting at the top with the first item, start at initialScrollIndex. This disables the "scroll to top" optimization that keeps the first initialNumToRender items always rendered and immediately renders the items starting at this initial index. Requires getItemLayout to be implemented.

keyExtractor: (item: ItemT, index: number) => string

此函数用于为给定的item生成一个不重复的key。 Key的作用是使React能够区分同类元素的不同个体,以便在刷新时能够确定其变化的位置,减少重新渲染的开销。 若不指定此函数,则默认抽取item.key作为key值。 若item.key也不存在,则使用数组下标。

legacyImplementation?: ?boolean

设置为true则使用旧的ListView的实现。

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属性

refreshing?: ?boolean

在等待加载新数据时将此属性设为true,列表就会显示出一个正在加载的符号。

renderItem: (info: {item: ItemT, index: number}) => ?React.Element<any>

根据行数据data渲染每一行的组件。 典型用法:

<FlatList ItemSeparatorComponent={Platform.OS !== 'android' && ({highlighted}) => ( <View style={[style.separator, highlighted && {marginLeft: 0}]} /> )} data={[{title: 'Title Text', key: 'item1'}]} renderItem={({item, separators}) => ( <TouchableHighlight onPress={() => this._onPress(item)} onShowUnderlay={separators.highlight} onHideUnderlay={separators.unhighlight}> <View style={{backgroundColor: 'white'}}> <Text>{item.title}}</Text> </View> </TouchableHighlight> )} />
Provides additional metadata like index if you need it, as well as a more generic separators.updateProps function which let's you set whatever props you want to change the rendering of either the leading separator or trailing sep arator in case the more common highlight and unhighlight (which set the highlighted: boolean prop) are insufficient for your use-case.

viewabilityConfig?: ViewabilityConfig #

请参考ViewabilityHelper的源码来了解具体的配置。

三、ListView常用的方法

scrollToEnd(params?: object)

滚动到底部。 如果不设置getItemLayout属性的话,可能会比较卡。

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 pla ces it at the top, 1 at the bottom, and 0.5 centered in the middle.

如果不设置getItemLayout属性的话,无法跳转到当前可视区域以外的位置。

scrollToItem(params: object)

Requires linear scan through data - use scrollToIndex instead if possible. 如果不设置getItemLayout属性的话,可能会比较卡。

scrollToOffset(params: object)

Scroll to a specific content pixel offset, like a normal ScrollView.

recordInteraction()

Tells the list an interaction has occured, which should trigger viewability calculations, e.g. if waitForInteractions is true and the user has not scrolled. This is typically called by taps on items or by navigation actions.

封装好的上拉下拉加载更多Demo


import React, {PureComponent} from 'react'
import {View, Text, StyleSheet, FlatList, ActivityIndicator, TouchableOpacity, Dimensions} from 'react-native'

export const RefreshState = {
Idle: 0,
HeaderRefreshing: 1,
FooterRefreshing: 2,
NoMoreData: 3,
Failure: 4,
}

const DEBUG = false
const log = (text: string) => {DEBUG && console.log(text)}

const footerRefreshingText = 'Job Loading…'
const footerFailureText = 'Click Reload'
const footerNoMoreDataText = ''

type Props = {
refreshState: number,
onHeaderRefresh: (refreshState: number) => void,
onFooterRefresh?: (refreshState: number) => void,
data: Array<any>,

footerContainerStyle?: any,
footerTextStyle?: any,
}

type State = {}

class RefreshListView extends PureComponent {
props: Props
state: State

componentWillReceiveProps(nextProps: Props) {
log('[RefreshListView] RefreshListView componentWillReceiveProps ' + nextProps.refreshState)
}

componentDidUpdate(prevProps: Props, prevState: State) {
log('[RefreshListView] RefreshListView componentDidUpdate ' + prevProps.refreshState)
}

onHeaderRefresh = () => {
log('[RefreshListView] onHeaderRefresh')

if (this.shouldStartHeaderRefreshing()) {
log('[RefreshListView] onHeaderRefresh')
this.props.onHeaderRefresh(RefreshState.HeaderRefreshing)
}
}

onEndReached = (info: any) => {
log('[RefreshListView] onEndReached ' + info.distanceFromEnd)

if (this.shouldStartFooterRefreshing()) {
log('[RefreshListView] onFooterRefresh')
this.props.onFooterRefresh && this.props.onFooterRefresh(RefreshState.FooterRefreshing)
}
}

shouldStartHeaderRefreshing = () => {
log('[RefreshListView] shouldStartHeaderRefreshing')

if (this.props.refreshState == RefreshState.HeaderRefreshing ||
this.props.refreshState == RefreshState.FooterRefreshing) {
return false
}

return true
}

shouldStartFooterRefreshing = () => {
log('[RefreshListView] shouldStartFooterRefreshing')

let {refreshState, data} = this.props
if (data.length == 0) {
return false
}

return (refreshState == RefreshState.Idle)
}

render() {
log('[RefreshListView] render')

return (
<FlatList
onEndReached={this.onEndReached}
onRefresh={this.onHeaderRefresh}
refreshing={this.props.refreshState == RefreshState.HeaderRefreshing}
ListFooterComponent={this.renderFooter}
{...this.props}
/>
)
}

renderFooter = () => {
let footer = null

let footerContainerStyle = [styles.footerContainer, this.props.footerContainerStyle]
let footerTextStyle = [styles.footerText, this.props.footerTextStyle]
switch (this.props.refreshState) {
case RefreshState.Idle:
footer = (<View style={footerContainerStyle} />)
break
case RefreshState.Failure: {
footer = (
<TouchableOpacity
style={footerContainerStyle}
onPress={() => {
this.props.onFooterRefresh && this.props.onFooterRefresh(RefreshState.FooterRefreshing)
}}
>
<Text style={footerTextStyle}>{footerFailureText}</Text>
</TouchableOpacity>
)
break
}
case RefreshState.FooterRefreshing: {
footer = (
<View style={footerContainerStyle} >
<ActivityIndicator size="small" color="#888888" />
<Text style={[footerTextStyle, {marginLeft: 7}]}>{footerRefreshingText}</Text>
</View>
)
break
}
case RefreshState.NoMoreData: {
footer = (
<View style={footerContainerStyle} >
<Text style={footerTextStyle}>{footerNoMoreDataText}</Text>
</View>
)
break
}
}

return footer
}
}

const styles = StyleSheet.create({
footerContainer: {
flex: 1,
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
padding: 10,
height: 44,
},
footerText: {
fontSize: 14,
color: '#555555'
}
})

export default RefreshListView

调用

<RefreshListView
data={this.state.dataSource}
keyExtractor={item => item.id}
renderItem={({item}) => this.renderItem(item)}
refreshState={this.state.refreshState}
onHeaderRefresh={this.onHeaderRefresh}
onFooterRefresh={this.onFooterRefresh}
onEndReachedThreshold={0.1}
/>
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容

  • 这篇笔记主要包含 Vue 2 不同于 Vue 1 或者特有的内容,还有我对于 Vue 1.0 印象不深的内容。关于...
    云之外阅读 5,040评论 0 29
  • 问答题47 /72 常见浏览器兼容性问题与解决方案? 参考答案 (1)浏览器兼容问题一:不同浏览器的标签默认的外补...
    _Yfling阅读 13,703评论 1 92
  • 不知道自己怎么让原本手上一个小脓包长成了一个肉瘤,必须清除,因为疼痛难忍。 于是一个人经过了五次到校医院后,终于有...
    照亮Br阅读 279评论 5 0
  • 本名 李煜 别称 南唐后主,李后主,词帝 字号 字重光 号钟隐、莲峰居士 所处时代 五代十国...
    额呵额呵额阅读 280评论 0 0
  • 在成都工作的第三天,爸爸收到了来自男盆友爸爸的投诉。他爸爸似乎十分介意我不愿意在他们家里住的事,我也不是很明白,为...
    _lexi_阅读 75评论 0 0