ReactNative开发——滑动组件
环境
window android react-native 0.45
ScrollView
介绍
ScrollView是一个可以滑动的组件,它内部可以是一个高度不受控制的View,但它自身必须要有个固定的高度。这里如果我们不给直接他设置高度,它的上层空间有固定高度的话也是可以的。
<ScrollView>
VS <FlatList>
我们应该选择哪个?
ScrollView
的工作是简单的渲染所有的子组件,它的用法比较简单。
FlatList
是惰性渲染item,只有当item出现在界面上时才渲染,并且从屏幕滑出去之后该item会被移除。所以它更省内存,也更节省cpu处理的时间。当然他的用法也比较复杂。
用法
我们来看一下用法示例,大家可以直接copy我的代码运行试试看:
/** * Created by blueberry on 6/9/2017. * @flow */
import React,{Component} from 'react';
import {
AppRegistry,StyleSheet,View,Text,ScrollView,ListView,FlatList,RefreshControl,Dimensions,Button,TextInput,} from 'react-native';
let totalWidth = Dimensions.get('window').width;
let totalHeight = Dimensions.get('window').height;
export default class MainPage extends Component {
state = {
isRefresh: false,}
_onRefresh() {
console.log('onRefresh.');
this.setState({isRefresh: true});
// 模拟获取数据需要三秒
setTimeout(() => this.setState({isRefresh: false}),3000);
}
_onScroll() {
console.log('onScroll.');
}
render() {
return (
<View style={styles.container}> <Button title="滑动到底部" onPress={() => _outScrollView.scrollToEnd({animated: false})}/> <ScrollView ref={(scrollView) => { _outScrollView = scrollView; }} contentContainerStyle={styles.outScrollView} onScroll={this._onScroll} //回调 scrollEventThrottle={100} // ios : 控制scroll回调的频率,没秒触发多少次 showsVerticalScrollIndicator={false} //设置不显示垂直的滚动条 keyboardDismissMode={'on-drag'} // 'none'默认值,滑动时不隐藏软件盘, // ‘on-drag'滑动时隐藏软件盘.interactive :ios可用。上滑可以回复键盘 keyboardShouldPersistTaps={'always'} //'never'默认值,点击TextInput以外的组件,软键盘收起, // 'always'不会收起,`handle` 当点击事件被子组件捕获时, //键盘不会自动收起,但我用android测试了,发现没有效果 //我使用的版本:RectNative 0.45 refreshControl={ <RefreshControl refreshing={this.state.isRefresh} onRefresh={this._onRefresh.bind(this)} title={'load...'} tintColor={'#ff0000'} colors={['#ff0000', '#00ff00','#0000ff']} progressBackgroundColor={'#ffff00'} /> } > <ScrollView horizontal={true} contentContainerStyle={styles.inScrollView} showsHorizontalScrollIndicator={false} //不显示滑动条 > <TextInput placeholder={'测试软键盘'} style={{width: totalWidth,height: 0.5 * totalHeight,backgroundColor: '#fff1ae'}}/> <View style={{width: totalWidth,backgroundColor: 'blue'}}/> </ScrollView> <ScrollView horizontal={true} contentContainerStyle={styles.inScrollView}> <TextInput placeholder={'测试软键盘'} style={{width: totalWidth,backgroundColor: 'blue'}}/> </ScrollView> <ScrollView horizontal={true} contentContainerStyle={styles.inScrollView}> <View style={{width: totalWidth,backgroundColor: 'red'}}/> <View style={{width: totalWidth,backgroundColor: 'blue'}}/> </ScrollView> <ScrollView horizontal={false} contentContainerStyle={styles.inScrollView}> <View style={{width: totalWidth,backgroundColor: 'blue'}}/> </ScrollView> </ScrollView> </View> ); } } var styles = StyleSheet.create({ container: { flex: 1,height: '50%',backgroundColor: 'grey',},outScrollView: { // flex: 1,这里指定flex的话,会出现不能上下滑动,原因在这样会把 "内容高度定死了",所以最好不要设置高度/flex,让内容的高度自适应 justifyContent: 'center',backgroundColor: 'green',inScrollView: { padding: 20,backgroundColor: '#88ff73' } }); AppRegistry .registerComponent( 'Project08',() => MainPage ) ;
上面的代码创建了一个ScrollView
其中嵌套了4个ScrollView
,有3个是横向滑动,最后一个是纵向滑动。ps:这里竟然没有滑动冲突,我想说:“666”,这要是android原生开的话,这种布局可是比较麻烦的。
基本属性
属性 | 作用 |
---|---|
contentContainerStyle | 设置内层容器的样式。what?什么是内层容器?这里我的理解是,它这个ScroolView中还包装着一个View,这里View包含有我们设置的Item,大家想想,我们在android原生开发中使用呢ScrollView的时候,内层是不是一般也要嵌套一个LinearLayout用来存放子View吧 |
onScroll | 回调方法,滑动的时候回调 |
scrollEventThrottle | 这个之后ios有效,用来设置onScroll滑动的频率,可以节省性能,类型:number.表示1秒回调多少次 |
showsVerticalScrollIndicator | 这个用来设置是否显示垂直滚动条,和他相似的还有showsHorizontalScrollIndicator |
showsHorizontalScrollIndicator | 用来设置是否显示横向滑动条 |
keyboardDismissMode | 用来设置软件盘滑动的时候,是否隐藏的模式,none(默认值),拖拽时不隐藏软键盘 on-drag 当拖拽开始的时候隐藏软键盘 interactive 软键盘伴随拖拽操作同步地消失,并且如果往上滑动会恢复键盘。安卓设备上不支持这个选项,会表现的和none一样 |
keyboardShouldPersistTaps | ‘never’(默认值),点击TextInput以外的子组件会使当前的软键盘收起。此时子元素不会收到点击事件。’always’,键盘不会自动收起,ScrollView也不会捕捉点击事件,但子组件可以捕获 ‘handled’,当点击事件被子组件捕获时,键盘不会自动收起。这样切换TextInput时键盘可以保持状态。多数带有TextInput的情况下你应该选择此项,但我用android机测试的时候,发现没卵用 |
refreshControl | 用来设置下拉刷新组件,这个组件下文将介绍 |
ok,这些是基本属性,更多属性大家可以参考:http://reactnative.cn/docs/0.45/scrollview.html#content
RefreshControl
refreshControl={
<RefreshControl refreshing={this.state.isRefresh} onRefresh={this._onRefresh.bind(this)} title={'load...'} tintColor={'#ff0000'} colors={['#ff0000','#0000ff']} progressBackgroundColor={'#ffff00'} /> }
需要的属性基本我都写上了,这里我再列个表格解释一下好了。
属性 | 作用 |
---|---|
refreshing | bool 类型,如果设置true,则下拉刷新按钮就一直显示着,如果设置false,就不显示,只有当下拉的时候显示 |
onRefresh | 下拉回调 |
title | 标题 |
tintColor | 指定刷新指示器的颜色 |
colors | 指定至少一种颜色用来绘制刷新指示器 |
progressBackgroundColor | 指定刷新指示器的背景色 |
ListView
ListView是一个可以垂直滑动的组件,一般用来显示列表数据
用法
/** * Created by blueberry on 6/9/2017. * @flow */
import React,{Component} from 'react';
import {AppRegistry,Button} from 'react-native';
import StaticContainer from './StaticContainer';
let array = [];
{
let len = 100;
for (let i = 0; i < len; i++) {
array.push('测试数据' + i);
}
}
/** * 加个log,用来测试,是否更新。 */
class LogView extends Component {
componentDidUpdate() {
console.log(this.props.name + 'Did update');
}
render() {
return (
<Text style={{backgroundColor: '#ffd98c'}}> 我是:{this.props.name} </Text> ); } } export default class ListViewPage extends Component { constructor() { super(); let ds = new ListView.DataSource({rowHasChanged: (r1,r2) => r1 !== r2}); this.state = { //填充数据 dataSource: ds.cloneWithRows(array),}; } render() { return ( <ListView // 数据源 dataSource={this.state.dataSource} initialListSize={10} //初始的时候显示的数量 onChangeVisibleRows={(visible, changedRows) => { // 我用android测试,没有回调.... // visible: 类型:{ sectionID: { rowID: true }} // { sectionID: { rowID: true | false }} console.log('visible:' + JSON.stringify(visible)); console.log('changedRow:' + JSON.stringify(changedRows)); }} onEndReached={() => console.log('onEndReached')}//当所有的数据都已经渲染过,并且列表被滚动到距离最底部不足 // onEndReachedThreshold个像素的距离时调用。原生的滚动事件会被作为参数传递。译注:当第一次渲染时, // 如果数据不足一屏(比如初始值是空的),这个事件也会被触发,请自行做标记过滤。 onEndReachedThreshold={2} //调用onEndReached之前的临界值,单位是像素 pageSize={3} //每次渲染的行数 // 返回一个头部可以渲染的组件 renderHeader={() => ( <LogView name="header"/> )} //返回一个尾部可以渲染的组件 renderFooter={() => ( <StaticContainer> <LogView name="Footer"/> </StaticContainer> )} //显示每一行 renderRow={ (rowData,sectionID,rowID,highlightRow) => { return ( <Text style={{borderBottomColor: 'grey',borderBottomWidth: 1}}> {'rowData:' + rowData + ' sectionId:' + sectionID + " rowId:" + rowID + " highlightRow:" + highlightRow} </Text> ) }}/> ); } } AppRegistry.registerComponent('Project08',() => ListViewPage);
上面的代码实现了一个用来显示100条数据的列表,它还有一个头部,和一个尾部,因为头部和尾部的数据一般都不收布局变化,所有使用了一个StaticContainer
来包装它,让他不刷新。这样做可以提高效率。为了看出效果,我特意定义了一个LogView
组件,用来测试。
ListView.DataSource
ListView.DataSource 主要用来为ListView提供数据,它的一般用法。上面的代码已经给出了。
let ds = new ListView.DataSource({rowHasChanged: (r1,r2) => r1 !== r2});
this.state = {
//填充数据
dataSource: ds.cloneWithRows(array),};
它还有另外一个方法:cloneWithRowsAndSections(dataBlob,sectionIdentities,rowIdentities)
用来填充分组数据。
使用详细可以参考:http://reactnative.cn/docs/0.45/listviewdatasource.html#content
基本属性
属性名 | 作用 |
---|---|
dataSource | 数据源,上文已经说明 |
initialListSize | 初始的时候显示的数量 |
onChangeVisibleRows | 当可见的行的集合变化的时候调用此回调函数。visibleRows 以 { sectionID: { rowID: true }}的格式包含了所有可见行,而changedRows 以{ sectionID: { rowID: true |
onEndReached | 当所有的数据都已经渲染过,并且列表被滚动到距离最底部不足onEndReachedThreshold个像素的距离时调用。原生的滚动事件会被作为参数传递。译注:当第一次渲染时,如果数据不足一屏(比如初始值是空的),这个事件也会被触发,请自行做标记过滤。 |
onEndReachedThreshold | 调用onEndReached之前的临界值,单位是像素。 |
pageSize | 每次事件循环(每帧)渲染的行数。 |
renderFooter | 页头与页脚会在每次渲染过程中都重新渲染(如果提供了这些属性)。如果它们重绘的性能开销很大,把他们包装到一个StaticContainer或者其它恰当的结构中。页脚会永远在列表的最底部,而页头会在最顶部。 |
renderHeader | 和renderFoot的用法一样 |
renderRow | (rowData,highlightRow) => renderable 最重要的方法,用渲染每个item,其中rowData是你定义的数据列表中的类型数据,sectionID是该行的sectionID,rowId是该行的rowID,hightlighRow是一个函数引用,我目前没有发现卵用,官网说:如果item正在被高亮,可以通过hightlightRow(null) 来重置 |
scrollRenderAheadDistance | 当一个行接近屏幕范围多少像素之内的时候,就开始渲染这一行 |
renderSectionHeader | (sectionData,sectionID) => renderable 如果提供了此函数,会为每个小节(section)渲染一个粘性的标题。 |
stickySectionHeadersEnabled设置小节标题(section header)是否具有粘性 | |
stickyHeaderIndices | 一个子视图下标的数组,用于决定哪些成员会在滚动之后固定在屏幕顶端 |
额,还有ScrollView有的属性,ListView都有;所有horizontal、refreshControl,都可以个ListView设置。
基本属性就这些,更多属性参考:http://reactnative.cn/docs/0.45/listview.html#content
分组显示
/** * Created by blueberry on 6/9/2017. * * @flow */
import React,Text} from 'react-native';
let array: Array<Array<string>> = [];
{
for (let i = 0; i < 10; i++) {
let group: Array<string> = [];
for (let j = 0; j < 10; j++) {
group.push('分组:' + i + " item:" + j);
}
array['分组' + i] = group;
}
}
export default class GroupListView extends Component {
constructor() {
super();
var ds = new ListView.DataSource({
rowHasChanged: (r1,r2) => r1 !== r2,sectionHeaderHasChanged: (pre,next) => pre !== next,});
this.state = {
/** * 填充数据 * @params 所有的分组数据,结构{分组1:[分组1 item1,分组1item2. ...],分组2:[分组2item1,....]} * @param sectionIdentities 每个分组的索引 */
dataSource: ds.cloneWithRowsAndSections(array,Object.keys(array)),};
}
render() {
return (
<ListView dataSource={this.state.dataSource} renderRow={(rowData) => { return <Text>{rowData}</Text>;
}}
renderSectionHeader={(sectionData,sectionId) => {
console.log('sectionData:' + JSON.stringify(sectionData) + ',sectionID:' + JSON.stringify(sectionId));
return <Text style={{backgroundColor: 'red'}}>{sectionData[0]}</Text> }} stickySectionHeadersEnabled={true} //开启之后,会有个粘性效果, stickyHeaderIndices={[1]} //一个子视图下标的数组(这个下标连section也算在内的,),用于决定哪些成员会在滚动之后固定在屏幕顶端.根 // stickySectionHeadersEnabled的效果很像 scrollRenderAheadDistance={10} //当一个行接近屏幕范围多少像素之内的时候,就开始渲染这一行。 /> ); } } AppRegistry.registerComponent('Project08',() => GroupListView);
FlatList
FlatList是ListView的升级版,它的性能比ListView好一些,但它目前刚出来,冒死还有些坑存在。。。
用法
/** * Created by blueberry on 6/11/2017. */
import React,{Component,PureComponent} from 'react';
import {AppRegistry,TouchableOpacity} from 'react-native';
class ListItem extends PureComponent {
_onPress = () => {
this.props.onPressItem(this.props.id);
}
render() {
let color = this.props.selected ? 'red' : 'blue';
return (
<TouchableOpacity style={{height: 200,justifyContent: 'center',flex: 1,alignItems: 'center',backgroundColor: color} } onPress={this._onPress}> <Text style={{fontSize: 20,height: 100,}}>{this.props.title}</Text> <Text style={{fontSize: 18,height: 80}}>{this.props.content}</Text> </TouchableOpacity> ); } } /** * PureComponent 可以提高性能,只有在props或state发生改变时render。 */ export default class FlatListPage extends PureComponent { state = {selected: (new Map(): Map<string, boolean>)}; _onPressItem = (id: string) => { this.setState((state) => { const selected = new Map(state.selected); selected.set(id,!selected.get(id));//toggle. return {selected}; }); }; _keyExtractor = (item,index) => item.id; /** * 使用箭头函数,既保证了this指向FlatListPage,也保证了不会每次都生成一个新的函数,这样在对比prop时,就返回'没有改变' */ _renderItem = ({item}) => ( <ListItem id={item.id} onPressItem={this._onPressItem} selected={!!this.state.selected.get(item.id)} title={item.title} content={item.content} /> ); render() { return ( <FlatList data={this.props.data} renderItem={this._renderItem} // extraData={this.state} keyExtractor={this._keyExtractor} ItemSeparatorComponent={() => <View style={{height: 2,backgroundColor: 'black'}}/>} //分割线 ListFooterComponent={() => <View style={{height: 50,backgroundColor: 'red'}}/>} //尾部布局 ListHeaderComponent={() => <View style={{height: 50,backgroundColor: 'blue'}}/>} //头部布局 columnWrapperStyle={{height: 200,}} numColumns={2} // //getItemCount={40} //getItemLayout={(data,index) => ({length: 200,offset: 200 * index,index})} refreshing={false} onEndReachedThreshold={20} //决定距离底部20个单位的时候,回到onEndReacted,但是我这只20, // 他距离4000左右的时候就回掉了,测试版本Android reactNative Api:0.45 onEndReached={(info) => { console.log('onEndReacted:' + info.distanceFromEnd); }} /> ); } } { let array = []; for (let i = 0; i < 40; i++) { array[i] = {id: i,key: 'key' + i,title: '标题' + i,content: '内容' + i,}; } FlatListPage.defaultProps = {data: array}; } AppRegistry.registerComponent('Project08',() => FlatListPage);
常用属性
属性 | 作用 |
---|---|
ItemSeparatorComponent | 行与行之间的分隔线组件。不会出现在第一行之前和最后一行之后 |
ListFooterComponent | 设置尾部组件 |
ListHeaderComponent | 设置头部组件 |
columnWrapperStyle | 如果设置了多列布局(即将numColumns值设为大于1的整数),则可以额外指定此样式作用在每行容器上。 |
data | 为了简化起见,data属性目前只支持普通数组。如果需要使用其他特殊数据结构,例如immutable数组,请直接使用更底层的VirtualizedList组件。 |
extraData | 如果有除data以外的数据用在列表中(不论是用在renderItem还是Header或者Footer中),请在此属性中指定。同时此数据在修改时也需要先修改其引用地址(比如先复制到一个新的Object或者数组中),然后再修改其值,否则界面很可能不会刷新。 |
keyExtractor此函数用于为给定的item生成一个不重复的key。Key的作用是使React能够区分同类元素的不同个体,以便在刷新时能够确定其变化的位置,减少重新渲染的开销。若不指定此函数,则默认抽取item.key作为key值。若item.key也不存在,则使用数组下标 | |
numColumns | 多列布局只能在非水平模式下使用,即必须是horizontal={false}。此时组件内元素会从左到右从上到下按Z字形排列,类似启用了flexWrap的布局。组件内元素必须是等高的——暂时还无法支持瀑布流布局 |
更多属性请参考: http://reactnative.cn/docs/0.45/flatlist.html#content
上述代码定义的组件都继承了 PureComponent,这个组件的作用是,只有prop和state它才render。实现它是为了提高效率。
SectionList
是一个高性能的分组列表组件
使用
/** * Created by blueberry on 6/12/2017. */
import React,SectionList,RefreshControl} from 'react-native';
class ListItem extends PureComponent {
render() {
return (
<Text style={{backgroundColor: 'red',height: 100}}>{this.props.title}</Text> ); } } let sections = []; for (let i = 0; i < 10; i++) { data = []; for (let j = 0; j < 10; j++) { data[j] = {title: '分组' + i + ',item' + j,id: j}; } sections.push({data: data,key: '分组' + i}); // 也可以使用下面方式自定义 不同section渲染不同类型的子组件 //sections.push({data: data,key: '分组' + i,renderItem: i === 2 ? ()=><ListItem title="测试"/> : undefined}); } export default class SectionListPage extends PureComponent { //为每一行生成唯一的key _keyExtractor = (item,index) => '' + item.key + index; render() { console.log(JSON.stringify(sections)); return ( <SectionList //渲染item的组件 renderItem={({item}) => <ListItem title={item.title} /> } //渲染sectionHeader的组件 renderSectionHeader={({section}) => <Text>{section.key}</Text> } //数据 sections={sections} //生成唯一的key keyExtractor={this._keyExtractor} ItemSeparatorComponent={() => <View style={{height: 2,backgroundColor: 'blue'}}/>} //头部布局 /> ); } } AppRegistry.registerComponent('Project08',() => SectionListPage);
基本属性
属性 | 作用 |
---|---|
renderItem | 和FlatList的renderItem作用一样。用来设置渲染item的组件,但SectionList,也可以section数据源中设置renderItem这个属性 |
renderSectionHeader | 设置渲染分组小标签的组件 |
seciton | 设置数据源 |
其余属性和FlatList
中的属性作用一样,这里就不在介绍了。
我在上述代码中有这么一行,用来设置不同的renderItem函数,读者可以去掉注释看看效果
// sections.push({data: data,key: '分组' + i,renderItem: i === 2 ? ()=><ListItem title="测试"/> : undefined});
总结
其实我们文章中我们主要提到了 6个组件:ScrollView
ListView
RefreshControl
FlatList
SectionList
PureComponent
;其中主要讲解了四个滑动组件
- ScrollView
它没有懒加载功能,适合少量数据显示,用法比较简单。
- ListView
它用来显示列表数据,是懒加载Item,支持下拉刷新,上拉加载应该用它的onEndReached
也是可以办到的。也支持分组列表显示,分组标签粘性滑动等功能,性能比较好。设置数据需要结合ListView.DataSource组件。
- FlatList
可以说是ListView的升级版,性能比ListView要好,同样支持下拉刷新等功能,目前我用0.45版本,刚出来,官方说还不稳定。
- SectionList
用来显示分组列表组件,性能比较高。
上面就是本文介绍的四个滑动组件,他们都支持下拉刷新组件,(ScrollView)有的属性,其他滑动组件基本都有。
RefreshControl
就是官方给出的下拉刷新组件,用来设置到滑动组件上。
PureComponent
之后当state或props变了之后,才能刷新。可以提高性能。
ok,介绍到这里了,其中我写的测试源码,在上文都贴出来了,大家可以测试测试。
参考
ReactNative 官网:http://facebook.github.io/react-native/releases/0.43/docs/flatlist.html
ReactNative 中文网:http://reactnative.cn/docs/0.45/sectionlist.html#content