免费开源的iOS开发学习平台

React Native基础:15-ListView组件

ListView组件简介

ListView组件是一个可以在垂直方向滚动的列表组件。从本质来说,ListView组件是对ScrollView组件的定制,优化了组件对内存资源的使用效率,提高了性能。与ScrollView组件不同的是,ListView组件不会把所有元素都立刻渲染出来,其只会渲染屏幕内可见区域的元素,从而节省对硬件资源的消耗。

一般来说,如果需要展示的数据量很大,并且每一行界面元素的结构相同或相似,推荐使用ListView组件来渲染界面。由于手机屏幕的尺寸都不大,因此使用React Native开发的移动应用,基本上都会用到ListView组件。

ListView组件有两个核心属性:dataSource与renderRow,这两个属性必需设置。ListView组件通过dataSource属性获取需要显示的数据,并且通过renderRow获取数据展示的样式。

render() {
  return (
    <ListView
      dataSource={this.state.dataSource}
      renderRow={(rowData) => <Text>{rowData}</Text>}
    />
  );
}

ListViewDataSource介绍

ListViewDataSource类型的属性dataSource为ListView组件提供高性能的数据处理和访问。我们需要调用方法从原始输入数据中抽取数据来创建ListViewDataSource对象,并用其进行数据变更的比较。原始输入数据可以是简单的字符串数组,也可以是复杂嵌套的对象——分不同区(section)各自包含若干行(row)数据。

每次更新datasource中的数据,都需要重新调用cloneWithRows方法(如果用到了section,则对应cloneWithRowsAndSections方法)。数据源中的数据本身是不可修改的,所以请勿直接尝试修改。clone方法会自动提取新数据并进行逐行对比(使用rowHasChanged方法中的策略),这样ListView就知道哪些行需要重新渲染了。

在下方的示例代码中,先通过创建设置rowHasChanged属性来创建一个ListView.DataSource实例。rowHasChanged是ListView.DataSource必需的属性,通过这个属性可以获取哪些行的数据发生了变化,进而可以获取到ListView中的哪些行需要重新渲染。ListView.DataSource的实例ds创建完成后,通过cloneWithRows方法来进行设置数据。cloneWithRows方法会自动提取新数据,并对比数据源检查数据的变化,进而去刷新对应的行。如果dataSource中的数据发生了变化,需要重新调用cloneWithRows方法来设置它的数据。

var ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
var dataSource = ds.cloneWithRows(this._genRows({}));

在ListViewDataSource中,提供了一些用于对ListViewDataSource进行构建以及操作的方法,如下几个比较常用。

  • constructor(params) :可以在此构造函数中针对section标题以及行数据提供自定义的提取方法和hasChanged比对方法。如果不提供,则会使用默认的defaultGetRowData和defaultGetSectionHeaderData方法来提取行数据和section标题。

  • cloneWithRows(dataBlob, rowIdentities) :根据指定的dataBlob和rowIdentities为ListViewDataSource复制填充数据。dataBlob即原始数据。需要在初始化时定义抽取函数,否则使用默认的抽取函数。rowIdentities是一个二维数组,包含了行数据对应的id标识符,例如[['a1', 'a2'], ['b1', 'b2', 'b3'], ...]。如果没有指定此数组,则默认取行数据的key。

  • cloneWithRowsAndSections(dataBlob, sectionIdentities, rowIdentities) :此方法作用基本等同cloneWithRows,区别在于可以额外指定sectionIdentities 。sectionIdentities是包含了section标识符的数组,例如['s1', 's2', ...]。如果没有指定此数组,则默认取section的key。

  • getRowData(sectionIndex, rowIndex) :返回渲染行所需的数据。

  • getSectionHeaderData(sectionIndex) :获取section标题数据。

示例代码

接下来我们实现一个ListView组件的例子,在ListView中会显示100行数据,当用户点击某一行后会在标题上添加"(pressed)"提示。

  • 在终端中执行如下命令,创建ListViewDemo工程;
react-native init ListViewDemo
  • 使用Atom打开工程的index.ios.js文件,编写组件所使用的样式
const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    backgroundColor: '#F5FCFF',
    paddingTop: 20,
  },
  row: {
    flex: 1,
    margin: 10,
  },
});
  • 创建ListView组件,设置其dataSouce和renderRow属性,并实现用户点击每一行后更新显示内容的功能。
export default class ListViewDemo extends Component {
  _pressData = ({}: {[key: number]: boolean})

  constructor(props) {
    super(props);

    this._renderRow = this._renderRow.bind(this);
    this._pressRow = this._pressRow.bind(this);

    var ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
    this.state = {
      dataSource: ds.cloneWithRows(this._genRows({})),
    };
  }

  render() {
    return (
      <View style={styles.container}>
        <ListView
          dataSource={this.state.dataSource}
          renderRow={this._renderRow}
          renderSeparator={this._renderSeparator}
        />
      </View>
    );
  }

  _renderRow(rowData: string, sectionID: number, rowID: number, highlightRow: (sectionID: number, rowID: number) => void) {
    return (
      <TouchableHighlight onPress={() => {
          console.log("this");
          console.log(this);
          this._pressRow(rowID);
          highlightRow(sectionID, rowID);
        }}>
        <View style={styles.row}>
          <Text>
            {rowData}
          </Text>
        </View>
      </TouchableHighlight>
    );
  }

  _pressRow(rowID: number) {
    this._pressData[rowID] = !this._pressData[rowID];
    this.setState({dataSource: this.state.dataSource.cloneWithRows(
      this._genRows(this._pressData)
    )});
  }

  _genRows(pressData: {[key: number]: boolean}): Array<string> {
    var dataBlob = [];
    for (var ii = 0; ii < 100; ii++) {
      var pressedText = pressData[ii] ? ' (pressed)' : '';
      dataBlob.push('Row ' + ii + pressedText);
    }
    return dataBlob;
  }
}
  • 使用import加载项目所使用的模块,并且注册组件ListViewDemo成为整个应用的根容器。
import React, { Component } from 'react';
import {
  AppRegistry,
  StyleSheet,
  Text,
  View,
  ListView,
  TouchableHighlight,
} from 'react-native';

AppRegistry.registerComponent('ListViewDemo', () => ListViewDemo);
  • 在终端中执行下面的命令运行程序,在iOS模拟器中可以看到展示的ListView组件,当我们点击某一行时,改行的文字会发生改变。
react-native run-ios