React-native实现联系人列表分组组件(支持拼音搜索)
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实现联系人列表分组组件(支持拼音搜索)相关推荐
- React-native实现联系人列表分组组件
React-native实现联系人列表分组组件(支持拼音搜索) 参考资料: React Native使用SectionList打造城市选择列表,包含分组的跳转:https://blog.csdn.ne ...
- React Native使用指南-原生UI组件
在如今的App中,已经有成千上万的原生UI部件了--其中的一些是平台的一部分,另一些可能来自于一些第三方库,而且可能你自己还收藏了很多.React Native已经封装了大部分最常见的组件,譬如Scr ...
- 自制Unity文件查找器,支持拼音搜索
前言 一个大型项目,配置表上千个是很正常的,这个时候想要搜索某个配置表,是有点蛋疼的事情. 自己写个配置查找器吧,效果如下: 优点: 搜索速度快: 支持拼音搜索: 支持快速打开: 缺点: 拼音只能挨个 ...
- 如何在React Native中使用文本输入组件?
You know, an app becomes more authentic and professional when there is the interaction between the a ...
- native react 折线图_【详解】纯 React Native 代码自定义折线图组件(译)
本文为 Marno 翻译,转载必须保留出处! 公众号[ Marno ],关注后回复 RN 加入交流群 React Native 优秀开源项目大全:http://www.marno.cn 一.前言 在移 ...
- 【React Native开发】React Native控件之TextInput组件讲解与QQ登录界面实现(11)
转载请标明出处: http://blog.csdn.net/developer_jiangqq/article/details/50589570 本文出自:[江清清的博客] (一)前言 [好消息]个人 ...
- 【React Native开发】React Native控件之RefreshControl组件具体解释(21)
转载请标明出处: http://blog.csdn.net/developer_jiangqq/article/details/50672747 本文出自:[江清清的博客] (一)前言 [好消息]个人 ...
- select2.js插件新增支持拼音搜索
话不多说直奔主题 1.拼音搜索的原理: 将下拉框选项中的中文转换成汉语拼音,然后与输入的字母进行比较,如果包含则被检索出来. 2.效果: 3.在select2.js中找到matcher 方法,对此方法 ...
- layui风格穿梭组件 带拼音搜索 添加多选
基于前辈的组件修改,layui风格,已修改成layui第三封控件规范 在原基础上添加多选: 添加拼音搜索: [原文]整合 layui 穿梭框组件 [引用]拼音搜索用到的JS,用法简单! 链接失效的话, ...
- android 高德地图 3d,在React Native中使用 高德地图组件react-native-amap3d
react-native 高德地图组件,使用最新 3D SDK,支持 Android + iOS,受react-native-maps启发,提供功能丰富且易用的接口. node 主要功能:react ...
最新文章
- 7个小众却很有意思的工具推荐,每一个都是大宝藏!
- FormsAuthentication详解
- IIS 的身份验证简要说明 - 摘录
- 【Elasticsearch 2.x】issues
- 成功解决Exception “unhandled AttributeError“ module ‘h5py‘ has no attribute ‘File‘
- 编译通过,但在运行时报Resolution of the dependency failed
- iOS_TableView的相关操作
- 系统架构设计师软考考后回顾
- Pyspark访问Hbase
- 【Objective-C】08-self关键字
- Conficker蠕虫病毒专杀工具集锦
- [C/C++]标准MIDI文件格式
- HDU 6287 口算训练(分解质因子 + 二分下标)
- 台州银行笔试考什么_历年台州银行笔试和面试经验分享
- position: sticky 属性
- 牛客小白月赛58 B(暴力)C(思维)D(dp滚动数组优化)
- 4、cas Server连接mysql
- 如何理解CRM软件里的销售机会与线索
- Centos(操作系统)
- linux下查找某文件/文件夹所在的位置