React Native学习之ListView组件
ListView组件
- Android 原生 ListView 实现(MVC方式):
ListView Adapter List
集合数据 - iOS 原生 ListView 实现:
UITableView(sectionId、rowId、rowData)
ListView组件是React Native中一个比较核心的组件,用途非常的广。该组件设计用来高效的展示垂直滚动的数据列表。
ListView最简单的API就是创建一个ListView.DataSource对象,同时给该对象传入一个简单的数据集合。并且使用数据源(data source)实例化一个ListView组件,定义一个renderRow回调方法(该方法的参数是一个数组),该renderRow方法会返回一个可渲染的组件(即列表的每一行的item)。
由于平台差异性,React Native 中的滚动列表组件 ListView 并没有直接映射为 Android 中的 ListView 或 iOS 中的 UITableView,而是在ScrollView 的基础上使用 JS 做了一次封装。滚动体验部分由 Native 负责,而 React 部分则专注于组件何时渲染、如何渲染等问题。
ListView 数据源结构
数据源默认的格式有三个维度:
- 第一个维度是 sectionId ,标识属于哪一段, 可以手动指定或隐式地使用数组索引或对象的 key 值;
- 第二个维度是 rowId ,标识某个数据段下的某一个行,同样可以手动指定或隐式地使用数组索引或对象的 key 值;
- 第三个维度是具体的数据对象,根据实际的需要而定。
需要注意的是,以上只是默认的数据格式 ,如果不符合实际的需求, 完全可以使用自定义的数据结构。唯一的区别就是需要额外指定给 ListView 数据源中哪些是 id,哪些是 rowData。
DataSource 数据源
DataSource 的构造函数接收以下4个参数(都是可选):
- rowHasChanged:用于在数据变化的时候,计算出变化的部分,在更新时只渲染脏数据;
- sectionHeaderHasChanged:同理,在列表带分段标题时需要实现;
- getRowData/getSectionHeaderData: 如果遵循默认的数据源格式,这两个方法就没有必要实现,用内部默认的即可;而当数据源格式是自定义时,需要手动实现这两个方法。
DataSource 的初始化一般在 constructor 方法中
数据源确定后,下一个工作就是列表的渲染。在渲染时发挥重要作用的是 renderRow 属性,它接收数据源中保存的数据对象,并通过返回值确定该行该如何进行展现。我们可以对所有行统一进行展现,也可以根据里面的字段做出不同的展现。在列表包含 sectionHeader 时,还需要实现 renderSectionHeader 方法。
注意:要更新datasource中的数据,请(每次都重新)调用cloneWithRows方法(如果用到了section,则对应调用cloneWithRowsAndSections方法)。数据源中的数据本身是不可修改的,所以请勿直接尝试修改。clone方法会自动提取新数据并进行逐行对比(使用rowHasChanged方法中的策略),这样ListView就会知道哪些行需要重新渲染。
ListView组件基础范例
范例1:创建并初始化ListView方式一
/**
* Sample React Native App
* https://github.com/facebook/react-native
* @flow
*/
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
ListView,
Image,
View
} from 'react-native';
class RNAPP extends Component {
// 初始化模拟数据
constructor(props) {
super(props);
const dataSource = new ListView.DataSource({rowHasChanged: (row1, row2) => row1 !== row2});
this.state = {
dataSource: dataSource.cloneWithRows([
'John', 'Joel', 'James', 'Jimmy', 'Jackson', 'Jillian', 'Julie', 'Devin'
])
};
}
render() {
return (
<View style={{paddingTop: 22}}>
<ListView
dataSource = {this.state.dataSource}
renderRow = {(rowData) => <Text>{rowData}</Text>}
/>
</View>
);
}
}
AppRegistry.registerComponent('RNAPP', () => RNAPP);
范例2:创建并初始化ListView方式二
/**
* Sample React Native App
* https://github.com/facebook/react-native
*/
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
ScrollView,
Image,
ListView,
TouchableHighlight,
RecyclerViewBackedScrollView,
View,
} from 'react-native';
export default class RNAPP extends Component {
constructor(props) {
super(props);
this.state = {
dataSource:new ListView.DataSource({
rowHasChanged:(row1,row2)=> row1 !== row2,
}),
};
}
componentWillMount() {
this.dsfetchData();
}
render() {
return(
<ListView
dataSource = {this.state.dataSource}
renderRow = {(rowData) => <Text>{rowData}</Text>}
style = {styles.listView}
/>
);
}
dsfetchData(){
this.setState({
dataSource: this.state.dataSource.cloneWithRows(['John', 'Joel', 'James', 'Jimmy', 'Jackson', 'Jillian', 'Julie', 'Devin']),
});
}
}
const styles = StyleSheet.create({
listView: {
paddingTop: 20,
backgroundColor: '#F5FCFF',
},
});
AppRegistry.registerComponent('RNAPP', () => RNAPP);
范例3:ListView数据源结构
/**
* Sample React Native App
* https://github.com/facebook/react-native
*/
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
ScrollView,
Image,
ListView,
TouchableHighlight,
RecyclerViewBackedScrollView,
View,
} from 'react-native';
export default class RNAPP extends Component{
constructor(props) {
super(props);
this.state = {
dataSource:new ListView.DataSource({
rowHasChanged:(row1,row2)=> row1 !== row2,
}),
};
}
render() {
return(
<ListView
dataSource = {this.state.dataSource}
renderRow = {this._renderRow}
style = {styles.listView}
/>
);
}
_renderRow(rowData, sectionID, rowID) {
return (
<View>
<Text>sectionID: {sectionID}</Text>
<Text>rowID: {rowID}</Text>
<Text>rowData: {rowData}</Text>
</View>
);
}
componentDidMount() {
this.dsfetchData();
}
dsfetchData() {
this.setState({
dataSource: this.state.dataSource.cloneWithRows(['John', 'Joel', 'James', 'Jimmy', 'Jackson', 'Jillian', 'Julie', 'Devin']),
});
}
}
const styles = StyleSheet.create({
listView: {
paddingTop: 20,
backgroundColor: '#F5FCFF',
},
});
AppRegistry.registerComponent('RNAPP', () => RNAPP);
范例4:ListView数据填充
/**
* Sample React Native App
* https://github.com/facebook/react-native
*/
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
ScrollView,
Image,
ListView,
TouchableHighlight,
RecyclerViewBackedScrollView,
View,
} from 'react-native';
//var MOCKED_MOVIES_DATA = [
// {title: '标题', year: '2015', posters: {thumbnail: 'http://i.imgur.com/UePbdph.jpg'}},
//];
/**
* 为了避免骚扰,使用一个样例数据来替代Rotten Tomatoes的API请求,这个样例数据放在React Native的Github库中。
*/
var REQUEST_URL = 'https://raw.githubusercontent.com/facebook/react-native/master/docs/MoviesExample.json';
class RNAPP extends Component{
constructor(props) {
super(props);
this.state = {
loaded: false,
dataSource: new ListView.DataSource({
rowHasChanged: (row1,row2) => row1 !== row2,
}),
};
}
render() {
if (!this.state.loaded) {
//如果movies==null,即初始情况,则渲染加载视图
return this.renderLoadingView();
}
//从网络上获取数据的情况
//var movie = this.state.movies[0];
//return this.renderMovie(movie);
return(
<ListView
dataSource = {this.state.dataSource}
renderRow = {this.renderMovie}
style = {styles.listView}
/>
);
}
renderLoadingView() {
return (
<View style={styles.container}>
<Text>
正在网络上获取电影数据……
</Text>
</View>
);
}
//渲染一个电影信息
renderMovie(movie) {
return (
<View style={styles.container}>
<Image
source={{uri: movie.posters.thumbnail}}
style={styles.thumbnail}
/>
<View style={styles.rightContainer}>
<Text style={styles.title}>{movie.title}</Text>
<Text style={styles.year}>{movie.year}</Text>
</View>
</View>
);
}
componentDidMount() {
this.fetchData();
}
//在React的工作机制下,setState实际上会触发一次重新渲染的流程,此时render函数被触发,发现this.state.movies不再是null
fetchData() {
fetch(REQUEST_URL)
.then((response) => response.json())
.then((responseData) => {
this.setState({
dataSource: this.state.dataSource.cloneWithRows(responseData.movies),
loaded: true,
});
})
.done();
//调用done(),可以抛出异常而不是简单忽略
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
listView: {
paddingTop: 20,
backgroundColor: '#F5FCFF',
},
thumbnail: {
width: 53,
height: 81,
},
//使rightContainer在父容器中占据Image之外剩下的全部空间
rightContainer: {
flex: 1,
},
title: {
fontSize: 20,
marginBottom: 8,
textAlign: 'left',
},
year: {
textAlign: 'left',
},
});
AppRegistry.registerComponent('RNAPP', () => RNAPP);
ListView组件自定义数据结构
dataBlob 数据
dataBlob
:The dataBlob is a data structure (generally an object) that holds all the data powering the ListView
dataBlob 包含ListView所需的所有数据(section header 和 rows),在ListView渲染数据时,使用 getSectionData 和 getRowData 来渲染每一行数据。dataBlob 的 key 值包含 sectionID 和 rowId。
sectionIDs[]
:用于标识每组section
rowIDs[]
:用于描述每个 section 里的每行数据的位置及是否需要渲染。在ListView渲染时,会先遍历 rowIDs 获取到对应的 dataBlob 数据。
rowData 的索引方式:dataBlob[sectionId:rowId]
React Native ListView sticky效果实现
Android 片段头sticky失效原因:
- 1) Found this out by accident, but if you put your ListView inside a ScrollView, the headers will not be sticky
- 2) iOS RCTScrollView implements sticky headers,而Android没有
- 3) ListView的Android版本不支持sticky-header特性
- 4) Android 原生sticky效果控件可使用StickyScrollViewItems替代:https://github.com/emilsjolander/StickyScrollViewItems
/**
* Sample React Native App
* https://github.com/facebook/react-native
*/
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
ScrollView,
Image,
ListView,
TouchableHighlight,
RecyclerViewBackedScrollView,
View,
} from 'react-native';
class RNAPP extends Component{
constructor(props) {
super(props);
var getSectionData = (dataBlob, sectionID) => {
return dataBlob[sectionID];
};
var getRowData = (dataBlob, sectionID, rowID) => {
return dataBlob[sectionID + ':' + rowID];
};
this.state = {
loaded: false,
dataSource: new ListView.DataSource({
getSectionData: getSectionData,
getRowData: getRowData,
rowHasChanged: (row1,row2)=> row1 !== row2,
sectionHeaderHasChanged: (s1, s2) => s1 !== s2
}),
};
}
render() {
if (!this.state.loaded) {
return this.renderLoadingView();
}
return(
<View style={styles.container}>
<View style={styles.header}>
<Text style={styles.headerText}>User List</Text>
</View>
<ListView
dataSource = {this.state.dataSource}
style = {styles.listview}
renderRow = {this.renderRow}
renderSectionHeader = {this.renderSectionHeader}
/>
</View>
);
}
renderSectionHeader(sectionData, sectionID) {
return (
<View style={styles.section}>
<Text style={styles.text}>片头-公司名:{sectionData}</Text>
</View>
);
}
renderRow (rowData, sectionID, rowID) {
return (
<View style={styles.rowStyle}>
<Text style={styles.rowText}>{rowData.name.title} {rowData.name.first} {rowData.name.last}-{sectionID}-{rowID}</Text>
</View>
);
}
renderLoadingView() {
return (
<View style={styles.loadingpage}>
<Text>
正在网络上获取电影数据……
</Text>
</View>
);
}
componentDidMount() {
this.fetchData();
}
fetchData () {
var responseData={
"results" : [
{
"organization":"阿里巴巴",
"id": 1234812,
"users": [{"user":{"gender":"female","name":{"title":"title1","first":"marga","last":"seegers"},"md5":"589a574553250be33f3b1170624ad2d1"}},
{"user":{"gender":"female","name":{"title":"title2","first":"kanne","last":"telar"},"md5":"5cbc80332e3523dd9d90cc116daf9d8e"}},
{"user":{"gender":"female","name":{"title":"title3","first":"livia","last":"stout"},"md5":"7d8c37997f09c122794651b1ec841491"}},
{"user":{"gender":"female","name":{"title":"title4","first":"dian","last":"grinwis"},"md5":"3956e950cbbf896533aef74c2c82cd89"}},
{"user":{"gender":"female","name":{"title":"title5","first":"sky","last":"vander"},"md5":"8d3c0c7c2338280d383815eeb93509f6"}},
{"user":{"gender":"male","name":{"title":"title6","first":"davi","last":"kero"},"md5":"c84ceab40f165ace6d8e91232a6905ce"}},
{"user":{"gender":"male","name":{"title":"title7","first":"jurren","last":"kempen"},"md5":"c56b46820b77e86cbfb7aaa519e9c4dc"}},
{"user":{"gender":"male","name":{"title":"title8","first":"alfons","last":"opden"},"md5":"8ad86f9082ffc5d59d97ad65da740b76"}},
{"user":{"gender":"male","name":{"title":"title9","first":"oene","last":"stroo"},"md5":"e756d75093bb3a65e5a6561eb04e11a1"}},
{"user":{"gender":"male","name":{"title":"title10","first":"yanick","last":"leuveren"},"md5":"dbcdaf6a92e48e2dd0b87053a6c0a460"}}]
},
{
"organization": "腾讯",
"id": 1235513,
"users": [{"user":{"gender":"female","name":{"title":"title1","first":"nicole","last":"sullivan"},"md5":"9bc08bb89d4b7163734e5a82bc1c913f"}},
{"user":{"gender":"male","name":{"title":"title2","first":"danny","last":"ellis"},"md5":"5084736a5dc54b3d01ab466cbe5a2bfd"}},
{"user":{"gender":"male","name":{"title":"title3","first":"richard","last":"morgan"},"md5":"5cf586edf1c296d439fc8c760ce0da78"}},
{"user":{"gender":"male","name":{"title":"title4","first":"adrian","last":"davies"},"md5":"17ae93548bf33c9fa6765339802eb9f7"}},
{"user":{"gender":"male","name":{"title":"title5","first":"charlie","last":"snyder"},"md5":"62bfb188b82a370c81d70c41b08f44fa"}},
{"user":{"gender":"male","name":{"title":"title6","first":"donald","last":"fitzpatrick"},"md5":"9ee42fe6081031aa5d798a1afcfeee19"}},
{"user":{"gender":"male","name":{"title":"title7","first":"adrian","last":"hawkins"},"md5":"f466f40834aa260d6ab86bee912fc443"}},
{"user":{"gender":"female","name":{"title":"title8","first":"caitlin","last":"robertson"},"md5":"10eaa195e29753ca6686755fceddef53"}},
{"user":{"gender":"female","name":{"title":"title9","first":"grace","last":"johnston"},"md5":"d91f66fe034218a2975ebd7db4738a12"}},
{"user":{"gender":"female","name":{"title":"title10","first":"rosie","last":"douglas"},"md5":"648b75b00311daa164439d898be76cf1"}}]
},
{
"organization": "百度",
"id": 1237141,
"users": [{"user":{"gender":"female","name":{"title":"title1","first":"joyce","last":"little"},"md5":"5b3318e717934073039028c9e5125896"}},
{"user":{"gender":"female","name":{"title":"title2","first":"ann","last":"gonzales"},"md5":"bc7a5338eb89da8b3044326277e7a9f1"}},
{"user":{"gender":"female","name":{"title":"title3","first":"genesis","last":"olson"},"md5":"f296f1cdcf1a901fc0d819e87f9db287"}},
{"user":{"gender":"female","name":{"title":"title4","first":"peyton","last":"simmmons"},"md5":"9835af99ec882eb8f29a5620d12181d6"}},
{"user":{"gender":"female","name":{"title":"title5","first":"ruby","last":"palmer"},"md5":"752c778c0f0b6d68cd61b636c75d1ddd"}},
{"user":{"gender":"female","name":{"title":"title6","first":"robin","last":"perkins"},"md5":"343d690344f146b8b9b0fecd0470e8bc"}},
{"user":{"gender":"male","name":{"title":"title7","first":"steve","last":"dean"},"md5":"353ec55e24648680ee30caeb32a8eb82"}},
{"user":{"gender":"male","name":{"title":"title8","first":"jessie","last":"barnett"},"md5":"8405ec6770a49f42fa00f94c2938d963"}},
{"user":{"gender":"female","name":{"title":"title9","first":"ann","last":"berry"},"md5":"140c33a7c143de515466ae0d6ff08b3a"}},
{"user":{"gender":"female","name":{"title":"title10","first":"hailey","last":"cunningham"},"md5":"7b8d57609043dc4947d1a65993ae6833"}}]
},
{
"organization": "小米",
"id": 1727272,
"users": [{"user":{"gender":"male","name":{"title":"title1","first":"tommy","last":"stanley"},"md5":"2155c170ed25a160f6202289bbb26aa5"}},
{"user":{"gender":"female","name":{"title":"title2","first":"rose","last":"gregory"},"md5":"ea3043ea9b36202218760e1e20c1a698"}},
{"user":{"gender":"male","name":{"title":"title3","first":"gilbert","last":"jennings"},"md5":"b9cec0adf70102f2f2864babd802c52c"}},
{"user":{"gender":"male","name":{"title":"title4","first":"alexander","last":"howell"},"md5":"1e27d0c330372b8dc2e88dc6f34d9ebf"}},
{"user":{"gender":"female","name":{"title":"title5","first":"eliza","last":"perry"},"md5":"c9b82b35a31e2b117fd8fe296af9a436"}},
{"user":{"gender":"female","name":{"title":"title6","first":"leah","last":"gregory"},"md5":"99561739ed756ef2bd633f7c22d5db77"}},
{"user":{"gender":"female","name":{"title":"title7","first":"catherine","last":"lee"},"md5":"78a29d0a21a66bb3e816d96ddb896c47"}},
{"user":{"gender":"female","name":{"title":"title8","first":"amelia","last":"obrien"},"md5":"76111906ba52da4ef50ac73871fa0da1"}},
{"user":{"gender":"female","name":{"title":"title9","first":"fiona","last":"may"},"md5":"9a35923e434be652e5441a2c49509dc6"}},
{"user":{"gender":"male","name":{"title":"title10","first":"earl","last":"banks"},"md5":"8071292ec92ad705c6be09b573d8bd8c"}}]
}
]
};
var organizations = responseData.results,
length = organizations.length,
dataBlob = {},
sectionIDs = [],
rowIDs = [],
organization,
users,
userLength,
user,
i,
j;
for (i = 0; i < length; i++) {
organization = organizations[i];
sectionIDs.push(organization.id);
dataBlob[organization.id] = organization.organization;
users = organization.users;
userLength = users.length;
rowIDs[i] = []; //rowIDs是一个二维数组
for(j = 0; j < userLength; j++) {
user = users[j].user;
rowIDs[i].push(user.md5); //二维数组放的都是 md5
dataBlob[organization.id + ':' + user.md5] = user;
}
}
this.setState({
dataSource: this.state.dataSource.cloneWithRowsAndSections(dataBlob, sectionIDs, rowIDs),
loaded: true
});
}
}
const styles = StyleSheet.create({
loadingpage:{
flex: 1,
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
container: {
flex: 1
},
header: {
height: 60,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#3F51B5',
flexDirection: 'column',
paddingTop: 25
},
headerText: {
fontWeight: 'bold',
fontSize: 20,
color: 'white'
},
text: {
color: 'white',
paddingHorizontal: 8,
fontSize: 16
},
rowStyle: {
paddingVertical: 20,
paddingLeft: 16,
borderTopColor: 'white',
borderLeftColor: 'white',
borderRightColor: 'white',
borderBottomColor: '#E0E0E0',
borderWidth: 1
},
rowText: {
color: '#212121',
fontSize: 16
},
subText: {
fontSize: 14,
color: '#757575'
},
section: {
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'flex-start',
padding: 6,
backgroundColor: '#2196F3'
}
});
AppRegistry.registerComponent('RNAPP', () => RNAPP);
ListView组件高级特性
分页
当数据量很大的时候如何分页加载,这种情形分两种情况考虑:
- 数据一次性拿到,边滚动边加载
- 数据不是一次性拿到,而是有可能分屏取数据
对于第一种情况,在 ListView 内部其实已经做了分页的处理:
ListView 内部通过 curRenderedRowsCount 状态保存已渲染的行数;初始状态下,要加载的数据条数等于 initialListSize (默认为 10 条);在滚动时检测当前滚动的位置和最底部的距离,如果小于 scrollRenderAheadDistance (默认为 1000),就更新 curRenderedRowsCount,在它原有值基础上加 pageSize 个(默认为 1 条);由于属性变化,触发 ListView 重新 render。在渲染过程中,curRenderedRowsCount 起到截断数据的作用,React 的 diff 算法使得只有新加入的数据才会渲染到了界面上。整个过程类似于 Web 端懒加载机制,即 每次在和底部的距离达到一个阈值时,加载接下来的 pageSize 个数据 。
对于第二种情况,ListView 提供了相关的属性:
onEndReachedThreshold
:在滚动即将到达底部时触发;
onEndReached
:在已经到达底部时触发;
我们可以在这两个方法中调用接口去拿数据,取到数据后再更新数据源。
多列 (Grid 效果)
很多页面中的列表并非单列的,通过布局即可达到网格效果。ListView 并没有强制要求一个 rowData 在展示时一定要占满一行,在多列的情况下,我们适时调整每个 rowData 占据的宽度即可。
由于 React Native 使用 Flexbox 进行布局,给ListView设置属性contentContainerStyle;在实现多列时,主要用到的是 flexWrap: wrap 属性:它的效果类似于 float,即水平地排列每一项,当放不下时进行折行处理。在设置每行视图占据一半宽度后即可达到两列的效果,多列的类似。
Grid 效果范例:
/**
* Sample React Native App
* https://github.com/facebook/react-native
*/
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
ScrollView,
Image,
ListView,
TouchableHighlight,
RecyclerViewBackedScrollView,
View,
} from 'react-native';
/**
* 为了避免骚扰,使用一个样例数据来替代Rotten Tomatoes的API请求,这个样例数据放在React Native的Github库中。
*/
var REQUEST_URL = 'https://raw.githubusercontent.com/facebook/react-native/master/docs/MoviesExample.json';
class HomeUI extends Component{
constructor(props) {
super(props);
this.state = {
loaded: false,
dataSource: new ListView.DataSource({
rowHasChanged: (row1,row2) => row1 !== row2,
}),
};
}
render() {
if (!this.state.loaded) {
return this.renderLoadingView();
}
//从网络上获取数据的情况
//var movie = this.state.movies[0];
//return this.renderMovie(movie);
return(
<ListView
dataSource = {this.state.dataSource}
renderRow = {this.renderMovie}
contentContainerStyle = {styles.list}
style = {styles.listView}
/>
);
}
renderLoadingView() {
return (
<View style={styles.container1}>
<Text>
正在网络上获取电影数据……
</Text>
</View>
);
}
_pressText() {
alert('点击时间');
}
//渲染一个电影信息
renderMovie(movie) {
return (
<View style={styles.container}>
<Image
source={{uri: movie.posters.thumbnail}}
style={styles.thumbnail}
/>
<Text style={styles.year} onPress={this._pressText}>年份{movie.year}</Text>
</View>
);
}
componentDidMount() {
this.fetchData();
}
//在React的工作机制下,setState实际上会触发一次重新渲染的流程,此时render函数被触发,发现this.state.movies不再是null
fetchData() {
fetch(REQUEST_URL)
.then((response) => response.json())
.then((responseData) => {
this.setState({
dataSource: this.state.dataSource.cloneWithRows(responseData.movies),
loaded: true,
});
})
.done();
//调用done(),可以抛出异常而不是简单忽略
}
}
const styles = StyleSheet.create({
list: {
justifyContent: 'flex-start',
flexDirection: 'row',
flexWrap: 'wrap'
},
container: {
width: 100,
height: 100,
backgroundColor: '#F5FCFF',
margin:5,
alignItems:'center',
},
listView: {
paddingTop: 20,
backgroundColor: '#F5FCFF',
},
thumbnail: {
width: 80,
height: 80,
borderRadius:16,
},
//使rightContainer在父容器中占据Image之外剩下的全部空间
container1: {
flex: 1,
justifyContent:'center',
alignItems:'center',
},
title: {
fontSize: 14,
marginBottom: 8,
},
year: {
fontSize: 14,
},
});
AppRegistry.registerComponent('RNAPP', () => RNAPP);
滚动
ListView 只是整合了数据和展现,但实际滚动的功能还是由 ScrollView 全权负责。ScrollView 实现完全和平台相关:在 iOS 上,它映射为 RCTScrollView ;在 Android 上,它映射为 RCTScrollView 和 AndroidHorizontalScrollView 。
React Native 使不同平台的技术融合在一起,同时也给开发人员提出了更高的要求。以 ScrollView 为例,大量的属性其实原封不动映射给了 UIScrollView ,这就意味着如果想再深入地研究下去,必须对客户端相关技术有足够了解。无论是前端还是客户端,跳出自己熟悉的那片领域也许才是更进一步的关键。
谈到滚动,有一点不得不说的就是 列表的无限加载 ,这牵涉到滚动的性能。
Github 上的这个 issue 对此展开了热烈的讨论。其中有人就提到,数据量很大情况下,ListView 在加载时所占用的 CPU 和内存会大大增加,滚动到最后就会导致应用 crash。
为此,ListView 中新添加了一个实验性的属性:removeClippedSubviews ,它能在滚动时及时删掉列表中处于视窗的之外的行,以此达到降低内存消耗的目的。不幸的是,即使设置了这个属性,程序虽然各项指标占用减少了不少,但还是没避免崩溃的命运。处于好奇,我也在最新版的 ListView 基础上做了简单尝试,不断加载一个无限大的列表,但并没有出现崩溃的情况:
即使加载了 3000、4000 行,Android 真机、iOS 真机和 iOS 模拟器上都没有崩溃;
Android 上明显感到数据加载有阶段性的延时 ,即滚动一定程度后,再次滚动数据始终加载不出来或要等一段时间才加载出来,体验较差;iOS 相比要流畅的多;
但不崩溃并非最终的目的,很多 React Native 使用者都在试图改进 ListView 的性能表现,相比于直接使用 Native 端的组件,ListView 性能还是差强人意,有很大优化空间。
总结
ListView 并没有创造出新的东西,它只是集各家所长,很好地将 React 的视图渲染和 Native 端很成熟的滚动机制融合在了一起,使用起来和其他组件无差,静态地定义View、动态地组织数据,是给人带来的直观感受。
版权声明:
作者:Joe.Ye
链接:https://www.appblog.cn/index.php/2023/02/25/react-native-learning-listview-component/
来源:APP全栈技术分享
文章版权归作者所有,未经允许请勿转载。
共有 0 条评论