1# 如何按字母分组展示联系人
2
3## 场景说明
4在通讯录中,需要将联系人按照姓氏的首字母进行分组排列,从而更方便联系人的查找;联系人列表右侧的字母导航可以随列表的滑动而定位到对应字母处;同时,也可以通过字母导航控制列表跳到指定联系人分组。
5本例即为大家介绍如何实现上述场景。
6
7## 效果呈现
8本示例最终效果如下:
9
10![contactlist](figures/contactlist.gif)
11
12## 环境要求
13- IDE:DevEco Studio 3.1 Beta1
14- SDK:Ohos_sdk_public 3.2.11.9 (API Version 9 Release)
15
16## 实现思路
17本例涉及的四个关键特性及其实现方案如下:
18- 联系人按字母分组展示:通过List组件显示联系人列表,通过ListItemGroup组件实现联系人分组。
19- 联系人右侧呈现字母导航:使用AlphabetIndexer组件实现字母导航,同时通过Stack组件使字母导航浮在联系人列表右侧。
20- 滑动联系人列表,右侧字母导航随之变动:通过List组件的onScrollIndex事件获取到联系人列表的滑动位置,并将该位置索引传递给字母导航的selected属性,作为字母导航的被选中项。
21- 通过右侧字母导航控制联系人列表滑动到指定分组:通过字母导航的onSelected事件获取选中字母的索引,并将该索引传递给联系人列表的控制器,控制列表滑动到指定分组。
22
23## 开发步骤
24针对上述关键特性,具体实现步骤如下:
25
261、通过Stack、List、ListItemGroup、AlphabetIndexer等关键组件将UI框架搭建起来。
27先构建列表数据,其中Contact为联系人数据类。
28```ts
29contactGroups: object[] = [
30   ...
31    {
32      title: 'D',
33      contacts: [
34        new Contact('Donna', $r('app.media.contact6')),
35        new Contact('朵朵', $r('app.media.contact1')),
36      ],
37    },
38    ...
39    {
40      title: 'K',
41      contacts: [
42        new Contact('孔孔', $r('app.media.contact2')),
43        new Contact('康康', $r('app.media.contact3')),
44      ],
45    },
46    {
47      title: 'L',
48      contacts: [
49        new Contact('Lisa', $r('app.media.contact4')),
50        new Contact('玲玲', $r('app.media.contact5')),
51      ],
52    },
53    {
54      title: 'N',
55      contacts: [
56        new Contact('牛牛', $r('app.media.contact6')),
57        new Contact('Natasha', $r('app.media.contact1')),
58      ],
59    },
60    ...
61  ]
62```
63有了列表数据后,我们来构建UI框架,关键代码如下:
64```ts
65@Entry
66@Component
67struct ContactList{
68  ...
69  // 自定义组件groupHeader,作为ListItemGroup的头部组件,即A、B、C等字母列表项
70  @Builder groupHeader(titleLetter:string){
71    Text(titleLetter)
72      .fontSize(20)
73      .backgroundColor('#fff1f3f5')
74      .width('100%')
75      .padding(5)
76  }
77  // 创建字母列表作为字母导航的内容
78  private alphabets:string[] = [ 'A', 'B', 'D', 'G', 'K', 'L', 'N', 'X'];
79
80  build() {
81    Stack({alignContent:Alignment.End}){
82      List(){
83        // 循环渲染列表内容
84        ForEach(this.contactGroups,contactGroup=>{
85          // 采用ListItemGroup对联系人进行分组,将groupHeader作为ListItemGroup的头部组件
86          ListItemGroup({header:this.groupHeader(contactGroup.title)}){
87            ForEach(contactGroup.contacts,contact=>{
88              ListItem(){
89                Column(){
90                  Row(){
91                    Image(contact.icon)
92                      ...
93                    Text(contact.name)
94                  }
95                  ...
96                  Divider().color('#fff1f3f5')
97                }
98                ...
99              }
100            })
101          }
102        })
103      }
104      ...
105      // 使用AlphabetIndexer组件实现右侧字母导航
106      AlphabetIndexer({arrayValue:this.alphabets,selected:0})
107        ...
108    }
109  }
110}
111```
112完成上述代码,我们的框架就搭建起来了,如图:
113
114![contactframe](figures/contactframe.PNG)
115
1162、接下来为UI框架添加逻辑控制。首先,通过List的onScrollIndex事件获取到列表滑动位置的索引,并将索引同步给右侧字母表的selected属性,从而在滑动联系人时,使右侧字母导航随之变动,关键代码如下:
117```ts
118  ...
119  // 创建动态变量,用于指定字母导航的选择项
120  @State selectedIndex:number = 0;
121  ...
122  build() {
123    Stack({alignContent:Alignment.End}){
124      List({scroller:this.listScroller}){
125        ForEach(this.contactGroups,contactGroup=>{
126          ListItemGroup({header:this.groupHeader(contactGroup.title)}){
127            ForEach(contactGroup.contacts,contact=>{
128              ListItem(){
129                ...
130              }
131            })
132          }
133        })
134      }
135      ...
136      // 获取联系人列表滑动位置的索引,并将索引通过selectedIndex同步给右侧字母导航
137      .onScrollIndex((firstIndex:number)=>{
138        this.selectedIndex = firstIndex
139      })
140      AlphabetIndexer({arrayValue:this.alphabets,selected:0})
141        ...
142        // 指定字母导航的选择项为selectedIndex,完成跟联系人列表的同步
143        .selected(this.selectedIndex)
144        ...
145    }
146  }
147```
148至此,当我们滑动联系人列表时,就可以让右侧字母导航随之变动了。效果如下:
149
150![listtonav](figures/listtonav.gif)
151
1523、最后,我们通过AlphabetIndexer组件的onSelect事件获取到字母导航选择项的索引,然后通过List组件的scroller控制器控制联系人列表滑动到相同的索引处,从而实现通过右侧字母导航控制联系人列表滑动到指定分组。关键代码如下:
153```ts
154...
155  @State selectedIndex:number = 0;
156  // 创建List组件的scroller控制器:listScroller,用于控制联系人列表的滑动位置
157  private listScroller:Scroller = new Scroller()
158  ...
159  build() {
160    Stack({alignContent:Alignment.End}){
161      // 将scroller控制器绑定到List组件
162      List({scroller:this.listScroller}){
163        ForEach(this.contactGroups,contactGroup=>{
164          ListItemGroup({header:this.groupHeader(contactGroup.title)}){
165            ForEach(contactGroup.contacts,contact=>{
166              ListItem(){
167                ...
168              }
169            })
170          }
171        })
172      }
173      ...
174      AlphabetIndexer({arrayValue:this.alphabets,selected:0})
175        ...
176        // 获取字母导航中选中字母的索引值,并通过listScroller控制列表滑动到对应索引位置
177        .onSelect((index:number)=>{
178          this.listScroller.scrollToIndex(index)
179        })
180        ...
181    }
182  }
183```
184至此,当我们在右侧字母导航选择某个字母时就可以控制联系人列表跳转到指定分组了,效果如下:
185
186![navtolist](figures/navtolist.gif)
187
188## 完整代码
189通过上述步骤我们已经完成了整个示例的开发,现提供本示例的完整代码供大家参考:
190
191联系人数据类代码:
192```ts
193// ListModel.ets
194export default class Contact{
195  name:string;
196  icon:Resource;
197
198  constructor(name:string,icon:Resource) {
199    this.name = name
200    this.icon = icon
201  }
202}
203```
204案例主代码:
205```ts
206// Contact.ets
207import Contact from '../model/ListModel'
208
209@Entry
210@Component
211struct ContactList{
212  // 联系人列表数据
213  contactGroups: object[] = [
214    {
215      title: 'A',
216      contacts: [
217        new Contact('艾薇而', $r('app.media.contact1')),
218        new Contact('安琪', $r('app.media.contact2')),
219        new Contact('Angela', $r('app.media.contact3')),
220      ],
221    },
222    {
223      title: 'B',
224      contacts: [
225        new Contact('Bobe', $r('app.media.contact4')),
226        new Contact('勃勃', $r('app.media.contact5')),
227      ],
228    },
229    {
230      title: 'D',
231      contacts: [
232        new Contact('Donna', $r('app.media.contact6')),
233        new Contact('朵朵', $r('app.media.contact1')),
234      ],
235    },
236    {
237      title: 'G',
238      contacts: [
239        new Contact('Gavin', $r('app.media.contact4')),
240        new Contact('果味', $r('app.media.contact1')),
241      ],
242    },
243    {
244      title: 'K',
245      contacts: [
246        new Contact('孔孔', $r('app.media.contact2')),
247        new Contact('康康', $r('app.media.contact3')),
248      ],
249    },
250    {
251      title: 'L',
252      contacts: [
253        new Contact('Lisa', $r('app.media.contact4')),
254        new Contact('玲玲', $r('app.media.contact5')),
255      ],
256    },
257    {
258      title: 'N',
259      contacts: [
260        new Contact('牛牛', $r('app.media.contact6')),
261        new Contact('Natasha', $r('app.media.contact1')),
262      ],
263    },
264    {
265      title: 'X',
266      contacts: [
267        new Contact('小可爱', $r('app.media.contact2')),
268        new Contact('徐总是', $r('app.media.contact3')),
269        new Contact('璇璇', $r('app.media.contact3')),
270        new Contact('欣欣', $r('app.media.contact3')),
271      ],
272    },
273  ]
274  // 自定义组件groupHeader,作为ListItemGroup的头部组件,即A、B、C等字母列表项
275  @Builder groupHeader(titleLetter:string){
276    Text(titleLetter)
277      .fontSize(20)
278      .backgroundColor('#fff1f3f5')
279      .width('100%')
280      .padding(5)
281  }
282  // 创建字母列表作为字母导航的内容
283  private alphabets:string[] = [ 'A', 'B', 'D', 'G', 'K', 'L', 'N', 'X'];
284  // 创建动态变量,用于指定字母导航的选择项
285  @State selectedIndex:number = 0;
286  // 创建List组件的scroller控制器:listScroller,用于控制联系人列表的滑动位置
287  private listScroller:Scroller = new Scroller()
288
289  build() {
290    Stack({alignContent:Alignment.End}){
291      // 将scroller控制器绑定到List组件
292      List({scroller:this.listScroller}){
293        // 循环渲染列表内容
294        ForEach(this.contactGroups,contactGroup=>{
295          // 采用ListItemGroup对联系人进行分组,将groupHeader作为ListItemGroup的头部组件
296          ListItemGroup({header:this.groupHeader(contactGroup.title)}){
297            ForEach(contactGroup.contacts,contact=>{
298              ListItem(){
299                Column(){
300                  Row(){
301                    Image(contact.icon)
302                      .width(35)
303                      .height(35)
304                      .margin(10)
305                    Text(contact.name)
306                  }
307                  .width('100%')
308                  Divider().color('#fff1f3f5')
309                }
310                .justifyContent(FlexAlign.Start)
311              }
312            })
313          }
314        })
315      }
316      .width('100%')
317      .height('100%')
318      .scrollBar(BarState.Auto)
319      // 获取联系人列表滑动位置的索引,并将索引通过selectedIndex同步给右侧字母导航
320      .onScrollIndex((firstIndex:number)=>{
321        this.selectedIndex = firstIndex
322      })
323      // 使用AlphabetIndexer组件实现右侧字母导航
324      AlphabetIndexer({arrayValue:this.alphabets,selected:0})
325        .margin({right:10})
326        .itemSize(25)
327        .font({size:15})
328        // 指定字母导航的选择项为selectedIndex,完成跟联系人列表的同步
329        .selected(this.selectedIndex)
330        // 获取选中字母的索引值,通过listScroller控制列表滑动到对应索引位置
331        .onSelect((index:number)=>{
332          this.listScroller.scrollToIndex(index)
333        })
334    }
335  }
336}
337```
338## 参考
339[创建列表](../application-dev/ui/arkts-layout-development-create-list.md)