ReactNative的分组ListView-----SectionList

今天我要实现一个 类似于 iOS 开发中带有分组的colllectionView 样式的布局, 每个section都要有个组头。
首先我们要先决定要使用什么控件。ScrollView和ListView/FlatList还有SectionList都是可以选择的。

  • ScrollView 会把所有子元素一次性全部渲染出来。使用上最简单。但是如果你有一个特别长的列表需要显示,可能会需要好几屏的高度。这时就会占用很大的内存去创建和渲染那些屏幕以外的JS组件和原生视图,性能上也会有所拖累。
  • ListView 更适用于长列表数据。它会惰性渲染子元素,并不会立即渲染所有元素,而是优先渲染屏幕上可见的元素。
  • FlatList 是0.43版本开始新出的改进版的ListView,性能更优,但是官方说现在可能不够稳定,尚待时间考验。但是它不能够分组/类/区(section)。
  • SectionList 也是0.43版本推出的, 高性能的分组列表组件。但是它不支持头部吸顶悬浮的效果,但是也不要伤心,官方在下一个版本开始就可以支持悬浮的section头部啦 😜。

好啦, 综上所诉我选择使用SectionList ,现在开始干活吧 ✌️

首先

第一步我们先把要显示的样式写好作为子控件, 把数据源整理好。
例一、

<SectionList
  renderItem={({item}) => <ListItem title={item.title} />}
  renderSectionHeader={({section}) => <H1 title={section.key} />}
  sections={[ // 不同section渲染相同类型的子组件
    {data: [...], key: ...},
    {data: [...], key: ...},
    {data: [...], key: ...},
  ]}
/>

例二、

<SectionList
  sections={[ // 不同section渲染不同类型的子组件
    {data: [...], key: ..., renderItem: ...},
    {data: [...], key: ..., renderItem: ...},
    {data: [...], key: ..., renderItem: ...},
  ]}
/>

sections 就是我们的数据源,每一个data 就是我们要用的item, renderItem就是你要显示的子控件哦。如果你每个组都复用一个子组件那就按照例一的结构, 如果你想要不同的组返回不同样式的子组件那就按照例二的结构返回不同的renderItem即可。

这里提个醒, key一定要有, 不同的section 要设置不同的key才会渲染相应的section, 如果你key值都相同, 那可能会出现只显示一组数据的情况哦~
下面来看看我的代码:

 <SectionList
                        renderItem={this._renderItem}
                        renderSectionHeader={this._renderSectionHeader}
                    sections={[ // 不同section渲染相同类型的子组件
                            { data: [{ title: this.state.appModel[0] }], key: this.state.groupsModel[0].title },
                            { data: [{ title: this.state.appModel[1] }], key: this.state.groupsModel[1].title },
                            { data: [{ title: this.state.appModel[2] }], key: this.state.groupsModel[2].title },
                            { data: [{ title: this.state.appModel[3] }], key: this.state.groupsModel[3].title },
                        ]}
                    />

这样有了最基础的样式, 四组纵向的列表, 但是我要横向的, 于是我要设置他的样式啦。

接下来

这里我添加两个属性:

  contentContainerStyle={styles.list}//设置cell的样式
  pageSize={4}  // 配置pageSize确认网格数量

const styles = StyleSheet.create({
    list: {
        //justifyContent: 'space-around',
        flexDirection: 'row',//设置横向布局  
        flexWrap: 'wrap',  //设置换行显示
        alignItems: 'flex-start',
        backgroundColor: '#FFFFFF'
    },
});

好啦, 让我们来看看效果。


😓这是什么鬼???
为什么它的组头也在左边 , 并且他的其他组数据都横着了, 对于小白我来说只有大写的懵~。不知道你们有没有遇到这种情况, 是什么原因导致的, 我很是困惑啊, 当我把

     renderSectionHeader={this._renderSectionHeader}

这行代码注掉的时候, 它的显示是正常的...


这就尴尬了...

它的每一个小方块是一个item,达不到我要的效果啊, 于是我决定换个思路, 谁让我是打不死的小白呢😝

重新来

我决定让每个section是一个item。在每个item上创建多个可点击的板块。

 _renderItem = ({ item}) => (

        <View  style={styles.list}>
            {
                item.map((item, i) => this.renderExpenseItem(item, i))
            }
        </View>

    );

    renderExpenseItem(item, i) {

        return <TouchableOpacity key={i} onPress={() => this._pressRow(item)} underlayColor="transparent">
            <View style={styles.row}>
                <CellView source={item.img}></CellView>
            </View>
        </TouchableOpacity>;
    }

    _renderSectionHeader = ({ section }) => (
        <View style={{ flex: 1, height: 25 }}>
            <Text style={styles.sectionHeader} >{section.key}</Text>
        </View>
    );

    render() {
        return (
            <View style={{ flex: 1 }}>
                <Text style={styles.navigatorStyle}> 发现 </Text>
                <View style={{ flex: 1, backgroundColor: '#F7F6F8' }}>

                    <SectionList
                        renderItem={this._renderItem}
                        renderSectionHeader={this._renderSectionHeader}
                        showsVerticalScrollIndicator={false}
                        sections={ // 不同section渲染相同类型的子组件
                            this.state.dataSource
                        }
                    />
                </View>
            </View>
        );
    }
}

const styles = StyleSheet.create({
    list: {
        //justifyContent: 'space-around',
        flexDirection: 'row',
        flexWrap: 'wrap',
        alignItems: 'flex-start',
        backgroundColor: '#FFFFFF'
    },
    row: {
        backgroundColor: '#FFFFFF',
        justifyContent: 'center',
        width: (ScreenWidth - 1) / 4,
        height: (ScreenWidth - 1) / 4,
        alignItems: 'center',
    },
    sectionHeader: {
        marginLeft: 10,
        padding: 6.5,
        fontSize: 12,
        color: '#787878'
    },
});

这里的dataSource 我是在之前数据的基础上又包了一层[],然后在renderItem里做了map映射, 这样每个renderItem上返回了每一组我所需要的子组件。快来看看我的变化吧😊

肿么样, 达到效果了吧, 但是你有没有发现 底部为啥是黄色的?,我可没有去设置这么丑的颜色哦,其实它是提醒我们有不完美的地方, 下面就让我们解决一下这个不完美吧 。
最后解决问题


最后让我们来解决问题。它警告我们每个item 要有不同的key ,还记不记得我上面的提醒,我也犯这个错误了。

  • 默认情况下每行都需要提供一个不重复的key属性。你也可以提供一个keyExtractor函数来生成key。
// 把这个属性添加到 <SectionList/> 里面
keyExtractor = {this._extraUniqueKey}   
_extraUniqueKey(item ,index){
      return "index"+index+item;
}

这是每个item要设置key, 同样每个子控件也不能放过, 一定要设置它的key, 要不然这个屎黄色一直伴着你 多烦~~~
最后看一下我最终的代码吧!

var Dimensions = require('Dimensions');//获取屏幕的宽高
var ScreenWidth = Dimensions.get('window').width;
var ScreenHeight = Dimensions.get('window').height;

// const AnimatedSectionList = Animated.createAnimatedComponent(SectionList);// 这个是创建动画
export default class Explore extends Component {

    constructor(props) {
        super(props);
        this.state = {
            appModel: null,
            groupsModel: null,
            dataSource: null,
        }
    }

    //Component挂载完毕后调用
    componentDidMount() {
        this.fetchData();
    }

    async fetchData() {
        try {
            let model = await NetFetch.post(_req_url_path, {

            });
            let apps = model.apps;
            let groups = model.groups;

            let data = [];
            for (let i = 0; i < model.groups.length; i++) {
                let row = [];
                for (let j = 0; j < model.apps.length; j++) {

                    if (model.groups[i].appIds.indexOf(model.apps[j].appId) >= 0) {
                        row.push(model.apps[j]);
                    }
                }

                data.push({ data: [row], key: model.groups[i].title });
            }
// 这里我重组了一下数据结构, 看没看见我在row外面又包了一层, 为了我循环创建每个section的子组件。

            this.setState({
                appModel: model.apps,
                groupsModel: model.groups,
                dataSource: data
            });

        } catch (error) {
            alert(error.msg);
        }
    }

    _renderItem = ({ item}) => (

        <View  style={styles.list}>
            {
                item.map((item, i) => this.renderExpenseItem(item, i))
            }
        </View>

    );

    renderExpenseItem(item, i) {

        return <TouchableOpacity key={i} onPress={() => this._pressRow(item)} underlayColor="transparent">
            <View style={styles.row}>
                <CellView source={item.img}></CellView>
            </View>
        </TouchableOpacity>;
    }


    _renderSectionHeader = ({ section }) => (
        <View style={{ flex: 1, height: 25 }}>
            <Text style={styles.sectionHeader} >{section.key}</Text>
        </View>
    );

    _listHeaderComponent() {
        return (
            <HeaderView integral={0}></HeaderView>
        );
    }

    _listFooterComponent() {
        return (
            <Text style={[styles.remark]}>*预期收益非平台承诺收益,市场有风险,投资需谨慎</Text>
        );
    }

    _pressRow(item) {
        this.props.navigator.pushTo(item.go)
    } 

    _extraUniqueKey(item ,index){
        return "index"+index+item;
   } 

    render() {
        if (!this.state.dataSource) {
            return (
                <View></View>
            );
        }

        return (
            <View style={{ flex: 1 }}>

                <Text style={styles.navigatorStyle}> 发现 </Text>

                <View style={{ flex: 1, backgroundColor: '#F7F6F8' }}>

                    <SectionList
                        contentInset={{top:0,left:0,bottom:49,right:0}}// 设置他的滑动范围
                        renderItem={this._renderItem}
                        ListFooterComponent={this._listFooterComponent}
                        ListHeaderComponent={this._listHeaderComponent}
                        renderSectionHeader={this._renderSectionHeader}
                        showsVerticalScrollIndicator={false}
                        keyExtractor = {this._extraUniqueKey}// 每个item的key
                        // contentContainerStyle={styles.list}
                        // horizontal={true}
                        // pageSize={4}  // 配置pageSize确认网格数量
                        sections={ // 不同section渲染相同类型的子组件
                            this.state.dataSource
                        }

                    />
                </View>
            </View>
        );
    }

}


const styles = StyleSheet.create({
    navigatorStyle: {
        height: 64,
        backgroundColor: '#FFFFFF',
        textAlign: 'center',
        paddingTop: 33.5,
        fontSize: 17,
        fontWeight: '600',
    },
    list: {
        //justifyContent: 'space-around',
        flexDirection: 'row',
        flexWrap: 'wrap',
        alignItems: 'flex-start',
        backgroundColor: '#FFFFFF'
    },
    row: {
        backgroundColor: '#FFFFFF',
        justifyContent: 'center',
        width: (ScreenWidth - 1) / 4,
        height: (ScreenWidth - 1) / 4,
        alignItems: 'center',
        // borderWidth: 0.5,
        // borderRadius: 5,
        // borderColor: '#E6E6E6'
    },
    sectionHeader: {
        marginLeft: 10,
        padding: 6.5,
        fontSize: 12,
        color: '#787878'
    },
    remark: {
        margin: 10,
        fontSize: 10,
        color: '#D2D2D2',
        marginBottom: 10,
        alignSelf: 'center',
    },
});

看下最终效果图吧



最最后总结一下在开发中遇到的疑难杂症还有sectionList的重要属性。


不知道你有没有遇见这个问题, 看起来很简单, 应该是我没有引入Text组件, 但是我确实引入了。最终发现这个问题竟是因为我有段代码是这样写的

           <Image> source={require('../../assets/image/deadline.png')} style={styles.iconStyle} </Image>
                            <Text style={styles.userNameStyle}>赚积分,换好礼!</Text>

不知道你有没有发现错误, 由于习惯我<Image>组件写成<Image></Image>,Image是自封闭标签所以

           <Image source={require('../../assets/image/deadline.png')} style={styles.iconStyle} />
                            <Text style={styles.userNameStyle}>赚积分,换好礼!</Text>

这样问题就解决了, 但是我不清楚它为啥会报这样的错,反正开发中总是会出现一些不知所以的错, 所以平时写代码的时候不断总结起来就好啦...

SectionList 属性

  • ItemSeparatorComponent?: ?ReactClass<any>

行与行之间的分隔线组件。不会出现在第一行之前和最后一行之后。

  • ListFooterComponent?: ?ReactClass<any>

尾部组件

  • ListHeaderComponent?: ?ReactClass<any>

头部组件

  • keyExtractor: (item: Item, index: number) => string

此函数用于为给定的item生成一个不重复的key。Key的作用是使React能够区分同类元素的不同个体,以便在刷新时能够确定其变化的位置,减少重新渲染的开销。若不指定此函数,则默认抽取item.key作为key值。若item.key也不存在,则使用数组下标。

  • onEndReached?: ?(info: {distanceFromEnd: number}) => void

当所有的数据都已经渲染过,并且列表被滚动到距离最底部不足onEndReachedThreshold个像素的距离时调用。

  • onRefresh?: ?() => void

如果设置了此选项,则会在列表头部添加一个标准的RefreshControl控件,以便实现“下拉刷新”的功能。同时你需要正确设置refreshing属性。

  • refreshing?: ?boolean

是否刷新喽

  • renderItem: (info: {item: Item, index: number}) => ?React.Element<any>

根据行数据data渲染每一行的组件。

除data外还有第二个参数index可供使用。

  • renderSectionHeader?: ?(info: {section: SectionT}) => ?React.Element<any>

这就是我用到的每个section的组头

  • sections: Array<SectionT>

你的数据源
最后再提醒一下不要忘了key key key 哦 。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容