写点什么

鸿蒙 Next 实现通讯录索引条 AlphabetIndexer

作者:auhgnixgnahz
  • 2025-06-25
    北京
  • 本文字数:5541 字

    阅读完需:约 18 分钟

当我们需要列表展示通讯录、城市名时,通常会使用到右侧的索引条,可以帮助用户快速定位到某一类的头部。本文介绍一下使用 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@ComponentV2struct 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) } } }}
复制代码


如果有用,请点点关注,有问题,请留言或私信。


发布于: 刚刚阅读数: 2
用户头像

auhgnixgnahz

关注

还未添加个人签名 2018-07-10 加入

欢迎关注:HarmonyOS开发笔记

评论

发布
暂无评论
鸿蒙Next实现通讯录索引条AlphabetIndexer_鸿蒙Next_auhgnixgnahz_InfoQ写作社区