react-native 实现好友列表通讯录功能

实现功能:

  1. 根据拼音,拼音首字母,汉字检索
  2. 右侧字母导航栏点击可跳转到对应字母处
  3. 字母排序
  4. 多选功能

需要修改的地方,数据源,图片资源,import处改成自己的,或者可以用我的

效果图:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

完整代码-------------


import React from 'react';
import {
    Dimensions,
    FlatList,
    SectionList,
    StyleSheet,
    Text,
    TouchableOpacity,
    View,
    SafeAreaView,
    Image,
    TextInput,
    Platform,
    StatusBar,
} from 'react-native';
import {pxToDp} from '../../../utils/stylesKits'; //像素转换工具
import {pinyin} from '../../../utils/pinyin';   //拼音  yarn add pinyin
import request from "../../../utils/request";  //后封装的请求方法,自行选择
import {Friend_LIST} from '../../../utils/pathMap'; //接口地址,自行选择

let  list=[]; //接口请求存放的数据
const selectedFieldName = 'id';
 
const isAndroid = Platform.OS === 'android';
export default class IndexListComponentExample extends React.PureComponent {    
    isLoading = false;      
    constructor(props) {
        super(props);
        this.getList()
        
        this.state = {
            searchValue: null, //搜索框里的值
        
            dataList: list,
            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: false,
            refreshCount: 0,
            
        };
        
    }

    // 获取 好友列表--更改你的接口
  getList = async (isNew = false) => {
    const res = await request.privateGet(Friend_LIST);
    this.setState({ list: res.data });

    // console.log(this.state.list);
    if (isNew) {
      // 重置数据
      this.setState({ list: res.data });
    //   console.log(this.state.list);
    } else {
      this.setState({ list: [...list, ...res.data] });
    //   console.log(this.state.list);

    }
   
    this.isLoading = false;
    this.getCC();
  }
  //拼音转换
  getCC=()=>{
        // 将数据列表转化为拼音存储,以便于拼音搜索
        //-----------数据源
        this.state.list.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(this.state.list);
  }
 
    /**
     * 转化数据列表
     */
    transferToSectionsData = (list) => {
        //获取联系人列表
        let sections = [], letterArr = [];
        // 右侧字母栏数据处理
        list.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: [],
            });
        });
 
        list.forEach((item1, index1, arr1) => {
            let listItem = item1;
            sections.forEach((item2, index2, arr2) => {
                let avatar=listItem.avatar;
                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});
                if (item2.title === firstLetter) {
                    item2.data.push({
                        avatar:avatar,
                        // 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 (
                <Image
                    style={{
                        width: 18,
                        height: 18,
                        marginRight: 10,
                    }}
                    source={isActive
                        ? require('../../../res/xuanzhong.png')
                        : require('../../../res/weixuanzhong.png')}
                />
            );
        } else {
            return null;
        }
    };
 
    _renderItem(item, index) {
        const {batchSelected} = this.state;
        return (
            <TouchableOpacity
                style={{
                    paddingLeft: pxToDp(20),
                    paddingRight: pxToDp(30),
                    height: pxToDp(50),
                    flexDirection: 'row',
                    justifyContent: 'flex-start',
                    alignItems: 'center',
                    borderBottomWidth: 1,
                    backgroundColor:"#FFFFFF",
                    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: pxToDp(30),
                        width: pxToDp(30),
                        justifyContent: 'center',
                        alignItems: 'center',
                        backgroundColor: '#fff',
                    }}>
                       
                            <Image style={{
                             width: pxToDp(35), height: pxToDp(35),
                             borderRadius:pxToDp(25)
                            }} source={{ uri: item.avatar }} />
                        
                    </View>
                    <View style={{
                        marginLeft: 20,
                    }}>
                        <View>
                        <View style={{flexDirection:"row",alignItems:"center"}}>
                            <Text style={{fontSize:pxToDp(12)}}>{item.name} </Text>
                            <Text style={{fontSize:pxToDp(10),left:pxToDp(8)}}>金牛座</Text>

                        </View>
                        <Text style={{fontSize:pxToDp(8),color:"#555"}}>今天天气真好啊,适合上山放羊呢</Text>
                        </View>
                    </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,
                    backgroundColor:"#FFFFFF",
                    borderBottomColor: '#efefef',
                }}>
                    <TouchableOpacity
                        style={{
                            padding: 10,
                        }}
                    >
                    </TouchableOpacity>
                    <View style={{}}>
                    </View>
                    <TouchableOpacity
                        style={{
                            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',
            }}>
                <TouchableOpacity
                    style={{
                        padding: 10,
                    }}
                    onPress={() => {
                        this.closeBatchSelectedMode();
                    }}
                >
                    <Text>取消</Text>
                </TouchableOpacity>
                <View style={{}}>
                    <Text>已选择{selectedItemSet.size}条记录</Text>
                </View>
                <TouchableOpacity
                    style={{
                        padding: 10,
                    }}
                    onPress={() => {
                        this.closeBatchSelectedMode();
                    }}
                >
                    <Text>确定</Text>
                </TouchableOpacity>
            </View>
        );
    };
 
 
    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 (
            <SafeAreaView style={{
                flex: 1,
            }}>
                {
                    this.renderSearchBar()
                }
                {/* 新朋友 */}
                <View style={{
                    backgroundColor:"#FFFFFF",
                    alignContent:"center",
                    flexDirection:"row",
                    borderBottomWidth: 1,
                    borderBottomColor: '#efefef'
                    }}>
                    <TouchableOpacity style={{
                        height:pxToDp(50),
                        width:"100%",
                        flexDirection:"row",
                        left:pxToDp(20),
                        alignItems:"center"
                    }}>                    
                        <View style={{
                        width: pxToDp(30), height: pxToDp(30), 
                        backgroundColor: "#1adbde", alignItems: 'center', justifyContent: 'center',borderRadius:5
                        }}>
                        <Image source={require('../../../res/xinpengyou.png')}
                        style={{width: pxToDp(30), height: pxToDp(30),color:"black"}}/>
                        {/* <IconFont style={{ color: "#fff", fontSize: pxToDp(24) }} name="iconxihuan-o" /> */}
                        </View>
                        <Text style={{ color: "#444",fontSize:pxToDp(20) ,left:pxToDp(18)}}>新朋友</Text>
                    
                    </TouchableOpacity>
                    
                </View>
                {/* 其他 */}
                <View style={{
                    backgroundColor:"#FFFFFF",
                    alignContent:"center",
                    flexDirection:"row",
                    borderBottomWidth: 1,
                    borderBottomColor: '#efefef'
                    }}>
                    <TouchableOpacity style={{
                        height:pxToDp(50),
                        width:"100%",
                        flexDirection:"row",
                        left:pxToDp(20),
                        alignItems:"center"
                    }}>                    
                        <View style={{
                        width: pxToDp(30), height: pxToDp(30), 
                        backgroundColor: "#1adbde", alignItems: 'center', justifyContent: 'center',borderRadius:5
                        }}>
                        <Image source={require('../../../res/xinpengyou.png')}
                        style={{width: pxToDp(30), height: pxToDp(30),color:"black"}}/>
                        {/* <IconFont style={{ color: "#fff", fontSize: pxToDp(24) }} name="iconxihuan-o" /> */}
                        </View>
                        <Text style={{ color: "#444",fontSize:pxToDp(20) ,left:pxToDp(18)}}>其他</Text>
                    
                    </TouchableOpacity>
                    
                </View>
                {
                    this.renderBatchSelectedHeader()
                }
                <SectionList
                    ref="_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}}>
                    <FlatList
                        data={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 (
                                <TouchableOpacity
                                    style={[
                                        {
                                            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 {list, searchValue} = this.state;
        console.log(searchValue);

        if (searchValue && searchValue.trim()) {
            let searchValueTemp = searchValue.toLocaleLowerCase();
            const resultList = [];
            list.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(list);
        }
    };
 
    /**
     * 在拼音数组中搜索单个拼音,如果匹配,则返回等于大于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',
            }}>
                <TouchableOpacity
                    style={{
                        marginTop:8,
                        paddingLeft: 15,
                        paddingRight: 15,
                    }}
                    onPress={() => {
 
                    }}
                >
                    {/* 搜索 */}
                    <Image style={{width: 20, height: 20}}
                    source={require('../../../res/fanhui.png')}/>
                </TouchableOpacity>
                <View
                    style={{
                        flex: 1,
                    }}
                >
                    <View style={{
                        marginTop:10,
                        height: 30,
                        backgroundColor: '#F0F0F0',
                        borderRadius: 30,
                        paddingLeft: 10,
                        flexDirection: 'row',
                        alignItems: 'center',
                        justifyContent: 'center',
                    }}>

                        <Image source={require('../../../res/sousuo.png')}
                               style={{width: 15, height: 15}}/>
                        <TextInput
                            placeholder="输入查询内容..."
                            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
                                ? <TouchableOpacity
                                    style={{
                                        marginRight: 10,
                                    }}
                                    onPress={() => {
                                        this.setSearchValue(null, () => {
                                            this.search();
                                        });
                                    }}
                                >
                                    <Image source={require('../../../res/1.png')}
                                           style={{
                                               width: 17,
                                               height: 17,
                                           }}
                                    />
                                </TouchableOpacity>
                                : null
                        }
                    </View>
 
                </View>
                <TouchableOpacity
                    style={{
                        marginTop:10,
                        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',
    },
});```


  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值