react-native使用SectionList做通讯录列表,分组跳转

前言:最近开发的app中有一个类似手机通讯录的组件,准备自己用 SectionList 做;

注意:本文使用的RN版本为0.55版本,从官方文档上看SectionList是从0.43版本才有的。如果需要使用SectionList,请注意RN的版本问题。

首先,看下最后的效果图:

react-native使用SectionList做通讯录列表,分组跳转_第1张图片

其次,接口请求的数据结构(mock模拟):

react-native使用SectionList做通讯录列表,分组跳转_第2张图片

然后,看一下官方对SectionList数据格式的要求:https://reactnative.cn/docs/0.55/sectionlist/

react-native使用SectionList做通讯录列表,分组跳转_第3张图片

 

接下来,进入正题:

1. 定义三个state,存储数据

react-native使用SectionList做通讯录列表,分组跳转_第4张图片

其实两个state就可以,我这里listData都没有用,只有把源数据保存下来了!

2. 请求数据,并将源数据拆分为SectionList需要的数据格式,并构造一个字母索引的数组

componentDidMount(){
        this.getContacts();
    }

    async getContacts() {
        try{
            let data = await API.app.contactlist();     //获取联系人列表
            const {list} = data;

            let sections = [], letterArr = [];
            list.map((item,index) => {
                if(letterArr.indexOf(item.spell.charAt(0)) === -1){
                    letterArr.push(item.spell.charAt(0) );          //得到所有不重复的首字母
                }
            });
            letterArr.sort();

            letterArr.map((item,index) => {
                const module = list.filter((it) => {         //遍历获取每一个首字母对应联系人
                        return it.spell.charAt(0) === item;
                    }
                );
                sections.push({key: item, title: item, data: module,});     //首字母对应联系人数组放到sections(每一次循环放一次)
            });

            this.setState({
                listData : list,
                letterArr,
                sections,
            });
        }catch (err) {
            console.log('err',err);
            showToast(err.message);
        }
    }

3. 构造页面(左侧为SectionList列表,右侧为字母索引列表)

const {listData, letterArr, sections} = this.state;
//偏移量 = (设备高度 - 字母索引高度 - 底部导航栏 - 顶部标题栏 - 24)/ 2         ===>> 最后这个24,我有点找不到原因,个人觉得可能是系统顶部的状态栏
const top_offset = (Dimensions.get('window').height - letterArr.length*20 - 56 - 44 - 24) / 2;
return(
    
         }
             sections={sections}
             keyExtractor={(item, index) => index}
             numColumns={1}
             renderItem={({item, index}) => this._renderItem(item, index)}
        />
        
             index.toString()}       //不重复的key
                  renderItem={({item,index}) =>
                      
                         {item.toUpperCase()}
                      
                  }
            />
     

);

注意:我这里为了把右侧的字母索引列表悬浮在SectionList只是,用了定位,我觉得不是很好,因为top的偏移量需要计算,但flex我没有找到很好的出来方法。

4. 点击右侧字母导航,SectionList滚动到对应的节点上

       4.1. 给字母索引列表的item加一个点击事件

        react-native使用SectionList做通讯录列表,分组跳转_第5张图片

        4.2. 定义_onSectionselect(滚动事件)

_onSectionselect = (key) => {
     let offset = key * 20;      //点击了那个字母索引,没有section的header的高是20;key为0是即偏移0
     const {sections} = this.state;
     sections.map((item,index) => {
         if(key > index){        //要滚动的距离就是,点击的字母索引之前的所有内容,所以当 点击字母的索引 大于 sections(变量)的索引时;技术要滚动的高度
             offset = offset + item.data.length*80 + (item.data.length-1);      //每个联系人的item高是60,上下padding各为10;然后每个节点里面有length-1条分割线且高度为1
          }
     });

     //滚动到指定的偏移的位置
     this.refs._sectionList.scrollToOffset({animated: true, offset: offset});
};

这里主要计算滚动的偏移量,比如我点击了第一个字母,就需要偏移一个字母的所有数据(item)、节点头部(renderSectionHeader)和分割线的总高度,移除类推。

5. 修改SectionList源码,添加scrollToOffset方法

注意:SectionList这个列表组件并没有scrollToOffset方法(直接使用会报错:找不到scrollToOffset方法);FlatList中才有,包括scrollToIndex和scrollToEnd也是一样

SectionList的方法有:

react-native使用SectionList做通讯录列表,分组跳转_第6张图片

FlatList的方法有:

react-native使用SectionList做通讯录列表,分组跳转_第7张图片

所以,需要参照FlatList里面的scrollToOffset方法,给SectionList这个列表组件手动添加scrollToOffset方法

第一步:其中SectionList的路径为:node_modules/react-native/Libraries/Lists/SectionList.js,代码格式化后大概在343行的位置,修改如下:

  //添加scrollToOffset方法
  scrollToOffset(params: {animated?: ?boolean, offset: number}) {
      if (this._wrapperListRef) {
          this._wrapperListRef.scrollToOffset(params);
      }
  }

即:

react-native使用SectionList做通讯录列表,分组跳转_第8张图片

第二步:同时还需要修改VirtualizedSectionList的代码,路径在node_modules/react-native/Libraries/Lists/VirtualizedSectionList.js,大概381行处修改如下:

//添加scrollToOffset方法
  scrollToOffset(params: {animated?: ?boolean, offset: number}) {
      if (this._listRef) {
          this._listRef.scrollToOffset(params);
      }
  }

即:

react-native使用SectionList做通讯录列表,分组跳转_第9张图片

注意,那个 ref 变量的名称在不同的RN版本中可能不一样,请参照其余地方使用即可!

修改完毕!

 

完整代码:(有其他组件的引入,可能用不起,请参考)

import React, {Component} from "react";
import {
    View,
    Text,
    // Button,
    Dimensions,
    StyleSheet,
    SectionList,
    Linking,
    Platform,
    FlatList,
    Image,
    TouchableOpacity,
    ScrollView,
    TouchableWithoutFeedback,
} from 'react-native'

import {
    Container,
    Header,
    Title,
    Content,
    Button,
    Footer,
    FooterTab,
    Left,
    Right,
    Body,
    Icon,
    Form,
    Item,
    Input,
    Label,
    H1,
    ListItem,
    // Text,
    CheckBox
} from 'native-base';

import {HeaderDark, HeaderLeft, HeaderBody, HeaderRight, HeaderBack, HeaderCaption} from '../common/ui/header';
import API from "../API/API";
import '../API/API+App';
import {makeImgUrl, showToast} from "../common/until";

export class ContactsListScreen extends Component{
    constructor() {
        super();
        this.state = {
            sections: [],       //section数组
            listData: [],       //源数组
            letterArr: [],      //首字母数组

            showIndex: -1,      //显示按钮的item对应的userId
        };
    }

    componentDidMount(){
        this.getContacts();
    }

    componentWillReceiveProps(nextProps){
        this.setState({showIndex : -1});    //每次重新进入联系人页面时没有选择任何人(不会显示图标)
    }

    async getContacts() {
        try{
            let data = await API.app.contactlist();     //获取联系人列表
            const {list} = data;

            let sections = [], letterArr = [];
            list.map((item,index) => {
                if(letterArr.indexOf(item.spell.charAt(0)) === -1){
                    letterArr.push(item.spell.charAt(0) );          //得到所有不重复的首字母
                }
            });
            letterArr.sort();

            letterArr.map((item,index) => {
                const module = list.filter((it) => {         //遍历获取每一个首字母对应联系人
                        return it.spell.charAt(0) === item;
                    }
                );
                sections.push({key: item, title: item, data: module,});     //首字母对应联系人数组放到sections(每一次循环放一次)
            });

            this.setState({
                listData : list,
                letterArr,
                sections,
            });
        }catch (err) {
            console.log('err',err);
            showToast(err.message);
        }
    }

    _onSectionselect = (key) => {
        let offset = key * 20;      //点击了那个字母索引,没有section的header的高是20;key为0是即偏移0
        const {sections} = this.state;
        sections.map((item,index) => {
            if(key > index){        //要滚动的距离就是,点击的字母索引之前的所有内容,所以当 点击字母的索引 大于 sections(变量)的索引时;技术要滚动的高度
                offset = offset + item.data.length*70 + (item.data.length-1);      //每个联系人的item高是60,上下padding各为10;然后每个节点里面有length-1条分割线且高度为1
            }
        });

        //滚动到指定的偏移的位置
        this.refs._sectionList.scrollToOffset({animated: true, offset: offset});
    };

    clickItem(flag, item){
        if(flag === 'phone'){
            Linking.openURL("tel:" + item.mobile);
        }else if(flag === 'note'){
            Linking.openURL("smsto:" + item.mobile);
        }else{
            Linking.openURL("mailto:" + item.email);
        }

    }

    _renderSectionHeader(sectionItem){
        const {section} = sectionItem;
        return(
            
                {section.title.toUpperCase()}
            
        );
    }

    _renderItem(item, index){
        const {showIndex} = this.state;
        return(
           {
                                this.setState({
                                    showIndex : item.userId,
                                });
                            }}
          >
              {item.avatar ?
                  
                  :
                  
              }
              
                  
                      {item.name}
                      {item.department}
                      {item.roleName}
                  

                  {showIndex === item.userId ?
                      
                           {
                              this.clickItem('phone', item)
                          }}>
                              {/**/}
                              
                          
                           {
                              this.clickItem('note', item)
                          }}>
                              {/**/}
                              
                          
                          {item.email ?
                               {
                                  this.clickItem('email', item)
                              }}>
                                  {/**/}
                                  
                              
                              : null
                          }
                      
                      :
                      null
                  }
              
          
        );
    }

    render(){
        const {listData, letterArr, sections} = this.state;
        //偏移量 = (设备高度 - 字母索引高度 - 底部导航栏 - 顶部标题栏 - 24)/ 2
        const top_offset = (Dimensions.get('window').height - letterArr.length*22 - 52 - 44 - 24) / 2;

        return(
            
                
                    
                    
                    
                        通讯录
                    
                    
                    
                
                
                     }
                        sections={sections}
                        keyExtractor={(item, index) => index}
                        numColumns={1}
                        renderItem={({item, index}) => this._renderItem(item, index)}
                    />
                    
                    {/**/}
                         index.toString()}       //不重复的key
                            renderItem={({item,index}) =>
                                {this._onSectionselect(index)}}
                                >
                                    {item.toUpperCase()}
                                
                            }
                        />
                    {/**/}
                    
                
            
        )
    }
}

const styles = StyleSheet.create({
    nameStyle: {
        fontSize: 14,
        color: '#1a1a1a',
    },
    userStyle: {
        fontSize: 12,
    },
    iconStyle: {
        color: '#4597cd',
        fontSize: 24,
    },
    iconImg:{
        width: 20,
        height: 20,

    },
    btnStyle: {
        width:30,
        height: 24,
        flexDirection: 'row',
        justifyContent: 'center',
        alignItems: 'center',
        marginHorizontal: 2,
    }

});

最后效果:

react-native使用SectionList做通讯录列表,分组跳转_第10张图片

 

参考博客:https://www.jianshu.com/p/09dd60d7b34f

                  https://blog.csdn.net/u011272795/article/details/74359305

SectionList组件官方文档:https://reactnative.cn/docs/0.55/sectionlist/

 

文章仅为本人学习过程的一个记录,仅供参考,如有问题,欢迎指出

对博客文章的参考,若原文章博主介意,请联系删除!请原谅

你可能感兴趣的:(react,native)