1# 如何实现列表项的新增和删除
2
3## 场景介绍
4列表的编辑模式用途十分广泛,常见于待办事项管理、文件管理、备忘录的记录管理等应用场景。在列表的编辑模式下,新增和删除列表项是最基础的功能,其核心是对列表项对应的数据集合进行数据添加和删除。
5
6下面以待办事项管理为例,介绍如何快速实现新增和删除列表项功能。
7
8## 环境要求
9- IDE:DevEco Studio 3.1 Beta1
10- SDK:Ohos_sdk_public 3.2.11.9 (API Version 9 Release)
11
12## 新增列表项
13
14如下图所示,当用户点击添加按钮时,将弹出列表项选择界面,用户点击确定后,列表中新增对应项目。
15
16  **图17** 新增待办  
17
18![新增列表](figures/add-item.gif)
19
20### 开发步骤
21
221. 定义列表项数据结构和初始化列表数据,构建列表整体布局和列表项。
23   以待办事项管理为例,首先定义待办事项的数据结构:
24
25   ```ts
26   import util from '@ohos.util';
27
28   export class ToDo {
29     key: string = util.generateRandomUUID(true);
30     name: string;
31
32     constructor(name: string) {
33       this.name = name;
34     }
35   }
36   ```
37
382. 然后,初始化待办事项列表和可选事项列表:
39
40   ```ts
41   @State toDoData: ToDo[] = [];
42   private availableThings: string[] = ['读书', '运动', '旅游', '听音乐', '看电影', '唱歌'];
43   ```
44
453. 构建UI界面。
46   初始界面包含“待办”和新增按钮“+”:
47   ```ts
48   Text('待办')
49     .fontSize(36)
50     .margin({ left: 40})
51   Blank()
52   Text('+')
53     .fontWeight(FontWeight.Lighter)
54     .fontSize(40)
55     .margin({ right: 30 })
56   ```
57   构建列表布局并通过ForEach循环渲染列表项:
58
59   ```ts
60   List({ space: 10 }) {
61     ForEach(this.toDoData, (toDoItem) => {
62       ListItem() {
63         ...
64       }
65     }, toDoItem => toDoItem.key)
66   }
67   ```
68
694. 为新增按钮绑定点击事件,并在事件中通过TextPickerDialog.show添加新增列表项的逻辑:
70
71   ```ts
72   Text('+')
73     .onClick(() => {
74       TextPickerDialog.show({
75         range: this.availableThings, // 将可选事项列表配置到选择对话框中
76         onAccept: (value: TextPickerResult) => {
77            this.toDoData.push(new ToDo(this.availableThings[value.index])); // 用户点击确认,将选择的数据添加到待办列表toDoData中
78         },
79       })
80     })
81   ```
82
83
84## 删除列表项
85
86如下图所示,当用户长按列表项进入删除模式时,提供用户删除列表项选择的交互界面,用户勾选完成后点击删除按钮,列表中删除对应的项目。
87
88  **图18** 长按删除待办事项
89
90![删除列表](figures/delete-item.gif)
91
92### 开发步骤
93
941. 列表的删除功能一般进入编辑模式后才可使用,所以需要提供编辑模式的入口。
95   以待办列表为例,通过LongPressGesture()监听列表项的长按事件,当用户长按列表项时,进入编辑模式。
96
97
98   ```ts
99   // ToDoListItem.ets
100
101   Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) {
102     ...
103   }
104   .gesture(
105   GestureGroup(GestureMode.Exclusive,
106     LongPressGesture() // 监听长按事件
107       .onAction(() => {
108         if (!this.isEditMode) {
109           this.isEditMode = true; //进入编辑模式
110           this.selectedItems.push(this.toDoItem); // 记录长按时选中的列表项
111         }
112       })
113     )
114   )
115   ```
116
1172. 需要响应用户的选择交互,记录要删除的列表项数据。
118   在待办列表中,通过勾选框的勾选或取消勾选,响应用户勾选列表项变化,记录所有选择的列表项。
119
120   ```ts
121   // ToDoListItem.ets
122
123   if (this.isEditMode) {
124     Checkbox()
125       .onChange((isSelected) => {
126         if (isSelected) {
127           this.selectedItems.push(this.toDoItem) // 勾选时,记录选中的列表项
128         } else {
129           let index = this.selectedItems.indexOf(this.toDoItem)
130           if (index !== -1) {
131             this.selectedItems.splice(index, 1) // 取消勾选时,则将此项从selectedItems中删除
132           }
133         }
134       })
135       ...
136   }
137   ```
138
1393. 需要响应用户点击删除按钮事件,删除列表中对应的选项。
140
141   ```ts
142   // ToDoList.ets
143
144   Button('删除')
145     .onClick(() => {
146       // 删除选中的列表项对应的toDoData数据
147       let leftData = this.toDoData.filter((item) => {
148         return this.selectedItems.find((selectedItem) => selectedItem !== item);
149       })
150
151       this.toDoData = leftData;
152       this.isEditMode = false;
153     })
154     ...
155   ```
156## 完整示例代码
157新增和删除列表项的实现共涉及三个文件,各文件完整代码如下:
1581. 待办事项数据结构代码(ToDo.ets):
159    ```ts
160    // ToDo.ets
161    import util from '@ohos.util';
162
163    export class ToDo {
164      key: string = util.generateRandomUUID(true)
165      name: string;
166
167      constructor(name: string) {
168        this.name = name;
169      }
170    }
171    ```
1722. 待办事项列表代码(ToDoList.ets):
173    ```ts
174    // ToDoList.ets
175    import { ToDo } from '../model/ToDo';
176    import { ToDoListItem } from './ToDoListItem';
177
178    @Entry
179    @Component
180    struct ToDoList {
181      @State toDoData: ToDo[] = []
182      @Watch('onEditModeChange') @State isEditMode: boolean = false
183      @State selectedItems: ToDo[] = []
184
185      private availableThings: string[] = ["读书", "运动", "旅游", '听音乐', '看电影', '唱歌']
186
187      saveData(value: string) {
188        this.toDoData.push(new ToDo(value))
189      }
190
191      onEditModeChange() {
192        if (!this.isEditMode) {
193          this.selectedItems = []
194        }
195      }
196
197      build() {
198        Column() {
199            Row() {
200              if (this.isEditMode) {
201                Text('X')
202                  .fontSize(20)
203                  .onClick(() => {
204                    this.isEditMode = false;
205                  })
206                  .margin({ left: 20, right: 20 })
207
208                Text('已选择' + this.selectedItems.length + '项')
209                  .fontSize(24)
210              } else {
211                Text('待办')
212                  .fontSize(36)
213                  .margin({ left: 40})
214                Blank()
215                Text('+')
216                  .fontWeight(FontWeight.Lighter)
217                  .fontSize(40)
218                  .margin({ right: 30 })
219                  .onClick(() => {
220                    TextPickerDialog.show({
221                      range: this.availableThings,
222                      onAccept: (value: TextPickerResult) => {
223                        this.toDoData.push(new ToDo(this.availableThings[value.index]))
224                        console.info('to do data: ' + JSON.stringify(this.toDoData))
225                      },
226                    })
227                  })
228              }
229            }
230            .height('12%')
231            .width('100%')
232
233            List({ initialIndex: 0, space: 10 }) {
234              ForEach(this.toDoData, toDoItem => {
235                ListItem() {
236                    ToDoListItem({
237                      isEditMode: $isEditMode,
238                      toDoItem: toDoItem,
239                      selectedItems: $selectedItems
240                    })
241                }.padding({ left: 24, right: 24, bottom: 12 })
242              }, toDoItem => toDoItem.key)
243            }
244            .height('73%')
245            .listDirection(Axis.Vertical)
246            .edgeEffect(EdgeEffect.Spring)
247
248          if (this.isEditMode) {
249            Row() {
250              Button('删除')
251                .width('80%')
252                .onClick(() => {
253                  let leftData = this.toDoData.filter((item) => {
254                    return this.selectedItems.find((selectedItem) => selectedItem != item)
255                  })
256                  console.log('leftData: ' + leftData);
257                  this.isEditMode = false;
258                  this.toDoData = leftData;
259                })
260                .backgroundColor('#ffd75d5d')
261            }
262            .height('15%')
263          }
264        }
265        .backgroundColor('#fff1f3f5')
266        .width('100%')
267        .height('100%')
268      }
269    }
270    ```
2713. 待办事项代码(ToDoListItem.ets):
272    ```ts
273    // ToDoListItem.ets
274    import { ToDo } from '../model/ToDo';
275
276    @Component
277    export struct ToDoListItem {
278      @Link isEditMode: boolean
279      @Link selectedItems: ToDo[]
280      private toDoItem: ToDo;
281
282      hasBeenSelected(): boolean {
283        return this.selectedItems.indexOf(this.toDoItem) != -1
284      }
285
286      build() {
287        Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) {
288          Row({ space: 4 }) {
289            Circle()
290              .width(24)
291              .height(24)
292              .fill(Color.White)
293              .borderWidth(3)
294              .borderRadius(30)
295              .borderColor('#ffdcdfdf')
296              .margin({ right: 10 })
297
298            Text(`${this.toDoItem.name}`)
299              .maxLines(1)
300              .fontSize(24)
301              .textOverflow({ overflow: TextOverflow.Ellipsis })
302          }
303          .padding({ left: 12 })
304
305          if (this.isEditMode) {
306            Checkbox()
307              .select(this.hasBeenSelected() ? true : false)
308              .onChange((isSelected) => {
309                if (isSelected) {
310                  this.selectedItems.push(this.toDoItem)
311                } else {
312                  let index = this.selectedItems.indexOf(this.toDoItem)
313                  if (index != -1) {
314                    this.selectedItems.splice(index, 1)
315                  }
316                }
317              })
318              .width(24)
319              .height(24)
320          }
321        }
322        .width('100%')
323        .height(80)
324        .padding({
325          left: 16,
326          right: 12,
327          top: 4,
328          bottom: 4
329        })
330        .borderRadius(24)
331        .linearGradient({
332          direction: GradientDirection.Right,
333          colors: this.hasBeenSelected() ? [[0xffcdae, 0.0], [0xFfece2, 1.0]] : [[0xffffff, 0.0], [0xffffff, 1.0]]
334        })
335        .gesture(
336        GestureGroup(GestureMode.Exclusive,
337        LongPressGesture()
338          .onAction(() => {
339            if (!this.isEditMode) {
340              this.isEditMode = true
341              this.selectedItems.push(this.toDoItem)
342            }
343          })
344        )
345        )
346      }
347    }
348    ```