ReactNative开发——滑动组件

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/a992036795/article/details/73161062

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, height: 0.5 * totalHeight, backgroundColor: 'blue'}}/>
                    </ScrollView>

                    <ScrollView horizontal={true} contentContainerStyle={styles.inScrollView}>
                        <TextInput placeholder={'测试软键盘'}
                                   style={{width: totalWidth, height: 0.5 * totalHeight, backgroundColor: '#fff1ae'}}/>
                        <View style={{width: totalWidth, height: 0.5 * totalHeight, backgroundColor: 'blue'}}/>
                    </ScrollView>

                    <ScrollView horizontal={true} contentContainerStyle={styles.inScrollView}>
                        <View style={{width: totalWidth, height: 0.5 * totalHeight, backgroundColor: 'red'}}/>
                        <View style={{width: totalWidth, height: 0.5 * totalHeight, backgroundColor: 'blue'}}/>
                    </ScrollView>


                    <ScrollView horizontal={false} contentContainerStyle={styles.inScrollView}>
                        <View style={{width: totalWidth, height: 0.5 * totalHeight, backgroundColor: 'red'}}/>
                        <View style={{width: totalWidth, height: 0.5 * totalHeight, 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', '#00ff00', '#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, StyleSheet, View, ListView, Text, 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, sectionID, rowID, 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, {Component} from 'react';
import {AppRegistry, StyleSheet, View, ListView, 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);

上面代码实现了用listView分组显示item。

FlatList

FlatList是ListView的升级版,它的性能比ListView好一些,但它目前刚出来,冒死还有些坑存在。。。

用法

/**
 * Created by blueberry on 6/11/2017.
 */

import React, {Component, PureComponent} from 'react';
import {AppRegistry, StyleSheet, View, FlatList, Text, 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, backgroundColor: 'green',}}
                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, {Component, PureComponent} from 'react';
import {AppRegistry, StyleSheet, View, SectionList, Text, 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: 'black'}}/>} //分割线
                ListFooterComponent={() => <View style={{height: 50, backgroundColor: 'red'}}/>} //尾部布局
                ListHeaderComponent={() => <View style={{height: 50, 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

猜你喜欢

转载自blog.csdn.net/a992036795/article/details/73161062