React-native实现联系人列表分组组件(支持拼音搜索)

参考资料:

React Native使用SectionList打造城市选择列表,包含分组的跳转:https://blog.csdn.net/sinat_17775997/article/details/71424324?utm_medium=distribute.pc_relevant.none-task-blog-OPENSEARCH-5&depth_1-utm_source=distribute.pc_relevant.none-task-blog-OPENSEARCH-5

一. 功能特性和效果图
功能特性如下:

  • 支持首字母和首个汉字分组检索。

  • 支持右侧分组字母的跳转控制。

  • 支持拼音搜索。

  • 支持批量选择功能、重置功能。

效果图:

二. 技术要点

  1. SectionList列表分组组件。
  • https://reactnative.cn/docs/sectionlist
  1. pinyin。 这是一个JS拼音工具开源库。
  • 它的用途是将中文转化为拼音。

  • https://github.com/hotoo/pinyin

三. 实现思路

  1. 将联系人列表中的数据项转化为拼音存储,以便于拼音搜索。

  2. 遍历联系人列表,获取字母分组,得到一个set集合,并且转化为数组,该数组用于渲染右侧字母导航组件。

  3. 构建SectionList组件渲染数据所需要的列表数据结构,其数组元素结构为:{title:‘分组名称’,data:[]},其中,title表示分组名称,data表示该分组的数据列表。

遍历遍历联系人列表,根据首字母分组汇总,得到SectionList组件渲染数据所需要的列表数据结构。

四. 关键代码

  1. 将联系人列表中的数据项转化为拼音存储,以便于拼音搜索。

componentWillMount = () => {
// 将数据列表转化为拼音存储,以便于拼音搜索
testData.forEach((item, index, arr) => {
// 将Item的名称转为拼音数组
let pinyinArr = pinyin(item.name, {style: pinyin.STYLE_NORMAL});
item.pinyinArr = pinyinArr;
let pinyinArrStr = ‘’;
// 将拼音数组转化为一个字符串,以支持拼音搜索
for (let i = 0; i < pinyinArr.length; i++) {
for (let j = 0; j < pinyinArr[i].length; j++) {
pinyinArrStr = pinyinArrStr + pinyinArr[i][j];
}
}
item.pinyinArrStr = pinyinArrStr;
});
this.transferToSectionsData(testData);
};
2. 搜索方法

search = () => {
// alert(‘搜索’);
const {dataList, searchValue} = this.state;
if (searchValue && searchValue.trim()) {
let searchValueTemp = searchValue.toLocaleLowerCase();
const resultList = [];
dataList.forEach((item, index, arr) => {
if (item.name) {
if (item.name.toLocaleLowerCase().indexOf(searchValueTemp) >= 0
|| this.pinyinSingleLetterIndexSearch(searchValueTemp, item.pinyinArr) >= 0
|| item.pinyinArrStr.toLocaleLowerCase().indexOf(searchValueTemp) >= 0) {
resultList.push(item);
}
}
});
console.log(‘search.resultList:’, resultList);
this.transferToSectionsData(resultList);
} else {
this.transferToSectionsData(dataList);
}
};

/*** 在拼音数组中搜索单个拼音,如果匹配,则返回等于大于0的值,否则返回-1* @param keyword* @param pinyinArr* @returns {number}*/
pinyinSingleLetterIndexSearch = (keyword, pinyinArr) => {let result = -1;if (keyword && pinyinArr) {for (let i = 0; i < pinyinArr.length; i++) {for (let j = 0; j < pinyinArr[i].length; j++) {let singleLetterIndex = pinyinArr[i][j].toLocaleLowerCase().indexOf(keyword);if (singleLetterIndex >= 0) {return singleLetterIndex;}}}}return result;
};

五. 完整代码
注意:图片资源需自行补充。

/**
*

  • @author chenlw
  • @date 2020/04/18
    */
    import React from ‘react’;
    import {
    Dimensions,
    FlatList,
    SectionList,
    StyleSheet,
    Text,
    TouchableOpacity,
    View,
    SafeAreaView,
    Image,
    TextInput,
    Platform,
    StatusBar,
    } from ‘react-native’;

import pinyin from ‘pinyin’;

let testData = [
{id: ‘盖伦’, name: ‘盖伦’},
{id: ‘崔丝塔娜’, name: ‘崔丝塔娜’},
{id: ‘大发明家’, name: ‘大发明家’},
{id: ‘武器大师’, name: ‘武器大师’},
{id: ‘刀妹’, name: ‘刀妹’},
{id: ‘卡特琳娜’, name: ‘卡特琳娜’},
{id: ‘盲僧’, name: ‘盲僧’},
{id: ‘蕾欧娜’, name: ‘蕾欧娜’},
{id: ‘拉克丝’, name: ‘拉克丝’},
{id: ‘剑圣’, name: ‘剑圣’},
{id: ‘赏金’, name: ‘赏金’},
{id: ‘发条’, name: ‘发条’},
{id: ‘瑞雯’, name: ‘瑞雯’},
{id: ‘提莫’, name: ‘提莫’},
{id: ‘卡牌’, name: ‘卡牌’},
{id: ‘剑豪’, name: ‘剑豪’},
{id: ‘琴女’, name: ‘琴女’},
{id: ‘木木’, name: ‘木木’},
{id: ‘雪人’, name: ‘雪人’},
{id: ‘安妮’, name: ‘安妮’},
{id: ‘薇恩’, name: ‘薇恩’},
{id: ‘小法师’, name: ‘小法师’},
{id: ‘艾尼维亚’, name: ‘艾尼维亚’},
{id: ‘奥瑞利安索尔’, name: ‘奥瑞利安索尔’},
{id: ‘布兰德’, name: ‘布兰德’},
{id: ‘凯特琳’, name: ‘凯特琳’},
{id: ‘虚空’, name: ‘虚空’},
{id: ‘机器人’, name: ‘机器人’},
{id: ‘挖掘机’, name: ‘挖掘机’},
{id: ‘EZ’, name: ‘EZ’},
{id: ‘暴走萝莉’, name: ‘暴走萝莉’},
{id: ‘艾克’, name: ‘艾克’},
{id: ‘波比’, name: ‘波比’},
{id: ‘赵信’, name: ‘赵信’},
{id: ‘牛头’, name: ‘牛头’},
{id: ‘九尾’, name: ‘九尾’},
{id: ‘菲兹’, name: ‘菲兹’},
{id: ‘寒冰’, name: ‘寒冰’},
{id: ‘猴子’, name: ‘猴子’},
{id: ‘深渊’, name: ‘深渊’},
{id: ‘凯南’, name: ‘凯南’},
{id: ‘诺克萨斯’, name: ‘诺克萨斯’},
{id: ‘祖安’, name: ‘祖安’},
{id: ‘德莱文’, name: ‘德莱文’},
{id: ‘德玛西亚王子’, name: ‘德玛西亚王子’},
{id: ‘豹女’, name: ‘豹女’},
{id: ‘皮城执法官’, name: ‘皮城执法官’},
{id: ‘泽拉斯’, name: ‘泽拉斯’},
{id: ‘岩雀’, name: ‘岩雀’},
];
const selectedFieldName = ‘id’;

const isAndroid = Platform.OS === ‘android’;
export default class IndexListComponentExample extends React.PureComponent {

constructor(props) {super(props);this.state = {searchValue: null,dataList: testData,sections: [],       //section数组// letterArr: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'],      //首字母数组letterArr: [],      //首字母数组activeLetterIndex: 0,selectedItemSet: new Set(),// 是否开启批量选择模式batchSelected: true,refreshCount: 0,};
}componentWillMount = () => {// 将数据列表转化为拼音存储,以便于拼音搜索testData.forEach((item, index, arr) => {// 将Item的名称转为拼音数组let pinyinArr = pinyin(item.name, {style: pinyin.STYLE_NORMAL});item.pinyinArr = pinyinArr;let pinyinArrStr = '';// 将拼音数组转化为一个字符串,以支持拼音搜索for (let i = 0; i < pinyinArr.length; i++) {for (let j = 0; j < pinyinArr[i].length; j++) {pinyinArrStr = pinyinArrStr + pinyinArr[i][j];}}item.pinyinArrStr = pinyinArrStr;});this.transferToSectionsData(testData);
};/*** 转化数据列表*/
transferToSectionsData = (dataList) => {//获取联系人列表let sections = [], letterArr = [];// 右侧字母栏数据处理dataList.forEach((item, index, arr) => {let itemTemp = pinyin(item.name.substring(0, 1), {style: pinyin.STYLE_FIRST_LETTER,})[0][0].toUpperCase();letterArr.push(itemTemp);});letterArr = [...new Set(letterArr)].sort();this.setState({letterArr: letterArr});// 分组数据处理letterArr.forEach((item, index, arr) => {sections.push({title: item,data: [],});});dataList.forEach((item1, index1, arr1) => {let listItem = item1;sections.forEach((item2, index2, arr2) => {let firstName = listItem.name.substring(0, 1);let firstLetter = pinyin(firstName, {style: pinyin.STYLE_FIRST_LETTER})[0][0].toUpperCase();let pinyinStrArr = pinyin(listItem.name, {style: pinyin.STYLE_NORMAL});console.log('pinyinStr', pinyinStrArr);if (item2.title === firstLetter) {item2.data.push({firstName: firstName,name: listItem.name,id: listItem.id,});}});});this.setState({sections: sections});
};openBatchSelectedMode = (callback) => {this.setState({batchSelected: true,selectedItemSet: new Set(),}, () => {callback && callback();});
};closeBatchSelectedMode = () => {this.setState({batchSelected: false,selectedItemSet: new Set(),});
};addOrDeleteSelectedItem = (item) => {const {batchSelected, selectedItemSet, refreshCount} = this.state;if (!batchSelected) {return;}if (item && item[selectedFieldName]) {if (selectedItemSet.has(item[selectedFieldName])) {selectedItemSet.delete(item[selectedFieldName]);} else {selectedItemSet.add(item[selectedFieldName]);}console.log('addOrDeleteSelectedItem.selectedItemSet', selectedItemSet);this.setState({selectedItemSet: selectedItemSet,refreshCount: refreshCount + 1,}, () => {});}
};/*** 重置选中的成员*/
clearSelectedItem = () => {const {batchSelected, selectedItemSet, refreshCount} = this.state;selectedItemSet.clear();this.setState({selectedItemSet: selectedItemSet,refreshCount: refreshCount + 1,}, () => {});
};// 字母关联分组跳转
_onSectionselect = (key) => {this.setState({activeLetterIndex: key,}, () => {});this.refs._sectionList.scrollToLocation({itemIndex: 0,sectionIndex: key,viewOffset: 20,});};// 分组列表的头部
_renderSectionHeader(sectionItem) {const {section} = sectionItem;return (<View style={{height: 20,backgroundColor: '#e7f0f9',paddingHorizontal: 10,flexDirection: 'row',alignItems: 'center',}}><Text style={{fontSize: 16}}>{section.title.toUpperCase()}</Text></View>);
}renderItemSelectedIcon = (item) => {if (!item) {return;}const {batchSelected, selectedItemSet} = this.state;if (batchSelected) {let isActive = selectedItemSet.has(item[selectedFieldName]);return (<Imagestyle={{width: 18,height: 18,marginRight: 10,}}source={isActive? require('@assets/icons/common/icon_item_selected.png'): require('@assets/icons/common/icon_item_unselected.png')}/>);} else {return null;}
};_renderItem(item, index) {const {batchSelected} = this.state;return (<TouchableOpacitystyle={{paddingLeft: 20,paddingRight: 30,height: 50,flexDirection: 'row',justifyContent: 'flex-start',alignItems: 'center',borderBottomWidth: 1,borderBottomColor: '#efefef',}}activeOpacity={.75}onLongPress={() => {if (!batchSelected) {this.openBatchSelectedMode(() => {this.addOrDeleteSelectedItem(item);});}}}onPress={() => {this.addOrDeleteSelectedItem(item);}}>{this.renderItemSelectedIcon(item)}<View style={{flexDirection: 'row',alignItems: 'center',// justifyContent: 'space-between',flexGrow: 1,}}><View style={{// padding: 10,height: 30,width: 30,justifyContent: 'center',alignItems: 'center',backgroundColor: '#2988FF',}}><Text style={{color: '#fff',fontSize: 18,}}>{item.firstName}</Text></View><View style={{marginLeft: 10,}}><Text style={{}}>{item.name}</Text></View></View></TouchableOpacity>);
}renderBatchSelectedHeader = () => {const {batchSelected, selectedItemSet} = this.state;if (!batchSelected) {return (<View style={{paddingLeft: 10,paddingRight: 10,height: 50,flexDirection: 'row',justifyContent: 'space-between',alignItems: 'center',borderBottomWidth: 1,borderBottomColor: '#efefef',}}><TouchableOpacitystyle={{padding: 10,}}></TouchableOpacity><View style={{}}></View><TouchableOpacitystyle={{padding: 10,}}onPress={() => {this.openBatchSelectedMode();}}><Text>批量选择</Text></TouchableOpacity></View>);}return (<View style={{paddingLeft: 10,paddingRight: 10,height: 50,flexDirection: 'row',justifyContent: 'space-between',alignItems: 'center',}}><TouchableOpacitystyle={{padding: 10,}}onPress={() => {this.closeBatchSelectedMode();}}><Text>取消</Text></TouchableOpacity><View style={{}}><Text>已选择{selectedItemSet.size}条记录</Text></View><TouchableOpacitystyle={{padding: 10,}}onPress={() => {this.closeBatchSelectedMode();}}><Text>确定</Text></TouchableOpacity></View>);
};render = () => {const {letterArr, sections, activeLetterIndex, batchSelected} = this.state;//偏移量 = (设备高度 - 字母索引高度 - 底部导航栏 - 顶部标题栏 - 24)/ 2let top_offset = (Dimensions.get('window').height - letterArr.length * 16 - 52 - 44 - 24) / 2;if (isAndroid) {top_offset = top_offset + StatusBar.currentHeight + 45;}return (<SafeAreaView style={{flex: 1,}}>{this.renderSearchBar()}{this.renderBatchSelectedHeader()}<SectionListref="_sectionList"renderItem={({item, index}) => this._renderItem(item, index)}renderSectionHeader={this._renderSectionHeader.bind(this)}sections={sections}keyExtractor={(item, index) => item + index}ItemSeparatorComponent={() => <View/>}/>{/*右侧字母栏*/}<View style={{position: 'absolute', width: 26, right: 0, top: top_offset}}><FlatListdata={letterArr}keyExtractor={(item, index) => index.toString()}renderItem={({item, index}) => {let isActive = index === activeLetterIndex;// let textStyle = isActive ? styles.activeIndicatorText : styles.inactiveIndicatorText;// let containerStyle = isActive ? styles.activeIndicatorContainer : styles.inactiveIndicatorContainer;let textStyle = styles.inactiveIndicatorText;let containerStyle = styles.inactiveIndicatorContainer;return (<TouchableOpacitystyle={[{marginVertical: 2,height: 16,width: 16,borderRadius: 8,flexDirection: 'row',justifyContent: 'center',alignItems: 'center',},containerStyle,]}onPress={() => {this._onSectionselect(index);}}><Text style={[{fontSize: 12,}, textStyle]}>{item.toUpperCase()}</Text></TouchableOpacity>);}}/></View></SafeAreaView>);
};setSearchValue = (searchValue, callback) => {this.setState({searchValue: searchValue,}, () => {callback && callback();});
};search = () => {// alert('搜索');const {dataList, searchValue} = this.state;if (searchValue && searchValue.trim()) {let searchValueTemp = searchValue.toLocaleLowerCase();const resultList = [];dataList.forEach((item, index, arr) => {if (item.name) {if (item.name.toLocaleLowerCase().indexOf(searchValueTemp) >= 0|| this.pinyinSingleLetterIndexSearch(searchValueTemp, item.pinyinArr) >= 0|| item.pinyinArrStr.toLocaleLowerCase().indexOf(searchValueTemp) >= 0) {resultList.push(item);}}});console.log('search.resultList:', resultList);this.transferToSectionsData(resultList);} else {this.transferToSectionsData(dataList);}
};/*** 在拼音数组中搜索单个拼音,如果匹配,则返回等于大于0的值,否则返回-1* @param keyword* @param pinyinArr* @returns {number}*/
pinyinSingleLetterIndexSearch = (keyword, pinyinArr) => {let result = -1;if (keyword && pinyinArr) {for (let i = 0; i < pinyinArr.length; i++) {for (let j = 0; j < pinyinArr[i].length; j++) {let singleLetterIndex = pinyinArr[i][j].toLocaleLowerCase().indexOf(keyword);if (singleLetterIndex >= 0) {return singleLetterIndex;}}}}return result;
};renderSearchBar = () => {const {searchValue} = this.state;return (<View style={{flexDirection: 'row',alignItems: 'center',paddingTop: 10,paddingBottom: 10,backgroundColor: '#fff',borderBottomWidth: 1,borderBottomColor: '#efefef',}}><TouchableOpacitystyle={{paddingLeft: 15,paddingRight: 15,}}onPress={() => {}}><Image source={require('@assets/icons/common/icon_back.png')}/></TouchableOpacity><Viewstyle={{flex: 1,}}><View style={{height: 30,backgroundColor: '#F0F0F0',borderRadius: 30,paddingLeft: 10,flexDirection: 'row',alignItems: 'center',justifyContent: 'center',}}><Image source={require('@assets/icons/common/icon_search.png')}style={{width: 15, height: 15}}/><TextInputplaceholder="输入查询内容..."maxLength={100}style={{flex: 1,marginLeft: 5,marginRight: 5,paddingTop: 5,paddingBottom: 5,paddingRight: 5,// backgroundColor: 'pink',}}autoFocus={false}value={searchValue}onChangeText={(text) => {this.setSearchValue(text, () => {this.search();});}}onSubmitEditing={() => {}}/>{searchValue? <TouchableOpacitystyle={{marginRight: 10,}}onPress={() => {this.setSearchValue(null, () => {this.search();});}}><Image source={require('@assets/icons/common/icon_search_cancel.png')}style={{width: 17,height: 17,}}/></TouchableOpacity>: null}</View></View><TouchableOpacitystyle={{paddingLeft: 10,paddingRight: 10,justifyContent: 'center',alignItems: 'center',}}onPress={() => {this.search();}}><Text style={{color: '#2988FF',fontSize: 16,}}>搜索</Text></TouchableOpacity></View>);
};

}

const styles = StyleSheet.create({
taskNodeTitleText: {
color: ‘#333333’,
fontWeight: ‘bold’,
fontSize: 16,
},
inactiveIndicatorContainer: {},
activeIndicatorContainer: {
backgroundColor: ‘#2988FF’,
},
inactiveIndicatorText: {
color: ‘#666666’,
},
activeIndicatorText: {
color: ‘#fff’,
},
});

————————————————
版权声明:本文为CSDN博主「举世武双」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_33721382/article/details/106007853

React-native实现联系人列表分组组件相关推荐

  1. React Native使用指南-原生UI组件

    在如今的App中,已经有成千上万的原生UI部件了--其中的一些是平台的一部分,另一些可能来自于一些第三方库,而且可能你自己还收藏了很多.React Native已经封装了大部分最常见的组件,譬如Scr ...

  2. native react 折线图_【详解】纯 React Native 代码自定义折线图组件(译)

    本文为 Marno 翻译,转载必须保留出处! 公众号[ Marno ],关注后回复 RN 加入交流群 React Native 优秀开源项目大全:http://www.marno.cn 一.前言 在移 ...

  3. 如何在React Native中使用文本输入组件?

    You know, an app becomes more authentic and professional when there is the interaction between the a ...

  4. 【React Native开发】React Native控件之RefreshControl组件具体解释(21)

    转载请标明出处: http://blog.csdn.net/developer_jiangqq/article/details/50672747 本文出自:[江清清的博客] (一)前言 [好消息]个人 ...

  5. 【React Native开发】React Native控件之TextInput组件讲解与QQ登录界面实现(11)

    转载请标明出处: http://blog.csdn.net/developer_jiangqq/article/details/50589570 本文出自:[江清清的博客] (一)前言 [好消息]个人 ...

  6. android 高德地图 3d,在React Native中使用 高德地图组件react-native-amap3d

    react-native 高德地图组件,使用最新 3D SDK,支持 Android + iOS,受react-native-maps启发,提供功能丰富且易用的接口. node 主要功能:react ...

  7. react native中有关日期的组件DatePicker 示例

    1.用node加载react-native-datepicker包 npm install react-native-datepicker 2.直接上代码 import React, { PureCo ...

  8. React Native 二 常用组件与开源组件

    2019独角兽企业重金招聘Python工程师标准>>> #0.手把手教React Native实战之开山篇##作者简介东方耀 Android开发RN技术 facebookgithub ...

  9. 移动开发者如何更好地学习 React Native? | 技术头条

    作者 | 魔笛 责编 | 郭芮 2015年3月,Facebook正式发布react-native,只支持iOS平台:2015年9月,Facebook发布了React Native for Androi ...

最新文章

  1. 使用CFURLCreateStringByAddingPercentEscapes进行URL编码
  2. 编程之美2.10 寻找数组中的最大值和最小值
  3. 3个月带你通关Go语言
  4. Shell中自定义函数
  5. SAP成都研究院马洪波:提升学习力,增强竞争力,收获一生乐趣
  6. 测试驱动开发 测试前移_我如何以及为什么认为测试驱动开发值得我花时间
  7. java 数组详解_java_day03:数组详解
  8. linux脚本输出缓存上限,关于 Linux 下后台执行 Python 脚本的缓冲问题
  9. mysql union all 别名_mysql union 与 union all 语法及用法
  10. java取下一天的零点
  11. 各种浏览器开启JavaScript脚本方法
  12. 嵌入式系统开发环境的构建08:在Hyper-V中安装Windows XP操作系统
  13. 575. 分糖果【我亦无他唯手熟尔】
  14. 用C语言算两个数的商和
  15. cncert网络安全周报35期 境内被植入后门的政府网站112个 环比上涨24.4%
  16. ASCII 码对照表(全)
  17. 电商WMS与云仓储-E店宝-陈涛
  18. 鹅鸭杀否认被腾讯收购 ;苹果已累计售出超 23.2 亿部 iPhone 手机;Android 13 测实用新功能|极客头条...
  19. JS逆向之某学习平台加密视频解密
  20. 【大数据开发运维解决方案】超级详细的VMware16安装Redhat8挂载镜像配置本地yum源安装unixODBC教程

热门文章

  1. 老毛桃制作装机版u盘
  2. html5支持2指触摸吗,html5 touch事件的几个问题
  3. 手动部署java jar包
  4. wifidog 源码初分析(三)
  5. 【图解】linux中安装WPS
  6. 世界上第一台计算机应用于什么方面,世界上第一台计算机的电子元器件是什么...
  7. 论文笔记: 图神经网络 GAT
  8. 如何监控代理记账行业的客户信息安全?
  9. h5怎么获取微信用户openId,h5如何获取微信用户openId
  10. 支付宝七(商户会员卡之发放卡券)