鸿蒙Next实现通讯录索引条AlphabetIndexer
当我们需要列表展示通讯录、城市名时,通常会使用到右侧的索引条,可以帮助用户快速定位到某一类的头部。本文介绍一下使用List+ListItemGroup+AlphabetIndexer实现2种常见模式的通讯录。看一下实现效果:
实现过程: 1.以通讯录为例,联系人一般我们以首字母分类,所以索引列表就是名字的首字母A-Z,由于会有一些特殊符号,或者数字开头等不是汉字或字母开头的,我们都归类为#,这样我们就定义好了,索引列表数据
private value: string[] = ['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','#'];
2.列表中,我们需要将不同首字母的名字展示到一组,如果想要实现吸顶效果,这时就需要用到ListItemGroup,使用scrollToItemInGroup,可以快速定位到某一组的头部。因此我们需要将联系人数据分组,如果接口数据已经时分组好的,那就不需要我们处理了,如果是没有分组好的,我们需要借用三方工具,将数据做一下分组,并且以字母顺序排好,这里我直接举一个分组好的例子。
contactGroup: ContactGroup[] = [
{
letter: 'A',
name: ['艾先生', '艾女士', '安迪', '爱迪生']
},
{
letter: 'B',
name: ['爸爸', '宝宝', '白色', '白鹿']
},
{
letter: 'C',
name: ['曹操', '曹丕', '曹植', '崔崔']
},
{
letter: 'D',
name: ['邓小', '邓超']
},
{
letter: 'F',
name: ['冯小刚', '方形']
},
{
letter: 'Z',
name: ['赵丽颖', '张雨绮','张韶涵']
},
{
letter: '#',
name: ['12345', '110', '120', '119', '114']
}
];
3.分组联系人数据展示,需要定义一个ListItemGroup头部组件,头部只需要展示A-Z#,ListItem展示联系人具体信息。 4.添加索引组件,详细属性说明在源码中添加了注释 5.索引选中,关联List滑动。当索引选中时,会回调onSelect,返回选中的index。例如选中索引A,返回index=0, 然后获取分组联系人letter=A的index,调用ListScroller的scrollToItemInGroup方法,可以定位到分组A联系人。其中scrollToItemInGroup需要传2个参数,分别是分组A的index,组内元素的scrollToItemInGroup,这个在二级索引时会用到,这里我们传0就可以。 6.当滑动List时,关联索引选中值。监听List的onScrollIndex方法,该方法返回了当前list可见的ListItemGroup,然后通过letter,定位到AlphabetIndexer选中索引。 7.如果需要实现二级索引,需要监听AlphabetIndexer的onRequestPopupData函数,该方法设置提示弹窗二级索引项内容事件,回调参数为当前选中项索引,回调返回值为提示弹窗需显示的二级索引项内容,这里我们提前写死了2级索引数组。
//ABCDFZ一级索引选中时 返回的二级索引 仿手机通讯录,去重姓分组
private arrayA: string[] = ['艾', '安', '爱'];
private arrayB: string[] = ['爸', '宝', '白'];
private arrayC: string[] = ['曹', '崔'];
private arrayD: string[] = ['邓'];
private arrayF: string[] = ['冯', '方'];
private arrayZ: string[] = ['赵', '张'];
//onRequestPopupData 函数回调
onRequestPopupData((index: number) => {
//一级索引选中时,返回二级索引的数据
if (this.value[index] == 'A') {
this.popupData=[]
this.popupData=[...this.arrayA]
return this.arrayA;
} else if (this.value[index] == 'B') {
this.popupData=[]
this.popupData=[...this.arrayB]
return this.arrayB;
} else if (this.value[index] == 'C') {
this.popupData=[]
this.popupData=[...this.arrayC]
return this.arrayC;
} else if (this.value[index] == 'D') {
this.popupData=[]
this.popupData=[...this.arrayD]
return this.arrayD;
} else if (this.value[index] == 'F') {
this.popupData=[]
this.popupData=[...this.arrayF]
return this.arrayF;
} else if (this.value[index] == 'Z') {
this.popupData=[]
this.popupData=[...this.arrayZ]
return this.arrayZ;
} else {
this.popupData=[]
return [];
}
})
8.二级索引点击时,跳转到对应的联系人位置,通过监听onPopupSelect,弹窗二级索引选中事件,回调参数为当前选中二级索引项索引,然后去匹配一级索引对应的分组中联系人的姓在group中的index,然后使用第5步中提到的方法,定位到分组中的联系人。
源码
export interface ContactGroup {
letter: string;
name: string[];
}
@Entry
@ComponentV2
struct AlphabetIndexerSample {
private scroller: ListScroller = new ListScroller();
private arrayA: string[] = ['艾', '安', '爱'];
private arrayB: string[] = ['爸', '宝', '白'];
private arrayC: string[] = ['曹', '崔'];
private arrayD: string[] = ['邓'];
private arrayF: string[] = ['冯', '方'];
private arrayZ: string[] = ['赵', '张'];
private value: string[] = ['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','#'];
contactGroup: ContactGroup[] = [
{
letter: 'A',
name: ['艾先生', '艾女士', '安迪', '爱迪生']
},
{
letter: 'B',
name: ['爸爸', '宝宝', '白色', '白鹿']
},
{
letter: 'C',
name: ['曹操', '曹丕', '曹植', '崔崔']
},
{
letter: 'D',
name: ['邓小', '邓超']
},
{
letter: 'F',
name: ['冯小刚', '方形']
},
{
letter: 'Z',
name: ['赵丽颖', '张雨绮','张韶涵']
},
{
letter: '#',
name: ['12345', '110', '120', '119', '114']
}
];
//使用groupheader时,如果吸顶模式关闭,定位到groupitem时,默认显示的是group的第一个元素,不显示头
@Local stick:StickyStyle =StickyStyle.None
@Local groupIndex:number = -1;
@Local popupData:string[] =[]
@Local popupModel:boolean =true
@Local usingPopup:boolean =true
@Local selected:number =0
@Builder
itemHead(text: string) {
Text(text)
.fontSize(20)
.width('100%')
.height(40)
.padding(10)
.backgroundColor(Color.White)
}
build() {
Column(){
Row(){
Button('通讯录模式索引定位').onClick(()=>{
this.popupModel =true
this.usingPopup =true
})
Button('联系人无二级索引吸顶').onClick(()=>{
this.popupModel =false
this.usingPopup =false
this.stick=StickyStyle.Header
})
}
RelativeContainer() {
List({ space: 10, initialIndex: 0 , scroller: this.scroller }) {
ForEach(this.contactGroup, (item: ContactGroup) => {
ListItemGroup({ header: this.itemHead(item.letter) }) {
ForEach(item.name, (name: string) => {
ListItem() {
Row({space:20}) {
Text(name.slice(-1))
.fontColor(Color.White)
.fontSize(20)
.textAlign(TextAlign.Center)
.backgroundColor('#CCCCCC')
.borderRadius(25)
.height(50)
.width(50)
Text(name)
.width('100%')
.height(80)
.fontSize(20)
.textAlign(TextAlign.Start)
}
}
}, (item: string) => item)
}
.divider({ startMargin: 60, strokeWidth: 0.5, color: '#CCCCCC' }) // 每行之间的分界线
})
}
.sticky(this.stick)
.padding({left:20,right:30})
.scrollBar(BarState.Off)
.width('100%')
.height('100%')
.onScrollStart(()=>{
this.usingPopup?this.stick =StickyStyle.None:this.stick=StickyStyle.Header
})
.onDidScroll(()=>{
// console.info('onDidScroll');
})
.onScrollIndex((start: number, end: number, center: number) => {
console.debug('start:'+start+'end:'+end+'center:'+center);
let arrayValue = this.contactGroup[start].letter
this.selected= this.value.findIndex((value:string)=>value==arrayValue)
this.usingPopup =false
})
AlphabetIndexer({ arrayValue: this.value, selected: this.selected })
.color(0x99182431)//未选中项文本颜色
.selectedColor('#FFFFFF')//选中项文本颜色 ABCD...
.selectedBackgroundColor(0xCCCCCC)// 选中项背景颜色
.autoCollapse(false)// 关闭自适应折叠模式
.enableHapticFeedback(false)// 关闭触控反馈
.popupColor('#3F56EA')// 提示弹窗一级索引文本颜色
.popupBackground('#FFFFFF')// 提示弹窗背景颜色
.usingPopup(this.usingPopup)// 索引项被选中时显示提示弹窗
.selectedFont({ size: 16, weight: FontWeight.Normal })// 选中项文本样式
.popupFont({ size: 30, weight: FontWeight.Bolder })// 提示弹窗一级索引的文本样式
.itemSize(24)// 索引项的尺寸大小
.alignStyle(IndexerAlign.Right)// 提示弹窗在索引条右侧弹出
.popupItemBorderRadius(24)// 设置提示弹窗索引项背板圆角半径
.itemBorderRadius(14)// 设置索引项背板圆角半径
.popupBackgroundBlurStyle(BlurStyle.NONE)// 设置提示弹窗的背景模糊材质
.popupTitleBackground('#CCCCCC')// 设置提示弹窗一级索引项背景颜色 ABC背景
.popupSelectedColor(Color.Black)// 弹窗二级索引 选中项 文本颜色
.popupUnselectedColor(Color.Black)// 提示弹窗二级索引 未选中项 文本颜色
.popupItemFont({ size: 30, style: FontStyle.Normal })// 提示弹窗二级索引项文本样式
.popupItemBackgroundColor(Color.Transparent)// 提示弹窗二级索引项背景颜色
.height('60%')
.onSelect((index: number) => {
this.stick =StickyStyle.Header
this.groupIndex = this.contactGroup.findIndex((item:ContactGroup)=>item.letter==this.value[index])
console.info(this.value[index] + ' Selected!');
console.info('GroupIndex:'+this.groupIndex);
this.scroller.scrollToItemInGroup(this.groupIndex,0)
})
.onRequestPopupData((index: number) => {
//一级索引选中时,返回二级索引的数据
if (this.value[index] == 'A') {
this.popupData=[]
this.popupData=[...this.arrayA]
return this.arrayA;
} else if (this.value[index] == 'B') {
this.popupData=[]
this.popupData=[...this.arrayB]
return this.arrayB;
} else if (this.value[index] == 'C') {
this.popupData=[]
this.popupData=[...this.arrayC]
return this.arrayC;
} else if (this.value[index] == 'D') {
this.popupData=[]
this.popupData=[...this.arrayD]
return this.arrayD;
} else if (this.value[index] == 'F') {
this.popupData=[]
this.popupData=[...this.arrayF]
return this.arrayF;
} else if (this.value[index] == 'Z') {
this.popupData=[]
this.popupData=[...this.arrayZ]
return this.arrayZ;
} else {
this.popupData=[]
return [];
}
})
.onPopupSelect((index: number) => {
if (this.popupData.length==0)return
this.stick =StickyStyle.None
let indexInGroup = this.contactGroup[this.groupIndex].name.findIndex((item:string)=>item[0]==this.popupData[index])
this.scroller.scrollToItemInGroup(this.groupIndex,indexInGroup)
console.info('onPopupSelect:' + indexInGroup);
})
.alignRules(AlignRules.alignParentRightCenter)
}
}
}
}
如果有用,请点点关注,有问题,请留言或私信。
- 0回答
- 0粉丝
- 0关注