React Native ListView section踩坑记

第一种情况

需求:在不同的section里面展示从同一个数据源获取的不同内容。获取dataSource后,将其转换成对象。 我的项目要求是判断某条数据的截止时间是否小于当前时间,如果小于则属于过期活动,放入过期活动列表,如果大于当前时间,则放入当前活动列表。如下:

GIF5.gif

关于ListView的基础知识就不提了,dataSource里面的数据格式最常用的就是下面两种:

  1. { sectionID_1: [ rowData1, rowData2, ... ], ... }
  2. [ [ rowData1, rowData2, ... ], ... ]

第二种数据格式就是最简单的纯数组格式,使用cloneWithRows()方法
今天我的目标是用第一种数据格式。我将我请求的数据格式写在本地json文件里
test.json:

{
  "data": [
    {
      "id": 1,
      "title": "活动标题2",
      "column_url": "http://upload.s.vidahouse.com/FgMw2dLfy2Ml2mUyodXI8_pxJ7xZ?imageMogr2/crop/!1822.7848101265824x729.1139240506329a48.607594936708864a174.9873417721519/thumbnail/!750x300r",
      "start_time": 1495598400000,
      "end_time": 1496203200000,
      "created_at": 0
    },
    {
      "id": 3,
      "title": "活动标题3",
    
      "column_url": "http://upload.s.vidahouse.com/Fpm7ElJmma6nHU_isG7RgIUcR-Xe?imageMogr2/crop/!1367.0886075949368x546.8354430379746a36.45569620253164a174.9873417721519/thumbnail/!750x300r",
      "start_time": 1494907200000,
      "end_time": 1495512000000,
      "created_at": 0
    },
    {
      "id": 5,
      "title": "活动标题4",
      "column_url": "http://upload.s.vidahouse.com/FgMw2dLfy2Ml2mUyodXI8_pxJ7xZ?imageMogr2/crop/!1822.7848101265824x729.1139240506329a48.607594936708864a174.9873417721519/thumbnail/!750x300r",
      "start_time": 1494907200000,
      "end_time": 1496894400000,
      "created_at": 0
    },
     {
      "id": 7,
      "title": "活动标题4",
      "column_url": "http://upload.s.vidahouse.com/FgMw2dLfy2Ml2mUyodXI8_pxJ7xZ?imageMogr2/crop/!1822.7848101265824x729.1139240506329a48.607594936708864a174.9873417721519/thumbnail/!750x300r",
      "start_time": 1495598400000,
      "end_time": 1695598400000,
      "created_at": 0
    },
  ]
}

如果你们运行了React Native项目,在项目根目录下新建一个test.json,将以上代码拷贝进去,访问http://localhost:8081/test.json 就能访问成功。

ListView定义dataSource:

constructor(props) {
    super(props);
    let ds = new ListView.DataSource({ 
      rowHasChanged: (r1,r2) => r1 !== r2,
      sectionHeaderHasChanged: (s1, s2) => s1 !== s2
     });
    this.state = {
      dataSource: ds,
    };
    this.allActivityData = [];
    this.renderRow = this.renderRow.bind(this);
  }

注意一下如果有section,则还需要加sectionHeaderHasChanged: (s1, s2) => s1 !== s2为数据源赋值。
componentDidMount()生命周期里面请求数据:

componentDidMount() {
   fetch('http://localhost:8081/test.json')
      .then((response) => response.json())
      .then((responseJson) => {
        console.log(responseJson.data)
        this.allActivityData = responseJson.data;
        this.setState({
          dataSource: this.state.dataSource.cloneWithRowsAndSections(this._genRow())
        })
      })
      .catch((error) => {
        console.log(error)
      })
  }

首先,我先将获取的数据传给 allActivityData这个变量,这个变量在_genRow()函数中要用到,注意一下这里我传递的参数是this._genRow(),这个函数做了什么呢?

_genRow() {
    let currentData = [];
    let expiredData = [];
    let data = this.allActivityData;
    let results = {};
    for (let i = 0; i < data.length; i++) {
      if(data[i].end_time > currentTime) {
        currentData.push(data[i]);
      } else {
        expiredData.push(data[i]);
      }
    }
    results = {
      "当前活动": currentData,
      "往期活动": expiredData,
    };
    return results
  }

前面我说了,我获取数据是从一个接口拿,拿到的数据有一个字段end_time,存放的是活动结束时间,格式是时间戳,如果结束时间在当前时间之前,那么活动过期。我定义的两个变量 currentDataexpiredData是分别用于存放过期活动数据和当前活动数据的。格式依然是数组。
重点来了,再看一次有section时,dataSource的数据格式:{ sectionID_1: [ rowData1, rowData2, ... ], ... },在这里,我要做的就是给 currentDataexpiredData加一个头部信息,将返回的数据格式变为对象:

 results = {
      "当前活动": currentData,
      "往期活动": expiredData,
    };
    return results

剩下的,关于如何渲染这些数据,ListView已经帮我们做好了。

 this._renderHeader(sectionData, sectionID)}
/>

renderRow就是用来渲染列表,renderSectionHeader是用来渲染头部信息。

renderRow(rowData, sectionID, rowID) {
    return (
      
        
        
        
      
    )
_renderHeader(sectionData, sectionID) {
    return(
      
        
        {sectionID}
      
    )

  }

注意在_renderHeader()参数里面,sectionData就是renderRowrowDatasectionID才是我们添加的‘往期活动’和‘当前活动’。

第二种情况

需求:头部有一条内容,下面是一个ListView列表,头部的内容可能是一个写死的展示信息而不是真正的数据,我一开始的做法是在最外层加一个ScrollView,里面又嵌套ListView,他们的移动方向都是一样的,当用户手势识别,设备并不知道用户滑动了哪一个组件,这是有问题的。最好的方式是将第一条展示信息加到renderRow()里面,也就是整个页面只有一个ListView。如下:

React Native ListView section踩坑记_第1张图片
2017-06-21_102458.jpg

上方是一个用户信息展示,下方是一个 ListView展示的用户作品,整个页面是可滚动的,我的目的是将用户信息展示放入到 ListViewrenderRow()里面,实现整个页面可滚动,并且没有嵌套滚动。
这里的做法和上一种情况有一些区别,就是第一条内容不是从数据库获取的数据,而只是一个普通的展示 view

constructor(props) {
    super(props);
    let ds = new ListView.DataSource({
      rowHasChanged: (r1,r2) => r1 !== r2,
      sectionHeaderHasChanged: (s1, s2) => s1 !== s2
    })
    this.state = {
      dataSource: ds,
    }
    this.data = []
  }

和上面一样,我要采用的数据格式仍然是一个对象,所以需要有sectionHeaderHasChanged方法,dataSourceListView的数据源。
获取数据:

getActivityWorksCallback(status, response) {
    if(status) {
      this.data = [...this.data, ...response.data]
      this.setState({
        dataSource:         this.state.dataSource.cloneWithRowsAndSections(this._genRow())
      })
    }
  }

this.data是传递给this._genRow()处理的数据。

_genRow() {
    let obj = {
      header: [1],
      body: this.data
    }
    return obj
  }

我将获取的数据放在body中,由于头部的 view里面并不需要数据,所以我这里随意给了一个值给header,我需要的是sectionID,根据sectionID的值去判断渲染头部还是获取的数据。

 this._loadMore()}
          enableEmptySections = {true}
/>

我并没有将头部放在renderHeader()里面,而是直接放入了renderRow()里面,两种方式都是可以的,只不过要注意一下如果放在renderHeader()里面,iOS是会将header粘在顶部的,记得将这个属性值改成false
renderRow()里面:

_renderRow(rowData, sectionID, rowID) {
    if(sectionID == 'header') {
      return(
        {/*头部的视图*/}
      )
    }
    return(
      {/*真正获取的数据视图*/}
    )
GIF2.gif

总结

其实对于我而言之前最大的难点在于我不知道怎么修改我拿到数据的格式,怎么添加字段进去,到目前为止,我从服务器获取的数据都是在一个大的数组里面,而很多页面的需求是顶部有一个静态展示的视图,下方是一个ListView,最好的方式是将第一条展示信息加到dataSourcesectionHeader中,如果你懂得了怎么随心所欲的去修改数据的格式,那么学会用section也就不是难事了。
一起加油吧!

你可能感兴趣的:(React Native ListView section踩坑记)