ListView是什么
ListView - 一个核心组件,用于高效地显示一个可以垂直滚动的变化的数据列表。最基本的使用方式就是创建一个
ListView.DataSource
数据源,然后给它传递一个普通的数据数组,再使用数据源来实例化一个ListView
组件,并且定义它的renderRow
回调函数,这个函数会接受数组中的每个数据作为参数,返回一个可渲染的组件(作为listview的每一行)
简单说它就是一个列表组件,用来显示列表视图,类似Android中的ListView,iOS中的UITableView。作为列表组件,ListView是非常常用的。虽然官方文档中指出ListView已经过期,指定FlatList和SectionList替代ListView来使用,但我认为还是有必要对这个控件加深理解,在很多情况下使用ListView还是很方便的。
ListView的相关属性
这里我只列出平常会用的比较多的重要属性加以讲解,其它属性可以查阅官方文档。
dataSource——ListView.DataSource实例,用来设置列表数据源
renderRow——方法,用来渲染列表中每一行,该方法有四个参数(rowData, sectionID, rowID, highlightRow),可以通过传递参数来设置每行的数据,区分不同的section和row。四个参数分别表示数据源中一条数据,分组的ID,行的ID,以及标记是否是高亮选中的状态信息。
有了dataSource和renderRow这两个属性,我们就可以完成一个ListView视图了,这两个属性是ListView最基本的属性,是必须要设置的。
renderHeader——渲染头部视图,以iOS为例,UITableView有一个tableHeaderView属性,可以设置头部视图,这里renderHeader也可以渲染一个自定义的头部视图。
renderFooter——渲染底部视图,类似于UITableView组件的tableFooterView,可以设置一个尾部视图。
renderSectionHeader——为每个section渲染一个粘性的头部视图,类似于UITableView中的sectionHeader视图。
stickySectionHeadersEnabled——sectionHeader是否支持粘性,实际就是是否支持ListView在滑动时当前sectionHeader悬停在上方。这个效果跟UITableView中plain样式的sectionHeader效果一样。
renderSeparator——渲染每行的分割线,通常我不习惯这么做,可以直接在renderRow中给行视图底部设置一个宽度,使用borderBottomWidth和borderColor来实现分割线。
onEndReached——当所有的数据都已经渲染过,并且列表被滚动到距离最底部不足onEndReachedThreshold个像素的距离时调用。这个方法就是我们用来做列表的上拉加载时用到的。
onEndReachedThreshold——这个属性在ListView中是数值,当列表被滚动到底部不足这个值的距离时会触发onEndReached中的方法。在FlatList和SectionList中这个值是比值0~1之间,这里需要区分清楚。
ListView的用法
本篇文章我们使用ListView实现三种不同的列表视图,基本可以涵盖我们日常使用ListView的情况。
第一种,简单列表视图的实现
如图,要实现这样一个列表,我们只需要设置数据源dataSource和renderRow就可以了。
在使用dataSource时,我们需要先new一个dataSource对象,对于单个section的列表,使用rowHasChanged(prevRowData, nextRowData)和cloneWithRows(dataBlob, rowIdentities)来构造数据源。
对于每行视图,我们需要一个image组件来显示图片,一个Text组件显示文本,使用TouchableOpacity组件将图片和文本包裹起来,并给它设置点击事件,renderRow就完成了。
代码如下:
constructor(props) {
super(props);
let ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 });
this.state = {
dataSource: ds.cloneWithRows(data)
}
}
render() {
return (
<View style={styles.container}>
<ListView
dataSource={this.state.dataSource}
renderRow={this._renderRow}
/>
</View>
)
}
_renderRow = (rowData) => {
return (
<TouchableOpacity style={styles.cellContainer} onPress={() => {}}>
<Image source={rowData.image} style={styles.image}/>
<Text style={styles.title}>{rowData.title}</Text>
</TouchableOpacity>
)
}
其中data是一个包含图片和title的数组。通过设置数据源和渲染行视图的方式我们就得到了一个简单的列表。
第二种,网格形式的列表
要实现这样一个Grid Layout的视图,其实很简单,只需要修改下上面的代码。将ListView内容的样式改为横向布局(flexDireaction:'row'),然后支持换行(flexWrap:'wrap'),renderRow中渲染的元素就会横向排列,如果所有元素一行显示不了就自动换行。
render() {
return (
<View style={{ flex: 1}}>
<ListView
contentContainerStyle={styles.listView}
dataSource={this.state.dataSource}
renderRow={this._renderRow}
/>
</View>
)
}
ListView是继承于ScrollView的,所以这里我们可以给它设置内容样式,注意是contentContainerStyle而不是style。
listView: {
flexDirection:'row',
flexWrap:'wrap',
justifyContent:'space-between',
paddingLeft: 20,
paddingRight: 20,
},
然后调整renderRow中图片、文字和容器的样式,一个GridLayout的列表就完成了。
第三种,分组视图,包含单列和网格样式的列表
假设我们有这样一个列表,有好几个分组(section),每个section中的row显示样式不同,有的是单列的,有的是网格形式的。这时候我们就不能简单的像GridLayout那样将ListView的内容样式改变,因为ListView的内容样式改变后里面的所有元素都受到影响,要么全部纵向显示,要么全部横向显示,达不到我们想要的效果。这里有两种方法:
- 对于网格元素的section,此section数据源我们给它设置为只有一条数据的数组,在renderRow中使用一个View将所有子元素包裹起来,使用数组的map方法循环显示网格中的子元素。View的内容样式同GridLayout一样横向排列显示然后换行。分组的ListView数据源格式为
{ sectionID_1: [ rowData1, rowData2, ... ], sectionID_2: [rowData], sectionID_3: [ rowData1, rowData2, ... ], ...}
或者
其中sectionID_2的分组就是要用来显示网格视图的。[[ rowData1, rowData2, ... ], [ rowData], [ rowData1, rowData2, ... ], ...]
- 在要显示网格元素的section中再嵌入一个ListView,方法同GridLayout一样。
以上两种方式其实原理一致,只不过使用控件不同。这部分的实现相对来说稍微复杂点,在Demo中我分了两种情况实现,具体逻辑请看demo。
这里需要说明的是,对于多个section的ListView和单一section的ListView,dataSource的实现是不一样的。
- 单一section
let ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
this.state = { dataSource: ds.cloneWithRows(data)}
- 多个section
let ds = new ListView.DataSource({
rowHasChanged: (r1, r2) => r1 !== r2,
sectionHeaderHasChanged: (s1, s2) => s1 !== s2
});
this.state = { dataSource: ds.cloneWithRowsAndSections(data)}
效果图如下
Demo地址: github.com/mrarronz/re…