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

 

一. 功能特性和效果图

功能特性如下:

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

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

- 支持拼音搜索。

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

 

效果图:

React-native实现联系人列表分组组件(支持拼音搜索)_第1张图片

 

React-native实现联系人列表分组组件(支持拼音搜索)_第2张图片

 

二. 技术要点

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 (
            
                {section.title.toUpperCase()}
            
        );
    }

    renderItemSelectedIcon = (item) => {
        if (!item) {
            return;
        }
        const {batchSelected, selectedItemSet} = this.state;
        if (batchSelected) {
            let isActive = selectedItemSet.has(item[selectedFieldName]);
            return (
                
            );
        } else {
            return null;
        }
    };

    _renderItem(item, index) {
        const {batchSelected} = this.state;
        return (
             {
                    if (!batchSelected) {
                        this.openBatchSelectedMode(() => {
                            this.addOrDeleteSelectedItem(item);
                        });
                    }
                }}
                onPress={() => {
                    this.addOrDeleteSelectedItem(item);
                }}
            >
                {
                    this.renderItemSelectedIcon(item)
                }
                
                    
                        
                            {item.firstName}
                        
                    
                    
                        {item.name}
                    
                
            
        );
    }

    renderBatchSelectedHeader = () => {
        const {batchSelected, selectedItemSet} = this.state;
        if (!batchSelected) {
            return (
                
                    
                    
                    
                    
                     {
                            this.openBatchSelectedMode();
                        }}
                    >
                        批量选择
                    
                
            );
        }
        return (
            
                 {
                        this.closeBatchSelectedMode();
                    }}
                >
                    取消
                
                
                    已选择{selectedItemSet.size}条记录
                
                 {
                        this.closeBatchSelectedMode();
                    }}
                >
                    确定
                
            
        );
    };


    render = () => {
        const {letterArr, sections, activeLetterIndex, batchSelected} = this.state;
        //偏移量 = (设备高度 - 字母索引高度 - 底部导航栏 - 顶部标题栏 - 24)/ 2
        let top_offset = (Dimensions.get('window').height - letterArr.length * 16 - 52 - 44 - 24) / 2;
        if (isAndroid) {
            top_offset = top_offset + StatusBar.currentHeight + 45;
        }
        return (
            
                {
                    this.renderSearchBar()
                }
                {
                    this.renderBatchSelectedHeader()
                }
                 this._renderItem(item, index)}
                    renderSectionHeader={this._renderSectionHeader.bind(this)}
                    sections={sections}
                    keyExtractor={(item, index) => item + index}
                    ItemSeparatorComponent={() => }
                />

                {/*右侧字母栏*/}
                
                     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 (
                                 {
                                        this._onSectionselect(index);
                                    }}
                                >
                                    
                                        {item.toUpperCase()}
                                    
                                
                            );
                        }}
                    />
                

            
        );
    };

    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 (
            
                 {

                    }}
                >
                    
                
                
                    
                        
                         {
                                this.setSearchValue(text, () => {
                                    this.search();
                                });
                            }}
                            onSubmitEditing={() => {

                            }}
                        />
                        {
                            searchValue
                                ?  {
                                        this.setSearchValue(null, () => {
                                            this.search();
                                        });
                                    }}
                                >
                                    
                                
                                : null
                        }
                    

                
                 {
                        this.search();
                    }}
                >
                    
                        搜索
                    
                
            
        );
    };

}

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

 

 

 

 

 

 

 

 

你可能感兴趣的:(我的React,Native进阶之路)