继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

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

慕虎7371278
关注TA
已关注
手记 1259
粉丝 203
获赞 873

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

  • 效果图


    webp

    单词本.png


    webp

    点击字母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)中。
有什么意见或者疑问都可以评论哦~
路过的小伙伴们觉得还不错就点个赞吧~



作者:Benny_lzb
链接:https://www.jianshu.com/p/65a6a6d42abc


打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP