FlatList是一個(gè)高性能的列表組件膳犹,它是ListView組件的升級(jí)版可柿,性能方面有了很大的提升役听,當(dāng)然也就建議大家在實(shí)現(xiàn)列表功能時(shí)使用FlatList,盡量不要使用ListView续扔,更不要使用ScrollView离唬。既然說到FlatList子眶,那就先溫習(xí)一下它支持的功能吓懈。高性能的簡單列表組件蝙眶,支持下面這些常用的功能:
完全跨平臺(tái)。
支持水平布局模式恨樟。
行組件顯示或隱藏時(shí)可配置回調(diào)事件半醉。
支持單獨(dú)的頭部組件。
支持單獨(dú)的尾部組件劝术。
支持自定義行間分隔線缩多。
支持下拉刷新。
支持上拉加載养晋。
支持跳轉(zhuǎn)到指定行(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>
行與行之間的分隔線組件绳泉。 不會(huì)出現(xiàn)在第一行之前和最后一行之后逊抡。
ListEmptyComponent?: ?ReactClass<any> | React.Element<any>
列表為空時(shí)渲染該組件。 可以是React Component, 也可以是一個(gè)render函數(shù)零酪, 或者渲染好的element秦忿。
ListFooterComponent?: ?ReactClass<any>
尾部組件
ListHeaderComponent?: ?ReactClass<any>
頭部組件
columnWrapperStyle?: StyleObj
如果設(shè)置了多列布局(即將numColumns值設(shè)為大于1的整數(shù))麦射,則可以額外指定此樣式作用在每行容器上。
data: ?Array<ItemT>
為了簡化起見灯谣,data屬性目前只支持普通數(shù)組。 如果需要使用其他特殊數(shù)據(jù)結(jié)構(gòu)蛔琅,例如immutable數(shù)組胎许,請(qǐng)直接使用更底層的VirtualizedList組件。
extraData?: any
如果有除data以外的數(shù)據(jù)用在列表中(不論是用在renderItem還是Header或者Footer中)罗售,請(qǐng)?jiān)诖藢傩灾兄付ā?同時(shí)此數(shù)據(jù)在修改時(shí)也需要先修改其引用地址(比如先復(fù)制到一個(gè)新的Object或者數(shù)組中)辜窑,然后再修改其值,否則界面很可能不會(huì)刷新寨躁。
getItem?:
getItemCount?:
getItemLayout?: (data: ?Array<ItemT>, index: number) => {length: number, offset: number, index: number}
getItemLayout是一個(gè)可選的優(yōu)化穆碎,用于避免動(dòng)態(tài)測量內(nèi)容尺寸的開銷,不過前提是你可以提前知道內(nèi)容的高度职恳。 如果你的行高是固定的所禀,getItemLayout用起來就既高效又簡單,類似下面這樣:
getItemLayout={(data, index) => ( {length: 行高, offset: 行高 * index, index} )}
注意如果你指定了SeparatorComponent放钦,請(qǐng)把分隔線的尺寸也考慮到offset的計(jì)算之中色徘。
horizontal?: ?boolean
設(shè)置為true則變?yōu)樗讲季帜J健?/p>
initialNumToRender: number
指定一開始渲染的元素?cái)?shù)量,最好剛剛夠填滿一個(gè)屏幕操禀,這樣保證了用最短的時(shí)間給用戶呈現(xiàn)可見的內(nèi)容褂策。 注意這第一批次渲染的元素不會(huì)在滑動(dòng)過程中被卸載,這樣是為了保證用戶執(zhí)行返回頂部的操作時(shí)颓屑,不需要重新渲染首批元素斤寂。
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
此函數(shù)用于為給定的item生成一個(gè)不重復(fù)的key。 Key的作用是使React能夠區(qū)分同類元素的不同個(gè)體揪惦,以便在刷新時(shí)能夠確定其變化的位置遍搞,減少重新渲染的開銷。 若不指定此函數(shù)丹擎,則默認(rèn)抽取item.key作為key值尾抑。 若item.key也不存在,則使用數(shù)組下標(biāo)蒂培。
legacyImplementation?: ?boolean
設(shè)置為true則使用舊的ListView的實(shí)現(xiàn)再愈。
numColumns: number
多列布局只能在非水平模式下使用,即必須是horizontal={false}护戳。 此時(shí)組件內(nèi)元素會(huì)從左到右從上到下按Z字形排列翎冲,類似啟用了flexWrap的布局。 組件內(nèi)元素必須是等高的——暫時(shí)還無法支持瀑布流布局媳荒。
onEndReached?: ?(info: {distanceFromEnd: number}) => void
當(dāng)列表被滾動(dòng)到距離內(nèi)容最底部不足onEndReachedThreshold的距離時(shí)調(diào)用抗悍。
onEndReachedThreshold?: ?number
決定當(dāng)距離內(nèi)容最底部還有多遠(yuǎn)時(shí)觸發(fā)onEndReached回調(diào)驹饺。 注意此參數(shù)是一個(gè)比值而非像素單位。 比如缴渊,0.5表示距離內(nèi)容最底部的距離為當(dāng)前列表可見長度的一半時(shí)觸發(fā)赏壹。
onRefresh?: ?() => void
如果設(shè)置了此選項(xiàng),則會(huì)在列表頭部添加一個(gè)標(biāo)準(zhǔn)的RefreshControl控件衔沼,以便實(shí)現(xiàn)“下拉刷新”的功能蝌借。 同時(shí)你需要正確設(shè)置refreshing屬性。
onViewableItemsChanged?: ?(info: {viewableItems: Array<ViewToken>, changed: Array<ViewToken>}) => void
在可見行元素變化時(shí)調(diào)用指蚁。 可見范圍和變化頻率等參數(shù)的配置請(qǐng)?jiān)O(shè)置viewabilityconfig屬性
refreshing?: ?boolean
在等待加載新數(shù)據(jù)時(shí)將此屬性設(shè)為true菩佑,列表就會(huì)顯示出一個(gè)正在加載的符號(hào)。
renderItem: (info: {item: ItemT, index: number}) => ?React.Element<any>
根據(jù)行數(shù)據(jù)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 #
請(qǐng)參考ViewabilityHelper的源碼來了解具體的配置稍坯。
三、ListView常用的方法
scrollToEnd(params?: object)
滾動(dòng)到底部搓劫。 如果不設(shè)置getItemLayout屬性的話瞧哟,可能會(huì)比較卡。
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.
如果不設(shè)置getItemLayout屬性的話糟把,無法跳轉(zhuǎn)到當(dāng)前可視區(qū)域以外的位置绢涡。
scrollToItem(params: object)
Requires linear scan through data - use scrollToIndex instead if possible. 如果不設(shè)置getItemLayout屬性的話,可能會(huì)比較卡遣疯。
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
調(diào)用
<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}
/>