跨平台开发RN-组件ListView

组件ListView

  1. ListView就是iOS中的UITabelView
  2. 使用步骤:
    1. 由于ListView在0.60之后已经过期了,被移除react-native框架了,因此导入方法为: import ListView from "deprecated-react-native-listview";
    2. 创建一个 ListView.DataSource 数据源,然后给它传递一个普通的数据数组;
    3. 使用数据源(data source)实例化一个ListView组件,定义一个renderRow回调函数,这个函数会接受数组中的每个数据作为参数,并返回一个可渲染的组件(该就是列表的每一行的item)。
  3. 代码如下:

     import React, {Component} from 'react';
     import {View, StyleSheet, Image, Text} from 'react-native';
     import ListView from 'deprecated-react-native-listview';
        
     //导入包的json数据:[{image: money: name:},{...}]
     //是一个数组
     var wineData = require('./resources/Wine');
        
     class Enty6 extends Component {
       //1.创建一个 ListView.DataSource 数据源,然后给它传递一个普通的数据数组;
       constructor(props) {
         super(props);
         //创建数据源
         var ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 != r2});
         //设置数据
         this.state = {
           dataSource: ds.cloneWithRows(wineData),
         };
       }
       //2. 使用数据源(data source)实例化一个ListView组件,定义一个renderRow回调函数,这个函数会接受数组中的每个数据作为参数,并返回一个可渲染的组件(该就是列表的每一行的item)。
       render() {
         return (
           <ListView
             //使用数据源(`data source`)实例化一个`ListView`组件
             dataSource={this.state.dataSource}
             //定义一个`renderRow`回调函数,这个函数会接受数组中的每个数据作为参数,并返回一个可渲染的组件(该就是列表的每一行的item)。
             renderRow={this.renderRow}
           />
         );
       }
        
       //返回具体的cell,相当于iOS的-(UITableViewCell)tableView...数据源方法
       renderRow(rowData, sectionID, rowID, highlightRow) {
         console.log(sectionID);
         //返回要实现的cell
         return (
           <View>
             <Text>1111</Text>
           </View>
         );
       }
     }
    

ListView常用属性

  1. ScrollView 相关属性样式全部继承
  2. dataSource: ListViewDataSource 设置ListView的数据源
  3. initialListSize:number
    1. 设置ListView组件刚刚加载的时候渲染的列表行数,用这个属性确定首屏或者首页加载的数量,而不是花大量的时间渲染加载很多页面数据,提高性能。
  4. onChangeVisibleRows: function (visibleRows,changedRows)=>void。
    1. 当可见的行发生变化的时候回调该方法。
  5. onEndReachedThreshold: number
    1. 当偏移量达到设置的临界值调用onEndReached
  6. onEndReached function
    1. 当所有的数据项行被渲染之后,并且列表往下进行滚动。一直滚动到距离底部onEndReachedThredshold设置的值进行回调该方法。原生的滚动事件进行传递(通过参数的形式)。
  7. pageSize number
    1. 每一次事件的循环渲染的行数
  8. removeClippedSubviews: bool
    1. 该属性用于提供大数据列表的滚动性能。该使用的时候需要给每一行(row)的布局添加over:’hidden’样式。该属性默认是开启状态。
  9. renderFooter: function 方法 ()=>renderable
    1. 在每次渲染过程中头和尾总会重新进行渲染。如果发现该重新绘制的性能开销比较大的时候,可以使用StaticContainer容器或者其他合适的组件。
  10. renderHeader function
    1. 在每一次渲染过程中Footer(尾)该会一直在列表的底部,header(头)该会一直在列表的头部,用法同上。
  11. renderRow function (rowData,sectionID,rowID,highlightRow)=>renderable
    1. 该方法有四个参数,其中分别为数据源中一条数据,分组的ID,行的ID,以及标记是否是高亮选中的状态信息。
  12. renderScrollComponent function (props)=>renderable
    1. 该方法可以返回一个可以滚动的组件。默认该会返回一个ScrollView
  13. renderSectionHeader function (sectionData,sectionID)=>renderable
    1. 如果设置了该方法,这样会为每一个section渲染一个粘性的header视图。该视图粘性的效果是当刚刚被渲染开始的时候,该会处于对应的内容的顶部,然后开始滑动的时候,该会跑到屏幕的顶端。直到滑动到下一个section的header(头)视图,然后被替代为止。
  14. renderSeparator function (sectionID,rowID,adjacentRowHighlighted)=>renderable
    1. 如果设置该方法,会在被每一行的下面渲染一个组件作为分隔。除了每一个section分组的头部视图前面的最后一行。
  15. scrollRenderAheadDistance number
    1. 进行设置当该行进入屏幕多少像素以内之后就开始渲染该行

ListView的高阶特性

  1. ListView同样支持一些高级特性,包括设置每一组的粘性的头部(类似于iPhone)、支持设置列表的header以及footer视图、当数据列表滑动到最底部的时候支持onEndReached方法回调、设备屏幕列表可见的视图数据发生变化的时候回调onChangeVisibleRows以及一些性能方面的优化特性。
  2. ListView设计的时候,当需要动态加载非常大的数据的时候,下面有一些方法性能优化的方法可以让我们的ListView滚动的时候更加平滑:
    1. 只更新渲染数据变化的那一行 ,rowHasChanged方法会告诉ListView组件是否需要重新渲染当前那一行。
    2. 选择渲染的频率,默认情况下面每一个event-loop(事件循环)只会渲染一行(可以同pageSize自定义属性设置)。这样可以把大的工作量进行分隔,提供整体渲染的性能。

ListView使用案例

案例1,图文列表

  1. 效果如下

    图1

  2. 代码如下:

     import React, {Component} from 'react';
     import {
       View,
       StyleSheet,
       Image,
       Text,
       Dimensions,
       TouchableOpacity,
       Alert,
     } from 'react-native';
     import ListView from 'deprecated-react-native-listview';
        
     //导入包的json数据:[{image: money: name:},{...}]
     //是一个数组
     var wineData = require('./resources/Wine');
     var screenW = Dimensions.get('window').width;
     class Enty6 extends Component {
       //1.创建一个 ListView.DataSource 数据源,然后给它传递一个普通的数据数组;
       constructor(props) {
         super(props);
         //创建数据源
         //当且仅当任意的2个cell内容完全不相等时,才会重新创建cell,相当于iOS的复用功能
         var ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 != r2});
         //设置数据
         this.state = {
           dataSource: ds.cloneWithRows(wineData),
         };
       }
       //2. 使用数据源(data source)实例化一个ListView组件,定义一个renderRow回调函数,这个函数会接受数组中的每个数据作为参数,并返回一个可渲染的组件(该就是列表的每一行的item)。
       render() {
         return (
           <ListView
             //使用数据源(`data source`)实例化一个`ListView`组件
             dataSource={this.state.dataSource}
             //定义一个`renderRow`回调函数,这个函数会接受数组中的每个数据作为参数,并返回一个可渲染的组件(该就是列表的每一行的item)。
             renderRow={this.renderRow}
           />
         );
       }
        
       //3. 返回具体的cell,相当于iOS的-(UITableViewCell)tableView...数据源方法
       renderRow(rowData, sectionID, rowID, highlightRow) {
         //返回要实现的cell
         return (
           //给cell添加点击事件
           <TouchableOpacity
             activeOpacity={0.5}
             onPress={() => {
               Alert.alert('点击了' + rowID + '行');
             }}>
             <View style={styles.cellViewStyle}>
               {/*左边的图片*/}
               <Image source={*uri: rowData.image}} style={styles.leftImageStyle} />
               {/*右边的View*/}
               <View style={styles.rightViewStyle}>
                 {/*上边的文本信息*/}
                 <Text style={styles.topTitleStyle}>{rowData.name}</Text>
        
                 {/*下边的文本信息*/}
                 <Text style={styles.bottomTitleStyle}>¥{rowData.money}</Text>
               </View>
             </View>
           </TouchableOpacity>
         );
       }
     }
        
     const styles = StyleSheet.create({
       cellViewStyle: {
         padding: 10,
         backgroundColor: 'white',
         //cell下划线
         borderBottomWidth: 0.5,
         borderBottomColor: '#e8e8e8',
         //设置主轴方向
         flexDirection: 'row',
       },
       leftImageStyle: {
         width: 60,
         height: 60,
         //举例右边举例
         marginRight: 15,
       },
       rightViewStyle: {
         justifyContent: 'center',
       },
       topTitleStyle: {
         color: 'red',
         fontSize: 15,
         //  自动换行
         width: screenW - 100,
       },
       bottomTitleStyle: {
         color: 'blue',
         marginTop: 8,
       },
     });
        
     export default Enty6;
    

案例2:ListView 实现九宫格

  1. 效果图 图1
  2. 技术点
    1. 通常情况下,我们对ListView的操作是纵向的,如果是横向的,则需要设置ListViewcontentContainerStyle 属性,添加 flexDirection:‘row’ 让多个ListView在同一行显示,而且通过flexWrap:'wrap'进行换行。
  3. 代码实现:

     import React, {Component} from 'react';
     import {
       View,
       StyleSheet,
       Image,
       Text,
       Dimensions,
       TouchableOpacity,
       Alert,
     } from 'react-native';
     import ListView from 'deprecated-react-native-listview';
        
     //导入包的json数据:{data: [{icon: title:},{...}]}
     //是一个数组
     var shareData = require('./resources/shareData');
     var screenW = Dimensions.get('window').width;
     var clos = 3;
     //每个cell的宽高
     var itemW = 80;
     var itemH = itemW + 16;
     //水平间距
     var vmargin = (screenW - itemW * clos) / (clos + 1);
     //垂直间距
     var hmargin = 20;
        
     class Enty7 extends Component {
       //1.创建一个 ListView.DataSource 数据源,然后给它传递一个普通的数据数组;
       constructor(props) {
         super(props);
         //创建数据源
         //当且仅当任意的2个cell内容完全不相等时,才会重新创建cell,相当于iOS的复用功能
         var ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 != r2});
         //设置数据
         this.state = {
           dataSource: ds.cloneWithRows(shareData.data),
         };
       }
       //2. 使用数据源(data source)实例化一个ListView组件,定义一个renderRow回调函数,这个函数会接受数组中的每个数据作为参数,并返回一个可渲染的组件(该就是列表的每一行的item)。
       render() {
         return (
           <ListView
             //使用数据源(`data source`)实例化一个`ListView`组件
             dataSource={this.state.dataSource}
             //定义一个`renderRow`回调函数,这个函数会接受数组中的每个数据作为参数,并返回一个可渲染的组件(该就是列表的每一行的item)。
             renderRow={this.renderRow}
             //修改ListView的contentView的显示方式
             contentContainerStyle={styles.listViewStyle}
           />
         );
       }
        
       //3. 返回具体的cell,相当于iOS的-(UITableViewCell)tableView...数据源方法
       renderRow(rowData, sectionID, rowID, highlightRow) {
         //返回要实现的cell
         return (
           <TouchableOpacity
             activeOpacity={0.5}
             onPress={() => {
               Alert.alert('点击了' + rowID + '行');
             }}>
             <View style={styles.cellViewStyle}>
               <Image source={*uri: rowData.icon}} style={styles.imageStyle} />
               {/*不能直接写rowData.title,因为这么写他是一个常量,加{}就是变量了*/}
               <Text>{rowData.title}</Text>
             </View>
           </TouchableOpacity>
         );
       }
     }
        
     const styles = StyleSheet.create({
       imageStyle: {
         width: 50,
         height: 50,
         marginBottom: 5,
       },
       //修改ListView内容排列方式
       listViewStyle: {
         //修改主轴方向,这cell横向排列
         flexDirection: 'row',
         //让cells换行显示
         flexWrap: 'wrap',
       },
       cellViewStyle: {
         backgroundColor: 'red',
         width: itemW,
         //图标+标题
         height: itemH,
         marginLeft: vmargin,
         marginTop: hmargin,
         //内容居中
         justifyContent: 'center',
         alignItems: 'center',
       },
     });
        
     export default Enty7;
    

案例3:ListView分组展示

  1. 技术点分析:
    1. 在React Native中,ScrollView组件可以使用 stickyHeaderIndices轻松实现sticky(吸顶)效果;而使用ListView组件时,使用stickyHeaderIndices则不生效。
    2. 如何实现滚动时每个section header会吸顶?
      1. 在ListView中要实现sticky,需要使用cloneWithRowsAndSections方法,将dataBlob(object),sectionIDs(array),rowIDs(array)三个值传进去。
    3. 那么dataBlob这3个数据是什么呢?
      1. 先看源数据

         {
           "data": [
             {
               "cars": [
                 {
                   "icon": "m_180_100.png",
                   "name": "AC Schnitzer"
                 },
                 {
                   "icon": "m_92_100.png",
                   "name": "阿尔法·罗密欧"
                 },
                 {
                   "icon": "m_9_100.png",
                   "name": "奥迪"
                 }
               ],
               "title": "A"
             },
             {
               "cars": [
                 {
                   "icon": "m_172_100.png",
                   "name": "巴博斯"
                 },
                 {
                   "icon": "m_157_100.png",
                   "name": "宝骏"
                 }
               ],
               "title": "B"
             },
             {
               "cars": [
                 {
                   "icon": "m_113_100.png",
                   "name": "道奇"
                 },
                 {
                   "icon": "m_165_100.png",
                   "name": "大通"
                 }
               ],
               "title": "C"
             },
             ...
            ]
         }
        
      2. dataBlob数据
        1. 包含ListView所需的所有数据(section header 和 rows),在ListView渲染数据时,使用getSectionDatagetRowData来渲染每一行数据。
        2. 数据如下:

           [
               // 组ID : title
               {"0":"A"},
               {"1":"B"},
               {"2":"C"},
               ...
               //组ID:行ID : 行value
               {
                   "0:0" : {
                          "icon": "m_180_100.png",
                           "name": "AC Schnitzer"
                       }
               },
               {
                   "0:1" : {
                         "icon": "m_92_100.png",
                         "name": "阿尔法·罗密欧"
                       }
               },
               {
                   "0:2" : {
                         "icon": "m_9_100.png",
                         "name": "奥迪"
                       }
               },
               {
                   "1:0" : {
                          "icon": "m_172_100.png",
                           "name": "巴博斯"
                       }
               },
               {
                   "1:1" : {
                         "icon": "m_157_100.png",
                         "name": "宝骏"
                       }
               },
               {
                   "2:0" : {
                          "icon": "m_113_100.png",
                          "name": "道奇""
                       }
               },
               {
                   "3:1" : {
                         "icon": "m_165_100.png",
                         "name": "大通"
                       }
               },
               ...
           ]   
          
        3. 即数据格式为:将一维多key字典数组整合成一维单key字典数组,key值为组ID组ID:行ID
      3. sectionIDs
        1. 就是一个数组存放所有的组ID
        2. 作用:dataBlob[sectionID]取数据
      4. rowIDs
        1. 是一个二维数组,外层长度为Section长度,内层长度为sectionID对应的row长度

           [
               [rowID0,rowID1,rowID2...],
               [rowID0,rowID1,rowID2...],
               ...
           ]
          
  2. 效果图如下:

    图1

  3. 代码示例:

     import React, {Component} from 'react';
     import {
       View,
       StyleSheet,
       Image,
       Text,
       Dimensions,
       TouchableOpacity,
       Alert,
     } from 'react-native';
     import ListView from 'deprecated-react-native-listview';
        
     //导入车的json数据:{data: [{cars:[{icon: name: },...] title:},{...}]}
     //是一个数组
     var carData = require('./resources/Car');
     var screenW = Dimensions.get('window').width;
        
     class Enty8 extends Component {
       //1.创建一个 ListView.DataSource 数据源,然后给它传递一个普通的数据数组;
       constructor(props) {
         super(props);
         //根据sectionID获取组数据
         var getSectionData = (dataBlob, sectionID) => {
           return dataBlob[sectionID];
         };
         //根据sectionID与rowID获取行数据
         var getRowData = (dataBlob, sectionID, rowID) => {
           return dataBlob[sectionID + ':' + rowID];
         };
         //设置数据
         this.state = {
           dataSource: new ListView.DataSource({
             //获取组数据
             getSectionData: getSectionData,
             //获取行中的数据
             getRowData: getRowData,
             //行优化机制
             rowHasChanged: (r1, r2) => r1 != r2,
             //组优化机制
             sectionHeaderHasChanged: (s1, s2) => s1 != s2,
           }),
         };
       }
       //2. 使用数据源(data source)实例化一个ListView组件,定义一个renderRow回调函数,这个函数会接受数组中的每个数据作为参数,并返回一个可渲染的组件(该就是列表的每一行的item)。
       render() {
         return (
           <View style={styles.outViewStyle}>
             <View style={styles.headerViewStyle}>
               <Text style={*color: 'white', fontSize: 25}}>hhhhhhhh</Text>
             </View>
             <ListView
               //使用数据源(`data source`)实例化一个`ListView`组件
               dataSource={this.state.dataSource}
               //定义一个`renderRow`回调函数,这个函数会接受数组中的每个数据作为参数,并返回一个可渲染的组件(该就是列表的每一行的item)。
               renderRow={this.renderRow}
               renderSectionHeader={this.renderSectionHeader}
             />
           </View>
         );
       }
       //复杂操作,数据请求,异步操作
       componentDidMount() {
         this.loadDataFromJson();
       }
        
       //3. 返回具体的cell,相当于iOS的-(UITableViewCell)tableView...数据源方法
       renderRow(rowData, sectionID, rowID) {
         //返回要实现的cell
         return (
           <TouchableOpacity
             activeOpacity={0.5}
             onPress={() => {
               Alert.alert('点击了' + rowID + '行');
             }}>
             <View style={styles.rowViewStyle}>
               <Image source={*uri: rowData.icon}} style={styles.imageStyle} />
               <Text style={*marginLeft: 5}}>{rowData.name}</Text>
             </View>
           </TouchableOpacity>
         );
       }
       //返回section的视图控件
       renderSectionHeader(sectionData, sectionID) {
         return (
           <View style={styles.sectionHeaderStyle}>
             <Text style={*marginLeft: 15, color: 'red', marginTop: 5}}>
               {sectionData}
             </Text>
           </View>
         );
       }
       //整理数据
       //{data: [{cars:[{icon: name: },...] title:},{...}]}
       loadDataFromJson() {
         //拿到json数据
         var jsonData = carData.data;
         //定义一些变量
         var dataBlob = [];
         var sectionIDs = [];
         var rowIDs = [];
         var rcars = [];
        
         for (var i = 0; i < jsonData.length; i++) {
           //1. 把组号放入sectionIDs
           sectionIDs.push(i);
           //2. 把组中内容放入dataBlob中
           dataBlob[i] = jsonData[i].title;
           //3. 取出改组中所有的car
           rcars = jsonData[i].cars;
           rowIDs[i] = [];
           //4. 遍历所有的car
           for (var j = 0; j < rcars.length; j++) {
             //把行号放入rowIDs
             rowIDs[i].push(j);
             //把每一行的内容放入dataBlob中
             dataBlob[i + ':' + j] = rcars[j];
           }
         }
         //更新状态
         this.setState({
           dataSource: this.state.dataSource.cloneWithRowsAndSections(
             dataBlob,
             sectionIDs,
             rowIDs,
           ),
         });
       }
     }
        
     const styles = StyleSheet.create({
       outViewStyle: {
         //占满整个窗口
         flex: 1,
       },
       imageStyle: {
         width: 50,
         height: 50,
       },
       headerViewStyle: {
         height: 64,
         backgroundColor: 'orange',
         justifyContent: 'center',
         alignItems: 'center',
       },
       sectionHeaderStyle: {
         backgroundColor: '#e8e8e8',
         height: 25,
       },
       rowViewStyle: {
         flexDirection: 'row',
         alignItems: 'center',
         padding: 10,
         borderBottomWidth: 1,
         borderBottomColor: '#e8e8e8',
       },
     });
     export default Enty8;
    
Table of Contents