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"
        }
      ]
    }
  ]
}
View Code

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,
    },
});
View Code

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,
    }
});
View Code

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); //发监听
        }
    }


}
View Code

其中,使用事件监听来实现点击和滑动的监听.

我们使用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);
    }
}
View Code

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