前言

  • 学习本系列内容需要具备一定 HTML 开发基础,没有基础的朋友可以先转至 HTML快速入门(一) 学习

  • 本人接触 React Native 时间并不是特别长,所以对其中的内容和性质了解可能会有所偏差,在学习中如果有错会及时修改内容,也欢迎万能的朋友们批评指出,谢谢

  • 文章第一版出自简书,如果出现图片或页面显示问题,烦请转至 简书 查看 也希望喜欢的朋友可以点赞,谢谢

更新公告:

  • 2017.05.16 —— 根据一些朋友私信我的代码,发现有些错误是文中有一些拼写错误导致,已进行更正,对此造成的不便,请见谅。

ListView组件介绍


  • ListView组件是React Native中一个比较核心的组件,用途非常广,设计初衷就是用来高效的展示垂直滚动的列表数据

  • ListView 继承了 ScrollView 的所有属性

  • 使用步骤:

    • 创建一个ListView.DataSource数据源,然后给它传递一个普通的数组数据
    	getInitialState(){
    // 初始化数据源(rowHasChanged是优化的一种手段,只有当r1 !== r2的时候才会重新渲染)
    var ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
    return{
    // 给dataSource传递一组 数组
    dataSource: ds.cloneWithRows(['内容0', '内容1', '内容2', '内容3', '内容4', '内容5'])
    }
    },
    • 使用数据源实例化一个ListView组件,定义一个renderRow回调函数,这个函数会接受数组中的每个数据作为参数,并返回一个可渲染的组件(也就是该列表的每一行Item)
    	render() {
    return (
    <View style={styles.container}>
    // 根据数据源实例化一个ListView
    <ListView style={{backgroundColor:'yellow'}}
    // 获取数据源
    dataSource={this.state.dataSource}
    // 根据数据源创建一个Item
    // 注:这里的this.renderRow是隐式写法,系统会根据函数的需要,将对应的参数传递过去(共有4个参数:rowData, sectionID, rowID, highlightRow)
    renderRow={this.renderRow}
    />
    </View>
    );
    }, // 返回一个Item
    renderRow(rowData,sectionID,rowID) {
    return(
    // 实例化Item
    <View>
    <Text style={{backgroundColor:'red', height:44}}>内容{rowData},在第{sectionID}组第{rowID}行</Text>
    </View>
    )
    }

    效果:

  • ListView 同样支持一些高级特性,包括设置每一组的粘性的头部、支持设置列表 header 和 footter 视图、当数据列表滑动到最底部的时候支持 onEndReached 方法回调、设备屏幕列表可见的视图数据发生变化的时候回调 onChangeVisibleRows 以及一些性能方面的优化特性

ListView常用属性


  • ScrollView 全部属性

  • dataSource:设置ListView的数据源

  • initialListSize:指定在组件刚挂载的时候渲染多少行数据。用这个属性来确保首屏显示合适数量的数据,而不是花费太多帧逐步显示出来

  • onChangeVisibleRows:((visibleRows, changedRows) => void)当可见的行的集合变化的时候调用此回调函数。visibleRows 以 { sectionID: { rowID: true }}的格式包含了所有可见行,而changedRows 以{ sectionID: { rowID: true | false }}的格式包含了所有刚刚改变了可见性的行,其中如果值为true表示一个行变得可见,而为false表示行刚刚离开可视区域而变得不可见

  • onEndReached:当所有的数据都已经渲染过,并且列表被滚动到距离最底部不足onEndReachedThreshold个像素的距离时调用。原生的滚动事件会被作为参数传递。译注:当第一次渲染时,如果数据不足一屏(比如初始值是空的),这个事件也会被触发

  • onEndReachedThreshold:调用onEndReached之前的临界值,单位是像素

  • pageSize:每次事件循环(每帧)渲染的行数

  • removeClippedSubviews:用于提升大列表的滚动性能。需要给行容器添加样式overflow:'hidden'。(Android已默认添加此样式)此属性默认开启

  • renderFooter:(() => renderable)页头与页脚会在每次渲染过程中都重新渲染(如果提供了这些属性)。如果它们重绘的性能开销很大,把他们包装到一个StaticContainer或者其它恰当的结构中。页脚会永远在列表的最底部,而页头会在最顶部

  • renderHeader: 在每一次渲染过程中Footer(尾)该会一直在列表的底部,header(头)该会一直在列表的头部

  • renderRow:【(rowData, sectionID, rowID, highlightRow) => renderable

    • 从数据源(Data source)中接受一条数据,以及它和它所在section的ID。返回一个可渲染的组件来为这行数据进行渲染。默认情况下参数中的数据就是放进数据源中的数据本身,不过也可以提供一些转换器
    • 如果某一行正在被高亮(通过调用highlightRow函数),ListView会得到相应的通知。当一行被高亮时,其两侧的分割线会被隐藏。行的高亮状态可以通过调用highlightRow(null)来重置
  • renderScrollComponent:【(props) => renderable】指定一个函数,在其中返回一个可以滚动的组件。ListView将会在该组件内部进行渲染。默认情况下会返回一个包含指定属性的ScrollView

  • renderSectionHeader:【(sectionData, sectionID) => renderable】

    • 如果提供了此函数,会为每个小节(section)渲染一个粘性的标题。
    • 粘性是指当它刚出现时,会处在对应小节的内容顶部;继续下滑当它到达屏幕顶端的时候,它会停留在屏幕顶端,一直到对应的位置被下一个小节的标题占据为止
  • renderSeparator:【(sectionID, rowID, adjacentRowHighlighted) => renderable】

    • 如果提供了此属性,一个可渲染的组件会被渲染在每一行下面,除了小节标题的前面的最后一行。在其上方的小节ID和行ID,以及邻近的行是否被高亮会作为参数传递进来
  • scrollRenderAheadDistance:当一个行接近屏幕范围多少像素之内的时候,就开始渲染这一行

  • stickyHeaderIndices(iOS):一个子视图下标的数组,用于决定哪些成员会在滚动之后固定在屏幕顶端。举个例子,传递stickyHeaderIndices={[0]}会让第一个成员固定在滚动视图顶端。这个属性不能和horizontal={true}一起使用

方法


  • getMetrics():导出一些用于性能分析的数据

  • scrollTo(...args):滚动到指定的x, y偏移处,可以指定是否加上过渡动画。

    • 参考 ScrollView#scrollTo.

ListView简单优化建议


  • ListView 设计的时候,当需要动态加载非常大量或者渲染复杂的数据时,下面有一些方法可以提高 ListView 的性能

    • 只渲染更新数据变化的那个Item,rowHasChange方法会告诉ListView组件是否需要重新渲染当前Item
    • 选择渲染的频率,默认情况下,每一个event-loop(事件循环)只会渲染一行(可以同pageSize自定义属性设置)这样可以把大工作量进行分隔,提供整体渲染性能

ListView 基本布局


  • 这边我们就按照下图中的布局实现一个简单的列表数据展示

  • 分析上图整体布局,我这边就将其划分为几个模块,首先需要有个 大的View 来包装内部所有的内容,其次再给标题部分分配一个 小View 以方便维护,具体如下图

  • 接下来就可以开始干活啦~

    • 首先,ListView 需要数据源,那么我们就先来自定义一下数据源的 Json 数据,
    	[
    {"title" : "icon", "img" : "icon"},
    {"title" : "lufei", "img" : "lufei"},
    {"title" : "icon", "img" : "icon"},
    {"title" : "lufei", "img" : "lufei"},
    {"title" : "icon", "img" : "icon"},
    {"title" : "lufei", "img" : "lufei"},
    {"title" : "icon", "img" : "icon"},
    {"title" : "lufei", "img" : "lufei"},
    {"title" : "icon", "img" : "icon"},
    {"title" : "lufei", "img" : "lufei"},
    {"title" : "icon", "img" : "icon"},
    {"title" : "lufei", "img" : "lufei"},
    {"title" : "icon", "img" : "icon"},
    {"title" : "lufei", "img" : "lufei"}
    ]
    • 有了数据后,我们就可以根据数据来实例化 ListView

      • 获取数据
      	var newData = require('./Data/localData.json');
      
      
      • 初始化数据源
      	getInitialState(){
      var ds = new ListView.DataSource({rowHasChanged:(r1, r2) => r1 != r2});
      return{
      // 将获得的数组传递给dataSource
      dataSource : ds.cloneWithRows(newData)
      }
      },
      • 接着就是根据数据源实例化 ListView

        • 视图部分
        	render(){
        return(
        <View style={styles.container}>
        <ListView
        dataSource={this.state.dataSource}
        renderRow={this.renderRow}
        />
        </View>
        );
        }, // 返回一个Item
        renderRow(rowData){
        return(
        <View style={styles.itemStyle}>
        <Image source={{uri:rowData.img}} style={styles.imageStyle}/>
        <View style={styles.subItemStyle}>
        <Text style={{marginTop:5, fontSize:17}}>{rowData.title}</Text>
        <Text style={{marginBottom:5, fontSize:13, color:'green'}}>简介</Text>
        </View>
        </View>
        );
        }
        • 样式部分
        	var styles = StyleSheet.create({
        container: {
        flex:1
        }, itemStyle: {
        // 主轴方向
        flexDirection:'row',
        // 下边框
        borderBottomWidth:1,
        borderBottomColor:'gray'
        }, imageStyle: {
        // 尺寸
        width:60,
        height:60,
        // 边距
        marginLeft:10,
        margin:10
        }, subItemStyle: {
        // 对齐方式
        justifyContent:'space-around'
        }
        });

    效果:

ListView 九宫格布局实现


  • 先来看下大概的布局

  • 从上面可以看出,这个案例是为了实现类似 CollectionView 效果(比如常见的瀑布流),通常情况下,ListView 是纵向排列的,而此案例我们需要它横向排列,那么就需要使用到上面提到的 contentContainerStyle 属性,向里面添加 flexDirection:'row'和 flexWrap:'wrap'` 两个属性

    	contentViewStyle: {
    // 主轴方向
    flexDirection:'row',
    // 换行
    flexWrap:'wrap'
    },
  • 当然了,我们还是需要自定义一组数据供 ListView 使用,这边就使用上面案例的数据

  • 根据数据实例化 ListView ,参考上面案例,这里只粘贴 Item部分,其它的就不重复了

    • 视图部分
    	var ListViewDemo = React.createClass({
    getInitialState(){
    // 初始化数据源
    var ds = new ListView.DataSource({rowHasChanged:(r1, r2) => r1 != r2});
    return{
    dataSource : ds.cloneWithRows(newData)
    }
    }, render(){
    return(
    <ListView
    dataSource={this.state.dataSource}
    renderRow={this.renderRow}
    // 设置contentContainerStyle
    contentContainerStyle={styles.contentViewStyle}
    />
    );
    }, // 返回一个Item
    renderRow(rowData){
    return(
    {/* 实例化Item */}
    <View style={styles.itemStyle}>
    <Image source={{uri:rowData.img}} style={styles.itemImageStyle}/>
    <Text>{rowData.title}</Text>
    </View>
    );
    }
    });
    • 样式部分
    	var styles = StyleSheet.create({
    
    		contentViewStyle: {
    // 主轴方向
    flexDirection:'row',
    // 换行
    flexWrap:'wrap'
    }, itemStyle: {
    // 对齐方式
    alignItems:'center',
    justifyContent:'center',
    // 尺寸
    width:itemWH,
    height:itemWH,
    // 左边距
    marginLeft:vMargin,
    marginTop:hMargin
    }, itemImageStyle: {
    // 尺寸
    width:60,
    height:60,
    // 间距
    marginBottom:5
    }
    });

    效果:

ListView 分组样式的实现分析


  • 在移动设备里面,经常会看到 sticky效果,比如常见的通讯录

  • 在React Native中,ScrollView组件要实现 sticky效果 很简单,只需要使用

    stickyHeaderIndices 就可以了,但对于 ListView 来说,stickyHeaderIndices是无效的,下面我们就来分析怎样才能使 ListView 实现吸顶效果

  • 首先,ListView要实现 sticky效果 需要使用到 cloneWithRowsAndSections 方法将 dataBlob(object), sectionIDs (array), rowIDs (array) 三个值传递出去

    • dataBlob:包含ListView所需的所有数据(section header 和 rows),在ListView渲染数据时,使用getSectionData 和 getRowData 来渲染每一行数据。 dataBlob 的 key 值包含 sectionID + rowId,参考下面模拟的数据结构
    	var dataBlob = {
    'sectionID1' : {section1 data},
    'sectionID1:rowID0' : {row0 data},
    'sectionID1:rowID1' : {row1 data},
    'sectionID2' : {section1 data},
    'sectionID2:rowID0' : {row0 data},
    'sectionID2:rowID1' : {row1 data},
    'sectionID2:rowID2' : {row2 data},
    ...
    };
    • sectionIDs:sectionIDs 用于标识每组section,参考下面模拟的数据结构
    	var sectionIDs = ['sectionID0','sectionID1','sectionID2', ...];
    
    
    • rowIDs:rowIDs 用于描述每个 section 里的每行数据的位置及是否需要渲染。在ListView渲染时,会先遍历 rowIDs 获取到对应的 dataBlob 数据,参考下面模拟的数据结构
    	var rowIDs = [['rowID0', 'rowID1', 'rowID2'...], ['rowID0', 'rowID1', ...], ['rowID0', 'rowID1'], ...];
    
    

ListView 分组样式实现


  • 上面我们大概地分析了下 ListView 实现分组的原理,接下来就根据上面的分析加上实际的案例,来更直观地体验下 ListView分组功能的实现

  • 首先,因为要分组,所以数据肯定比之前的案例使用到的要复杂,但是不用担心,这边会尽量详细地将数组的处理表述出来,先来看下我们需要使用到的数据

    	{
    "data":[
    {
    "title":"A",
    "icons":[
    {
    "icon" : "icon"
    },
    {
    "icon" : "lufei"
    },
    {
    "icon" : "icon"
    }
    ]
    },
    {
    "title":"B",
    "icons":[
    {
    "icon" : "icon"
    },
    {
    "icon" : "lufei"
    },
    {
    "icon" : "icon"
    },
    {
    "icon" : "lufei"
    }
    ]
    },
    {
    "title":"C",
    "icons":[
    {
    "icon" : "icon"
    },
    {
    "icon" : "lufei"
    }
    ]
    },
    {
    "title":"D",
    "icons":[
    {
    "icon" : "icon"
    },
    {
    "icon" : "lufei"
    },
    {
    "icon" : "icon"
    },
    {
    "icon" : "lufei"
    }
    ]
    },
    {
    "title":"E",
    "icons":[
    {
    "icon" : "icon"
    },
    {
    "icon" : "lufei"
    },
    {
    "icon" : "lufei"
    }
    ]
    },
    {
    "title":"F",
    "icons":[
    {
    "icon" : "icon"
    },
    {
    "icon" : "lufei"
    }
    ]
    } ]
    }
  • 结合上面的分析,我们先来初始化数据源

    	getInitialState(){
    // 初始化getSectionData
    var getSectionData = (dataBlob, sectionID) => {
    return dataBlob[sectionID];
    }; // 初始化getRowData
    var getRowData = (dataBlob, sectionID, rowID) => {
    return dataBlob[sectionID + ':' + rowID];
    }; return {
    // 初始化数据源
    dataSource: new ListView.DataSource({
    getSectionData : getSectionData,
    getRowData : getRowData,
    rowHasChanged : (r1, r2) => r1 !== r2,
    sectionHeaderHasChanged : (s1, s2) => s1 !== s2
    })
    }
    },
  • 接着是数组的解析,然后将解析好的数据提供给 dataSource 进行更新,需要注意的是在实际开发中,数据的复杂程度远远要大于我们上面的数据,这是比较耗时的操作,所以我们会选择在异步线程中执行,之前的文章中也提到过 —— 在React Native中,我们一般将耗时复杂的操作放到 componentDidMount 中执行

    	// 耗时、复杂操作放到这里处理
    componentDidMount(){
    // 加载数据
    this.loadData();
    }, // 加载数据
    loadData(){
    // 拿到json数据中的数组
    var jsonData = iconData.data;
    // 定义变量
    var dataBlob = {},
    sectionIDs = [],
    rowIDs = [],
    icons = [];
    // 遍历数组中对应的数据并存入变量内
    for (var i = 0; i<jsonData.length; i++){
    // 将组号存入 sectionIDs 中
    sectionIDs.push(i);
    // 将每组头部需要显示的内容存入 dataBlob 中
    dataBlob[i] = jsonData[i].title;
    // 取出该组所有的 icon
    icons = jsonData[i].icons;
    rowIDs[i] = [];
    // 遍历所有 icon
    for (var j = 0; j<icons.length; j++){
    // 设置标识
    rowIDs[i].push(j);
    // 根据标识,将数据存入 dataBlob
    dataBlob[i + ':' + j] = icons[j];
    }
    }
    // 刷新dataSource状态
    this.setState({ dataSource:this.state.dataSource.cloneWithRowsAndSections(dataBlob, sectionIDs, rowIDs)
    });
    }
  • 最后,就是设置样式,将布局调到我们想要的效果就可以了

    • 视图部分
    	render(){
    return(
    <View style={styles.container}>
    // 实例化顶部View
    <View style={styles.topViewStyle}>
    <Text style={{fontSize:21}}>分组样式</Text>
    </View>
    // 实例化ListView
    <ListView
    dataSource={this.state.dataSource}
    renderRow={this.renderRow}
    renderSectionHeader={this.renderSectionHeader}
    />
    </View>
    );
    }, // 返回一个Item
    renderRow(rowData, sectionID, rowID){
    return(
    <View style={styles.itemStyle}>
    <Image source={{uri:rowData.icon}} style={{width: 60, height:60, marginTop:10, marginLeft:10}}></Image>
    <Text style={{marginTop:15, marginLeft:10}}>示例</Text>
    </View>
    );
    }, // 返回一个SectionHeader
    renderSectionHeader(sectionData, sectionID){
    return(
    <Text style={{backgroundColor:'yellow'}}>{sectionData}</Text>
    );
    },
    • 样式部分
    	var styles = StyleSheet.create({
    container:{
    flex:1
    }, topViewStyle: {
    // 尺寸
    height:44,
    // 边距
    marginTop:20,
    // 对齐方式
    justifyContent:'center',
    alignItems:'center'
    }, itemStyle: {
    // 尺寸
    height:80,
    // 主轴方向
    flexDirection:'row',
    // 下边框
    borderBottomWidth:1,
    borderBottomColor:'gray'
    },
    });

    效果:

React Native之ListView使用的更多相关文章

  1. React Native之ListView实现九宫格效果

    概述 在安卓原生开发中,ListView是很常用的一个列表控件,那么React Native(RN)如何实现该功能呢?我们来看一下ListView的源码 ListView是基于ScrollView扩展 ...

  2. React Native - 5 ListView实现图文混排

    首先在根目录下建一个images文件夹,准备好图片 准备datasource 准备图片资源 准备renderRow方法 记得要import相应的类,ListView, Image, Touchable ...

  3. React Native - 4 ListView 简单使用

    1. 首先要import ListView组件 2. 使用如下代码,注意ListView里的dataSource大小写,我当时把S给小写了,结果花了半个多小时找原因…… 3. 运行结果

  4. React Native的ListView的布局使用

    1> ListView组件用于显示一个垂直的滚动列表,其中的元素之间结构近似而仅数据不同. ListView更适于长列表数据,且元素个数可以增删.和ScrollView不同的是,ListView ...

  5. React Native填坑之旅--ListView篇

    列表显示数据,基本什么应用都是必须.今天就来从浅到深的看看React Native的ListView怎么使用.笔者写作的时候RN版本是0.34. 最简单的 //@flow import React f ...

  6. react native之组织组件

    这些组件包括<TabView>,<NavigatorView>和<ListView>,他们实现了手机端最常用的交互和导航.你会发现这些组件在实际的项目中会非常有用. ...

  7. React Native 二维码扫描组件

    学rn得朋友们,你们知道rn开源项目吗?来吧看这里:http://www.marno.cn/(rn开源项目) React Native学习之路(9) - 注册登录验证的实现 + (用Fetch实现po ...

  8. React Native笔记整理

    判断一个APP页面时原生还是H5:http://www.cnblogs.com/sonice-cinsy/p/5671324.html 写给移动开发者的React Native指南:http://bl ...

  9. React Native的组件ListView

    React Native的组件ListView类似于iOS中的UITableView和UICollectionView,也就是说React Native的组件ListView既可以实现UITableV ...

随机推荐

  1. EntityFramework与TransactionScope事务和并发控制

    最近在园子里看到一篇关于TransactionScope的文章,发现事务和并发控制是新接触Entity Framework和Transaction Scope的园友们不易理解的问题,遂组织此文跟大家共 ...

  2. CSharpGL(17)重构CSharpGL

    CSharpGL(17)重构CSharpGL CSharpGL用起来我自己都觉得繁琐了,这是到了重构的时候. 下载 CSharpGL已在GitHub开源,欢迎对OpenGL有兴趣的同学加入(https ...

  3. php下载文件

    $size=filesize($file);$file=fopen($file, "r");ob_start();header("Content-type: applic ...

  4. WCF学习之旅—WCF服务的WAS寄宿(十二)

    上接    WCF学习之旅—WCF服务部署到IIS7.5(九) WCF学习之旅—WCF服务部署到应用程序(十) WCF学习之旅—WCF服务的Windows 服务程序寄宿(十一) 八.WAS宿主 IIS ...

  5. Python 小白的新手教程(一)

    本文是 python 入门级别的基础知识,包括数据类型和变量.输入输出.字符串和编码.list tuple dict set .条件判断.循环.函数.切片 迭代 列表生成器 生成器 迭代器等. 参考课 ...

  6. ASP.NET MVC系列:Model

    1. Model任务 Model负责通过数据库.AD(Active Directory).Web Service及其他方式获取数据,以及将用户输入的数据保存到数据库.AD.Web Service等中. ...

  7. 【.net 深呼吸】自定义特性(Attribute)的实现与检索方法

    在.net的各个语言中,尤其是VB.NET和C#,都有特性这一东东,具体的概念,大家可以网上查,这里老周说一个非标准的概念——特性者,就是对象的附加数据.对象自然可以是类型.类型成员,以及程序集. 说 ...

  8. 【Win10 应用开发】自定义应用标题栏

    Win 10 app对窗口标题栏的自定义包括两个层面:一是只定义标题中各部分的颜色,如标题栏上文本的颜色.三个系统按钮(最大化,最小化,关闭)的背景颜色等:另一层是把窗口的可视区域直接扩展到标题栏上, ...

  9. 跨域的jsonP

    1.出现原因:因为web中的同源策略(域名,协议,端口号)限制了跨域访问.   2.区别于json (个人理解)json是数据交换格式,jsonp是数据通信中的交互方式   3.jsonp的get与p ...

  10. vue+node+es6+webpack创建简单vue的demo

    闲聊: 小颖之前一直说是写一篇用vue做的简单demo的文章,然而小颖总是给自己找借口,说没时间,这一没时间一下就推到现在了,今天抽时间把这个简单的demo整理下,给大家分享出来,希望对大家也有所帮助 ...