React-Native在0.43推出了两款新的列表组件:FlatList(高性能的简单列表组件)和SectionList(高性能的分组列表组件).
从官方上它们都支持常用的以下功能:
- 完全跨平台。
- 支持水平布局模式。
- 行组件显示或隐藏时可配置回调事件。
- 支持单独的头部组件。
- 支持单独的尾部组件。
- 支持自定义行间分隔线。
- 支持下拉刷新。
- 支持上拉加载。
其中,SectionList适合分组/类/区,但是在0.43版本中,如果希望section的头部能够吸顶悬浮,请暂时先使用老版的<ListView>.
它们都是基于<VirtualizedList>
组件的封装(不同于ListView,ListView是继承自ScrollView,这意味着ListView可以使用所有ScrollView的属性,但是不带重用,性能稍微不足,也就是说FlatList.SectionList这两款组件和ListView,ScrollView没啥关系,而ListView和ScrollView是父子关系),.所以需要注意几点.详细的请在官方浏览.其中有一点必须注意:在使用时,默认情况下每行都需要提供一个不重复的key属性.也可以提供一个keyExtractor
函数来动态绑定数据源中的id等其他不唯一的数据。如果不绑定会报一个警告:
接下里使用这两个组件写一个demo:列表组件的联动(ps:其实个人感觉使用ListView实现更加方便.也更易扩展)
数据源我们采用本地数据:
{ "food_spu_tags":[ { "title":"1", "data":[ { "name":"一 nghnh", "key":"1" }, { "name":"一 tyui22uyt", "key":"2" }, { "name":"一 3fdsfdga", "key":"3" } ] }, { "title":"2", "data":[ { "name":"二 fsd", "key":"4" }, { "name":"二 gfdh", "key":"5" }, { "name":"二 ghdsfd", "key":"6" }, { "name":"二 hkjhg", "key":"7" }, { "name":"二 oiuytre", "key":"8" }, { "name":"二 phfd", "key":"9" } ] }, { "title":"3", "data":[ { "name":"三 pknbv", "key":"10" }, { "name":"三 qazxsef", "key":"11" }, { "name":"三 plmnbgf", "key":"12" }, { "name":"三 ggggg", "key":"13" }, { "name":"三 gfd", "key":"14" }, { "name":"三 fgh", "key":"15" }, { "name":"三 hhf", "key":"16" }, { "name":"三 jff", "key":"17" }, { "name":"三 sfgd", "key":"18" }, { "name":"三 dffhsd", "key":"19" }, { "name":"三 ghd", "key":"20" }, { "name":"三 ghsg", "key":"21" } ] }, { "title":"4", "data":[ { "name":"四 ghs", "key":"22" }, { "name":"四 hth", "key":"23" } ] }, { "title":"5", "data":[ { "name":"五 teh", "key":"24" }, { "name":"五 thtr", "key":"25" }, { "name":"五 thereth", "key":"26" }, { "name":"五 yefdgs", "key":"27" }, { "name":"五 htweh", "key":"28" }, { "name":"五 thrhwt", "key":"29" }, { "name":"五 geheht", "key":"30" }, { "name":"五 thwtw", "key":"31" } ] }, { "title":"6", "data":[ { "name":"六 thsfsg", "key":"32" }, { "name":"六 thwfs", "key":"33" }, { "name":"六 htsfd", "key":"34" } ] }, { "title":"7", "data":[ { "name":"七 hgshfd", "key":"35" } ] }, { "title":"8", "data":[ { "name":"八 rgdsgsfd", "key":"36" }, { "name":"八 grht", "key":"37" }, { "name":"八 htrfss", "key":"38" }, { "name":"八 thsgfd", "key":"39" }, { "name":"八 hthe", "key":"40" }, { "name":"八 trgtsf", "key":"41" }, { "name":"八 f45f", "key":"42" }, { "name":"八 4qtq", "key":"43" }, { "name":"八 43f", "key":"44" }, { "name":"八 43ff", "key":"45" }, { "name":"八 45gwrsfd", "key":"46" } ] }, { "title":"9", "data":[ { "name":"九 43qgf", "key":"47" }, { "name":"九 ref3", "key":"48" }, { "name":"九 54sf", "key":"49" } ] }, { "title":"10", "data":[ { "name":"十 43refsd", "key":"50" }, { "name":"十 43refzd", "key":"51" }, { "name":"十 4q3gfd", "key":"52" }, { "name":"十 wgf", "key":"53" }, { "name":"十 4q3fs", "key":"54" } ] }, { "title":"11", "data":[ { "name":"十一 wrf", "key":"55" }, { "name":"十一 5ersf", "key":"56" }, { "name":"十一 43fs", "key":"57" }, { "name":"十一 43fs", "key":"58" }, { "name":"十一 5gs", "key":"59" }, { "name":"十一 w5gfsd", "key":"60" }, { "name":"十一 4qrgfs", "key":"61" } ] }, { "title":"12", "data":[ { "name":"十二 4wgfsd", "key":"62" }, { "name":"十二 w5gfsd", "key":"63" }, { "name":"十二 4qgfsgf", "key":"64" }, { "name":"十二 3qgsf", "key":"65" } ] } ] }
1.新建个主类放置左右两个列表组件(左边的FlatList右边的SectionList)
/** * Sample React Native App * https://github.com/facebook/react-native * @flow */ import React, { Component } from 'react'; import { AppRegistry, StyleSheet, Text, View } from 'react-native'; import LeftFlatList from './leftFlatList' import RightSectionList from './RightSectionList' import linkageData from './linkage.json' export default class Main extends Component { render() { return ( <View style={{flexDirection:'row'}}> <LeftFlatList data = {linkageData}/> <RightSectionList data = {linkageData}/> </View> ); } } const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#F5FCFF', }, welcome: { fontSize: 20, textAlign: 'center', margin: 10, }, instructions: { textAlign: 'center', color: '#333333', marginBottom: 5, }, });
2.左边的FlatList,key采用keyExtractor函数绑定,就是数据源中title.
/** * Created by shaotingzhou on 2017/6/22. */ import React, { Component } from 'react'; import { AppRegistry, StyleSheet, Text, View, Image, TouchableOpacity, Platform, Dimensions, RefreshControl, FlatList, ActivityIndicator, DeviceEventEmitter, ScrollView } from 'react-native'; var {width,height} = Dimensions.get('window'); var dataAry = [] export default class LeftFlatList extends Component{ // 构造 constructor(props) { super(props); dataAry = this.props.data.food_spu_tags this.state = { dataAry: dataAry, cell:0 //默认选中第一行 }; } render() { return ( <FlatList ref='FlatList' style={{width:80}} data = {this.state.dataAry} //数据源 renderItem = {(item) => this.renderRow(item)} //每一行render ItemSeparatorComponent = {()=>{return(<View style={{height:1,backgroundColor:'cyan'}}/>)}} //分隔线 keyExtractor={this.keyExtractor} //使用json中的title动态绑定key /> ); } //使用json中的title动态绑定key keyExtractor(item: Object, index: number) { return item.title } //每一行render renderRow =(item) =>{ return( <TouchableOpacity onPress={()=>this.cellAction(item)}> <View style={{height:60,flexDirection:'row',alignItems:'center'}}> <View style={{height:50,width:5,backgroundColor: item.index == this.state.cell ? 'red' : 'rgba(0,0,0,0)'}}/> <Text style={{marginLeft:20}}>{item.item.title}</Text> </View> </TouchableOpacity> ) } //点击某行 cellAction =(item)=>{ // alert(item.index) if(item.index < this.state.dataAry.length - 1){ this.setState({ cell:item.index }) DeviceEventEmitter.emit('left',item.index); //发监听 } } componentWillUnmount(){ // 移除监听 this.listener.remove(); } componentWillMount() { this.listener = DeviceEventEmitter.addListener('right',(e)=>{ this.refs.FlatList.scrollToIndex({animated: true, index: e-1}) this.setState({ cell:e-1 }) }); } }; var styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#F5FCFF', }, welcome: { fontSize: 20, textAlign: 'center', margin: 10, }, instructions: { textAlign: 'center', color: '#333333', marginBottom: 5, } });
3.右边的SectionList,key采用数据源中id来绑定.
/** * Created by shaotingzhou on 2017/6/22. */ import React, {Component} from 'react'; import { StyleSheet, View, Text, SectionList, Dimensions, DeviceEventEmitter, ScrollView } from 'react-native'; var {width,height} = Dimensions.get('window'); var sectionData = [] export default class RightSectionList extends Component { // 构造 constructor(props) { super(props); sectionData = this.props.data.food_spu_tags this.state = { sectionData:sectionData }; } //行 renderItem = (item) => { return ( <View style={{height:60,justifyContent:'center',marginLeft:15}}> <Text>{item.item.name}</Text> </View> ) } //头 sectionComp = (section) => { return ( <View style={{height:30,backgroundColor:'#DEDEDE',justifyContent:'center',alignItems:'center'}}> <Text >{section.section.title}</Text> </View> ) } render() { return ( <SectionList ref='sectionList' style={{width:width-80}} renderSectionHeader={(section)=>this.sectionComp(section)} //头 renderItem={(item)=>this.renderItem(item)} //行 ItemSeparatorComponent = {()=>{return(<View style={{height:1,backgroundColor:'black'}}/>)}}//分隔线 sections={this.state.sectionData} //数据 onViewableItemsChanged = {(info)=>this.itemChange(info)} //滑动时调用 /> ); } componentDidMount() { //收到监听 this.listener = DeviceEventEmitter.addListener('left',(e)=>{ // console.log(e + 1) // 左边点击了第几行 // console.log(sectionData) // 数据源 // console.log(sectionData[e]) // console.log(sectionData[e].data.length) // SectionList实现scrollToIndex需要修改VirtualizedSectionList和SectionList源码 if(e > 0){ //计算出前面有几行 var count = 0 for(var i = 0; i < e; i++){ count += sectionData[i].data.length +1 } this.refs.sectionList.scrollToIndex({animated: true, index: count}) }else { this.refs.sectionList.scrollToIndex({animated: true, index: 0}) //如果左边点击第一行,右边则回到第一行 } }); } componentWillUnmount(){ // 移除监听 this.listener.remove(); } itemChange = (info)=>{ let title = info.viewableItems[0].item.title var reg = new RegExp("^[0-9]*$"); if (reg.test(title)) { DeviceEventEmitter.emit('right',title); //发监听 } } }
其中,使用事件监听来实现点击和滑动的监听.
我们使用scrollToIndex来移动.但是呢,FlatList对VirtualizedList封装的时候有添加这个方法,而SectionList并没有(why?).无奈自己修改下它的源码.
a.在node_modules/react-native/Libraries/Lists/SectionList.js 下修改 250-310行代码为
class SectionList<SectionT: SectionBase<any>> extends React.PureComponent<DefaultProps, Props<SectionT>, void> { props: Props<SectionT>; static defaultProps: DefaultProps = defaultProps; render() { const List = this.props.legacyImplementation ? MetroListView : VirtualizedSectionList; return <List ref={this._captureRef} {...this.props} />; } _captureRef = (ref) => { this._listRef = ref; }; scrollToIndex = (params: { animated?: ?boolean, index: number, viewPosition?: number }) => { this._listRef.scrollToIndex(params); } }
b.在node_modules/react-native/Libraries/Lists/VirtualizedSectionList.js 下的335下面增加
scrollToIndex = (params: { animated?: ?boolean, index: number, viewPosition?: number }) => { this._listRef.scrollToIndex(params); }
修改后完整源码见:SectionList.js VirtualizedSectionList.js.
OK.修改完成后就可以实现点击左联右了.
而右联左,通过SectionList的onViewableItemsChanged属性实现.
以后就是关于FlatList和SectionList的学习demo.
再说一遍,实现联动组件最好使用ListView.因为现阶段官方推出的FlatList和SectionList的方法较少,bug较多.
demo源码github:https://github.com/pheromone/RN-FlatList-SectionList