React Native使用SectionList打造单词本,包含分组的跳转

由于项目需求,需要做个单词本并且可跳转的功能。网上找了很久,有用老版本的ListView的,也有说改源码virtualList的,现在用的新版SectionList,是性能较好的,且适合做这种分组滚动跳转的组件。
由于官网上的讲解文档不是很详细,跳转原理刚开始没搞清楚导致浪费了大量的时间研究跳转方法中的参数是什么意思 - -
后面百度到了解了SectionList组件的跳转原理后,做出来了单词本功能~
供大家参考

  • 效果图


    单词本.png

    点击字母M后跳转到M.png

1.SectionList中scrollToLocation()getItemLayout 理解

scrollToLocation(params: object)   =>这是官方文档的解释

将可视区内位于特定`sectionIndex` 或 `itemIndex` (section内)位置的列表项,滚动到可视区的制定位置。
比如说,`viewPosition` 为0时将这个列表项滚动到可视区顶部 (可能会被顶部粘接的header覆盖), 
为1时将它滚动到可视区底部,为0.5时将它滚动到可视区中央。`viewOffset`是一个以像素为单位,
到最终位置偏移距离的固定值,比如为了弥补粘接的header所占据的空间

注意: 如果没有设置`getItemLayout`,就不能滚动到位于外部渲染区的位置。

下面引用会开花的树-通俗易懂的解释。
scrollToLocation方法,可以传递sectionIndex,itemIndex,viewOffset三个参数,达到滚动到SectionList的某一个位置的效果
➡️戳这里了解跳转原理、参数意思以及index的理解

2.核心代码展示附注释

//@author:Benny
//@date:2018.06.19
//@description:单词本
import React from 'react';
import {
    View,
    Text,
    TouchableOpacity,
    SectionList
} from 'react-native';

import { commonStyles, studyWordStyles } from '../../../../styles';

const ITEM_HEIGHT = 44; //item的高度
const HEADER_HEIGHT = 24;  //分组头部的高度


export default class Index extends Component {
    constructor(props) {
        super(props);
            this.state = {
            wordList: [{ "title": "C", "data": [{ "Name": "complication", "Id": 3614 }] }, { "title": "D", "data": [{ "Name": "dominate", "Id": 5378 }] }, { "title": "E", "data": [{ "Name": "educate", "Id": 5417 }] }, { "title": "I", "data": [{ "Name": "ignore", "Id": 5686 }, { "Name": "intake", "Id": 4092 }, { "Name": "interaction", "Id": 4103 }] }, { "title": "M", "data": [{ "Name": "mutual", "Id": 1004 }] }, { "title": "N", "data": [{ "Name": "natural habitat", "Id": 4272 }, { "Name": "negatively", "Id": 4288 }, { "Name": "nutrition", "Id": 2648 }] }, { "title": "O", "data": [{ "Name": "obesity", "Id": 2652 }, { "Name": "over-consumption", "Id": 1074 }] }, { "title": "P", "data": [{ "Name": "professional", "Id": 6066 }, { "Name": "project", "Id": 6073 }] }, { "title": "R", "data": [{ "Name": "reveal", "Id": 4480 }] }, { "title": "S", "data": [{ "Name": "submit", "Id": 6334 }] }, { "title": "U", "data": [{ "Name": "urban", "Id": 1588 }] }, { "title": "W", "data": [{ "Name": "well-preserved", "Id": 4843 }, { "Name": "widespread", "Id": 4883 }] }],
        }
    }

    _getData() {
        // 这块是请求后台接口获取单词数据的,并且格式化成 title,data的格式 (供参考)

        //$http.post($urls.studentApi.Study_Word_GetWordListJson).then((data) => {
        //  if (data.Rstatus) {
        //      let list = data.Rdata.map(item => {
        //          return {
        //              title: item.ClassifyName,
        //              data: item.List.map(w => {
        //                  return {
        //                      Name: w.WordName,
        //                      Id: w.WordId
        //                  }
        //              }),
        //          };
        //      });
        //      console.log(JSON.stringify(list))
        //      
        //      this.setState({
        //          wordList: list,
        //          isLoading: false
        //      })
        //  }
        // });
    }
        
       /*最主要的是这块代码 (计算每一项Item对应的位移及索引)
          list指以title、data为格式的数组(参考this.state.wordList的数据)*/
    _setItemLayout(list) {
       //ITEM_HEIGHT是每一项的高度、HEADER_HEIGHT是每一个SectionHeader的高度 
       //例如单词C这一行的高度就是HEADER_HEIGHT,complication这一行的高度就是ITEM_HEIGHT
        let [itemHeight, headerHeight] = [ITEM_HEIGHT, HEADER_HEIGHT];
        let layoutList = [];
        let layoutIndex = 0;//索引
        let layoutOffset = 0;//偏移量

        list.forEach(section => {
            layoutList.push({
                index: layoutIndex,
                length: headerHeight,
                offset: layoutOffset,
            });
            layoutIndex += 1;   //Header索引
            layoutOffset += headerHeight; //加上头部title高度
            section.data.forEach(() => { //遍历每一个section里头的data值
                layoutList.push({
                    index: layoutIndex,
                    length: itemHeight,
                    offset: layoutOffset,
                });
                layoutIndex += 1; //各项item的索引
                layoutOffset += itemHeight; //加上各项item高度
            });
            layoutList.push({
                index: layoutIndex,
                length: 0,
                offset: layoutOffset,
            });
            layoutIndex += 1; //footer索引
            // if(screenH-layoutOffset)
        });

        this.layoutList = layoutList;
    }

    _getItemLayout(data, index) {
       //渲染元素在所有渲染元素中的位置(数组中的索引)
        let layout = this.layoutList.filter(n => n.index == index)[0];
        return layout; 
    }

    _keyExtractor = (item, index) => index;

    _onSectionselect = (k) => {
        //跳转到某一项
        this.sectionList.scrollToLocation(
            {
                sectionIndex: k,//右边字母点击后的索引值 k
                itemIndex: 0,
                viewOffset: HEADER_HEIGHT,//向下偏移一个头部高度
            }
        );
    }
    _renderItem = ({ item }) => {
        return (
          //这里的点击事件可以去掉。用来点击某个单词进入到单词详情
            <TouchableOpacity style={studyWordStyles.sectionItem} onPress={() => {
                this.props.navigation.navigate('StudyWordbookDetail', {
                    WordName: item.Name,
                    IsExistWordBook: true,
                    callback: () => {
                        this._getData()
                    }
                })
            }}>
                <Text style={commonStyles.text6}>{item.Name}</Text>
            </TouchableOpacity>
        )
    }

    _wordListView() {
        //判断单词本是否有数据,没有的会展示空记录
            if (this.state.wordList && this.state.wordList.length > 0) {
                return (
                    <View style={studyWordStyles.sectionContainer}>
                        <SectionList
                            ref={(ref) => { this.sectionList = ref }}
                            showsVerticalScrollIndicator={false}
                            getItemLayout={this._getItemLayout.bind(this)}
                            keyExtractor={this._keyExtractor}
                            sections={this.state.wordList}
                            renderItem={this._renderItem}
                            renderSectionHeader={({ section }) => <View style={studyWordStyles.sectionHeader}><Text style={studyWordStyles.sectionHeaderTxt}>{section.title}</Text></View>}
                        />
                          /*右侧单词索引*/
                        <View style={studyWordStyles.titleListWrapper}>
                            <View style={studyWordStyles.sectionTitleList}>
                                {
                                    this.state.wordList.map((v, k) => {
                                        return (
                                            <Text style={studyWordStyles.titleText} key={k} onPress={() => { this._onSectionselect(k) }}>{v.title}</Text>
                                        )
                                    })

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

    componentDidMount() {
        this._setItemLayout(this.state.wordList);
    }

    render() {
        return (
                <View style={commonStyles.container}>
                    {
                        this._wordListView()
                    }
                </View>
        );
    }
}

  • studyWordStyles文件样式代码(这里就不把所有代码贴出来啦。values对象的某些值自己修改下就行了)
import {
    Platform,
    StyleSheet,
    Dimensions
} from 'react-native';
import * as values from '../../values';
const ITEM_HEIGHT = 44; //item的高度
const HEADER_HEIGHT = 24;  //分组头部的高度
const screenHeight = Dimensions.get('window').height;
export default StyleSheet.create({
    //单词本
    container: {
        flex: 1,
        paddingTop: 22,
    },
    sectionContainer: {
        marginBottom: 50
    },
    sectionHeader: {
        height: HEADER_HEIGHT,
        paddingLeft: 10,
        justifyContent: 'center',
        backgroundColor: 'rgba(247,247,247,1.0)',
    },
    sectionHeaderTxt: {
        fontSize: 14,
        fontWeight: 'bold',
        color: values.color666
    },
    sectionItem: {
        height: ITEM_HEIGHT,
        paddingLeft: 10,
        justifyContent: 'center',
        borderBottomWidth: values.miniWidth,
        borderBottomColor: '#eee',
        backgroundColor: '#fff',
    },
    //右侧标题
    titleListWrapper: {
        position: 'absolute',
        height: screenHeight,
        top: 0,
        right: 10,
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',

    },
    sectionTitleList: {
        paddingVertical: 5
    },
    titleText: {
        color: values.color666,
        textAlign: 'center',
        paddingVertical: 1,
        width: 20,
        fontSize: values.fontSize12
    },
    inactivetext: {
        fontWeight: '700',
        color: '#CCCCCC'
    },
    //单词本详情
    wordPan: {
        paddingVertical: 20,
        paddingHorizontal: 15,
        borderBottomWidth: values.miniWidth,
        borderBottomColor: '#eee',
    },
    wordText: {
        fontSize: 30,
    },
    wordSoundPan: {
        marginTop: 20,
        flexDirection: 'row',
        alignItems: 'center',
    },
    iconSize: {
        marginRight: 10,
        color: values.sysColorGreen,
        fontSize: 20,
    },
    soundText: {
        color: values.color666,
        marginRight: 30,
    },
    translationText: {
        marginTop: 15,
        color: values.color666,
    },
    examplePan: {
        paddingHorizontal: 15,
        paddingVertical: 20,
    },
    eTitle: {
        fontSize: 20,
        marginBottom: 10,
    },
    media: {
        marginTop: 0,
        paddingTop: 10,
        paddingBottom: 10,
        display: "flex",
        flexDirection: "row",
    },
    sentenceText: {
        marginBottom: 10
    },
    translationSentence: {
        lineHeight: 20,
    },
    addWrapper: {
        flexDirection: 'row',
        position: 'absolute',
        bottom: 0,
        left: 0,
        width: values.screenWidth,
        height: 50,
        // backgroundColor: '#000',
        alignItems: 'center',
        justifyContent: 'center',
        borderTopWidth: values.miniWidth,
        borderTopColor: values.colorBg,
    },
    addContainer: {
        width: values.screenWidth,
    },
    mgtl: {
        paddingLeft: 10
    }



});

3.最后

总结下...关键的思路就是计算好itemLayout。各个项位置偏移算准了,点击索引跳转的位置才会准确。一个section的索引包含sectionHeader、Item(可能不止一个)、以及sectionFooter。索引从0开始计。主要的方法在2中_setItemLayout (list)中。
有什么意见或者疑问都可以评论哦~
路过的小伙伴们觉得还不错就点个赞吧~

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

推荐阅读更多精彩内容

  • 1、通过CocoaPods安装项目名称项目信息 AFNetworking网络请求组件 FMDB本地数据库组件 SD...
    阳明先生x阅读 15,967评论 3 119
  • 嗨!听说我们有过一段很幸福的时光,那怎么会形同陌路? 听说我们曾经是一对令人羡慕的闺蜜,那怎么会反目成仇? ...
    苏锦一阅读 174评论 0 1
  • 有没有试过一个人走在大街上,昏暗的灯光洋洋洒洒的散洒在街道上,还可以看到街道上砖瓦的斜影,可是整个人的重心都不是落...
    一个人的R小姐阅读 166评论 0 0