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

2. 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',},
});

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

  1. React-native实现联系人列表分组组件

    React-native实现联系人列表分组组件(支持拼音搜索) 参考资料: React Native使用SectionList打造城市选择列表,包含分组的跳转:https://blog.csdn.ne ...

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

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

  3. 自制Unity文件查找器,支持拼音搜索

    前言 一个大型项目,配置表上千个是很正常的,这个时候想要搜索某个配置表,是有点蛋疼的事情. 自己写个配置查找器吧,效果如下: 优点: 搜索速度快: 支持拼音搜索: 支持快速打开: 缺点: 拼音只能挨个 ...

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

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

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

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

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

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

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

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

  8. select2.js插件新增支持拼音搜索

    话不多说直奔主题 1.拼音搜索的原理: 将下拉框选项中的中文转换成汉语拼音,然后与输入的字母进行比较,如果包含则被检索出来. 2.效果: 3.在select2.js中找到matcher 方法,对此方法 ...

  9. layui风格穿梭组件 带拼音搜索 添加多选

    基于前辈的组件修改,layui风格,已修改成layui第三封控件规范 在原基础上添加多选: 添加拼音搜索: [原文]整合 layui 穿梭框组件 [引用]拼音搜索用到的JS,用法简单! 链接失效的话, ...

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

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

最新文章

  1. 7个小众却很有意思的工具推荐,每一个都是大宝藏!
  2. FormsAuthentication详解
  3. IIS 的身份验证简要说明 - 摘录
  4. 【Elasticsearch 2.x】issues
  5. 成功解决Exception “unhandled AttributeError“ module ‘h5py‘ has no attribute ‘File‘
  6. 编译通过,但在运行时报Resolution of the dependency failed
  7. iOS_TableView的相关操作
  8. 系统架构设计师软考考后回顾
  9. Pyspark访问Hbase
  10. 【Objective-C】08-self关键字
  11. Conficker蠕虫病毒专杀工具集锦
  12. [C/C++]标准MIDI文件格式
  13. HDU 6287 口算训练(分解质因子 + 二分下标)
  14. 台州银行笔试考什么_历年台州银行笔试和面试经验分享
  15. position: sticky 属性
  16. 牛客小白月赛58 B(暴力)C(思维)D(dp滚动数组优化)
  17. 4、cas Server连接mysql
  18. 如何理解CRM软件里的销售机会与线索
  19. Centos(操作系统)
  20. linux下查找某文件/文件夹所在的位置

热门文章

  1. 体重 年龄 性别 身高 预测鞋码_根据一个人的身高和体重能知道他的鞋码吗?
  2. 【OpenCV学习】(三)色彩及矩阵操作
  3. 十二笔记(一):winfrom 引用 FastReport.dll 打印标签
  4. 计算机频繁开机是什么原因,电脑频繁自动重启什么原因
  5. Robotframework之下拉列表select
  6. 【转载】Win10强制删除文件夹
  7. 新浪微博 mysql_新浪微博,腾讯微博mysql数据库主表猜想
  8. Android 真实简历
  9. 阿里云无影云桌面工作区详解
  10. Q3面试嵌入式软件工程师的面试经验