实现功能:
- 根据拼音,拼音首字母,汉字检索
- 右侧字母导航栏点击可跳转到对应字母处
- 字母排序
- 多选功能
需要修改的地方,数据源,图片资源,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',
},
});```