• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# 列表项交换案例
2
3### 介绍
4
5本案例通过List组件、组合手势GestureGroup、swipeAction属性以及attributeModifier属性等实现了列表项的交换和删除。
6
7### 效果图预览
8
9![](ListExchange/src/main/resources/base/media/list_exchange.gif)
10
11**使用说明**:
12
131. 进入页面,长按列表项,执行拖拽操作,当拖拽长度大于列表项所占高度一半的时候,列表项进行交换。
142. 列表项左滑,显示删除按钮,点击删除按钮,此列表项被删除。
15
16### 下载安装
17
181.模块oh-package.json5文件中引入依赖。
19```typescript
20"dependencies": {
21  "listexchange": "har包地址"
22}
23```
24
252.ets文件import自定义视图实现列表视图。
26
27```typescript
28import { ListExchange } from 'listexchange';
29```
30### 快速使用
31
32本章节主要介绍了如何快速上手自定义视图实现列表切换效果组件。
33
341. 设置列表项元素的类。开发者可以根据自身业务列表项的需求场景自行变更或者拓展属性,但是需要自定义列表项元素视图。
35
36```typescript
37class ListInfo {
38  icon: ResourceStr = '';
39  name: ResourceStr = '';
40
41  constructor(icon: ResourceStr = '', name: ResourceStr = '') {
42    this.icon = icon;
43    this.name = name;
44   }
45 }
46```
47
482. 数据准备。首先构建一个ListInfo类型的数组,然后向其中传入对应的内容数据。
49
50```typescript
51const MEMO_DATA: ListInfo[] = [
52      new ListInfo($r("app.media.list_exchange_ic_public_cards_filled"), '账户余额'),
53      new ListInfo($r("app.media.list_exchange_ic_public_cards_filled2"), 'xx银行储蓄卡(1234)'),
54      new ListInfo($r("app.media.list_exchange_ic_public_cards_filled3"), 'xx银行储蓄卡(1238)'),
55      new ListInfo($r("app.media.list_exchange_ic_public_cards_filled4"), 'xx银行储蓄卡(1236)')];
56
57@State appInfoList: ListInfo[] = MEMO_DATA;
58```
593. 声明管理列表项交换的类ListExchangeCtrl。通过ListExchangeCtrl来实现列表项与列表项之间的位置交换。具体实现请看后续的实现步骤章节。
60
61```typescript
62// 列表项交换类
63@State listExchangeCtrl: ListExchangeCtrl<ListInfo> = new ListExchangeCtrl();
64```
654. 自定义列表项组件。开发者可以自定义列表项的UI。
66
67```typescript
68
69 // 列表项数据信息
70 @Builder deductionView(listItemInfo: ListInfo) {...}
71
72```
735. 构建自定义列表视图。在代码合适的位置使用ListExchange组件并传入对应的参数。
74
75```typescript
76/**
77 * 列表交换视图
78 * appInfoList: 数据源
79 * listExchangeCtrl: 列表项交换类
80 * deductionView: 自定义列表项元素视图
81 */
82ListExchange({
83  appInfoList: this.appInfoList,
84  listExchangeCtrl: this.listExchangeCtrl,
85  deductionView: (listItemInfo: Object) => {
86    this.deductionView(listItemInfo as Listinfo)
87  }
88})
89
90```
91
92### 属性(接口)说明
93
94ListInfo类属性(开发者可以自行拓展或者更改列表的属性元素)
95
96|      属性       |     类型      |  释义  | 默认值 |
97|:-------------:|:-----------:|:----:|:---:|
98|     icon      | ResourceStr | 列表图片 |  -  |
99|     name      | ResourceStr | 列表名称 |  -  |
100
101
102ListExchange组件属性
103
104|        属性        |        类型        |     释义     | 默认值 |
105|:----------------:|:----------------:|:----------:|:---:|
106|   appInfoList    |    ListInfo[]    |   列表数据源    |  -  |
107| listExchangeCtrl | ListExchangeCtrl |  列表项元素交换类  |  -  |
108|  deductionView   |       void       | 自定义列表项元素视图 |  -  |
109
110### 实现思路
111
112首先创建一个数组modifier来添加自定义属性对象,根据组合手势GestureGroup来控制自定义属性的值并通过attributeModifier绑定自定义属性对象来动态加载属性。
113然后通过swipeAction属性绑定删除组件,左滑显示此删除组件,点击实现列表项的删除。
1141. 声明一个数组,添加自定义属性对象,每个自定义属性对象对应一个列表项,源码参考[AttributeModifier.ets](ListExchange/src/main/ets/model/AttributeModifier.ets)和[ListExchangeCtrl.ets](ListExchange/src/main/ets/model/ListExchangeCtrl.ets)。
115```typescript
116 initData(deductionData: Array<T>) {
117  this.deductionData = deductionData;
118  deductionData.forEach(() => {
119    this.modifier.push(new ListItemModifier());
120  })
121}
122 /**
123  * 通过实现AttributeModifier接口,自定义属性修改器
124  * 将拖拽排序相关样式封装成属性修改器,可以方便移植
125  */
126 export class ListItemModifier implements AttributeModifier<ListItemAttribute> {
127  // 阴影
128  public hasShadow: boolean = false;
129  // 缩放
130  public scale: number = 1;
131  // 纵轴偏移量
132  public offsetY: number = 0;
133  // 横轴偏移量
134  public offsetX: number = 0;
135  // 透明度
136  public opacity: number = 1;
137  // 是否被删除
138
139  public static getInstance(): ListItemModifier {
140    if (!ListItemModifier.instance) {
141      ListItemModifier.instance = new ListItemModifier();
142    }
143    return ListItemModifier.instance;
144  }
145
146  /**
147   * 定义组件普通状态时的样式
148   * @param instance: ListItem属性
149   */
150  applyNormalAttribute(instance: ListItemAttribute): void {
151    if (this.hasShadow) {
152      instance.shadow({ radius: $r('app.integer.list_exchange_shadow_radius'), color: $r('app.color.box_shadow') });
153      instance.zIndex(1);
154      instance.opacity(0.5);
155    } else {
156      instance.opacity(this.opacity);
157    }
158    instance.translate({ x: this.offsetX, y: this.offsetY });
159    instance.scale({ x: this.scale, y: this.scale });
160  }
161}
162```
1632. 绑定attributeModifier属性以及组合手势GestureGroup,attributeModifier属性的值为对应的自定义属性对象。源码参考[ListExchangeView.ets](ListExchange/src/main/ets/view/ListExchangeView.ets)。
164```typescript
165// 列表区域
166List() {
167  ForEach(this.appInfoList, (item: Object) => {
168    ListItem() {
169      this.deductionView(item)
170    }
171    .zIndex(this.currentListItem === item ? 2 : 1) // 层级属性
172    .swipeAction({ end: this.defaultDeleteBuilder(item) }) // 用于设置ListItem的划出组件
173    .transition(TransitionEffect.OPACITY)
174    .attributeModifier(this.listExchangeCtrl.getModifier(item)) //动态设置组件的属性方法, 参数为属性修改器
175    .gesture(
176      // 以下组合手势为顺序识别,当长按手势事件未正常触发时,则不会出发拖动手势事件
177      GestureGroup(GestureMode.Sequence,
178        // 长按
179        LongPressGesture()
180          .onAction((event: GestureEvent) => {
181            this.currentListItem = item;
182            this.isLongPress = true;
183            this.listExchangeCtrl.onLongPress(item);
184          }),
185        // 拖动
186        PanGesture()
187          .onActionUpdate((event: GestureEvent) => {
188            this.listExchangeCtrl.onMove(item, event.offsetY);
189          })
190          .onActionEnd((event: GestureEvent) => {
191            this.listExchangeCtrl.onDrop(item);
192            this.isLongPress = false;
193          })
194      ).onCancel(() => {
195        if (!this.isLongPress) {
196          return;
197        }
198        this.listExchangeCtrl.onDrop(item);
199      }))
200  }, (item: Object) => JSON.stringify(item))
201}
202.divider({ strokeWidth: '1px', color: 0xeaf0ef })
203.scrollBar(BarState.Off)
204.border({
205  radius: {
206    bottomLeft: $r('app.string.ohos_id_corner_radius_default_l'),
207    bottomRight: $r('app.string.ohos_id_corner_radius_default_l')
208  }
209})
210.backgroundColor(Color.White)
211.width('100%')
212
213```
214
2153. 长按列表项,通过LongPressGesture识别长按手势,执行onLongPress函数方法更改此列表项的scale、shadow、zIndex和opacity等属性,并通过animateTo来实现动画效果,源码参考[ListExchangeCtrl.ets](ListExchange/src/main/ets/model/ListExchangeCtrl.ets)。
216
217```typescript
218 onLongPress(item: T) {
219   const index: number = this.deductionData.indexOf(item);
220   this.dragRefOffset = 0;
221   // TODO:知识点:长按当前列表项透明度和放大动画
222   animateTo({ curve: Curve.Friction, duration: ANIMATE_DURATION }, () => {
223     this.state = OperationStatus.PRESSING;
224     this.modifier[index].hasShadow = true;
225     this.modifier[index].scale = 1.04; // 放大比例为1.04
226   })
227 }
228```
2294. 交换列表项,通过PanGesture手势的onActionUpdate方法监听拖动的纵轴移动长度,然后执行onMove方法,根据移动长度的大小来判断是否执行列表项交换方法changeItem,源码参考[ListExchangeCtrl.ets](ListExchange/src/main/ets/model/ListExchangeCtrl.ets)。
230```typescript
231 onMove(item: T, offsetY: number) {
232   const index: number = this.deductionData.indexOf(item);
233   this.offsetY = offsetY - this.dragRefOffset;
234   this.modifier[index].offsetY = this.offsetY;
235   const direction: number = this.offsetY > 0 ? 1 : -1;
236   // 触发拖动时,被覆盖子组件缩小与恢复的动画
237   const curveValue: ICurve = curves.initCurve(Curve.Sharp);
238   const value: number = curveValue.interpolate(Math.abs(this.offsetY) / ITEM_HEIGHT);
239   const shrinkScale: number = 1 - value / 10; // 计算缩放比例,value值缩小10倍
240   if (index < this.modifier.length - 1) { // 当拖拽的时候,被交换的对象会缩放
241     this.modifier[index + 1].scale = direction > 0 ? shrinkScale : 1;
242   }
243   if (index > 0) {
244     this.modifier[index - 1].scale = direction > 0 ? 1 : shrinkScale;
245   }
246   // TODO:知识点:处理列表项的切换操作
247   if (Math.abs(this.offsetY) > ITEM_HEIGHT / 2) {
248     animateTo({ curve: Curve.Friction, duration: commonConstants.ANIMATE_DURATION }, () => {
249       this.offsetY -= direction * ITEM_HEIGHT;
250       this.dragRefOffset += direction * ITEM_HEIGHT;
251       this.modifier[index].offsetY = this.offsetY;
252       this.changeItem(index, index + direction);
253     })
254   }
255 }
256
257 changeItem(index: number, newIndex: number): void {
258   const tmp: Array<T> = this.deductionData.splice(index, 1);
259   this.deductionData.splice(newIndex, 0, tmp[0]);
260   const tmp2: Array<ListItemModifier> = this.modifier.splice(index, 1);
261   this.modifier.splice(newIndex, 0, tmp2[0]);
262 }
263```
2645. 通过swipeAction属性绑定删除按钮组件,列表项左滑显示删除组件,点击删除按钮,列表项删除。源码参考[ListExchangeCtrl.ets](ListExchange/src/main/ets/model/ListExchangeCtrl.ets)。
265```typescript
266deleteItem(item: T): void {
267  const index = this.deductionData.indexOf(item);
268  this.dragRefOffset = 0;
269  // TODO:知识点:左偏移以及透明度动画
270  animateTo({
271    curve: Curve.Friction, onFinish: () => {
272      // TODO:知识点:列表项删除动画
273      animateTo({
274        curve: Curve.Friction, onFinish: () => {
275          this.state = OperationStatus.IDLE;
276        }
277      }, () => {
278        this.modifier.splice(index, 1);
279        this.deductionData.splice(index, 1);
280      })
281    }
282  }, () => {
283    this.state = OperationStatus.DELETE;
284    this.modifier[index].offsetX = 150; // 列表项左偏移150
285    this.modifier[index].opacity = 0; // 列表项透明度为0
286  })
287}
288```
289
290### 工程结构&模块类型
291
292```
293listexchange                                 // har类型
294|---common
295|   |---commonConstants.ets                  // 常量
296|---model
297|   |---AttributeModifier.ets                // 属性对象
298|   |---ListExchangeCtrl.ets                 // 列表项交换
299|   |---ListInfo.ets                         // 列表项信息
300|   |---MockData.ets                         // 模拟数据
301|---util
302|   |---ListExchange.ets                     // 自定义列表视图
303|   |---Logger.ets                           // 日志
304|---view
305|   |---ListExchangeView.ets                 // 视图层-应用主页面
306```
307
308### 参考资料
309
310[List](https://docs.openharmony.cn/pages/v5.0/zh-cn/application-dev/reference/apis-arkui/arkui-ts/ts-container-list.md)
311
312[GestureGroup](https://docs.openharmony.cn/pages/v5.0/zh-cn/application-dev/reference/apis-arkui/arkui-ts/ts-combined-gestures.md)
313
314### 约束与限制
315
3161.本示例仅支持标准系统上运行。
317
3182.本示例已适配API version 12版本SDK。
319
3203.本示例需要使用DevEco Studio 5.0.0 Release及以上版本才可编译运行。
321
322### 下载
323
324如需单独下载本工程,执行如下命令:
325```javascript
326git init
327git config core.sparsecheckout true
328echo /code/UI/listexchange/ > .git/info/sparse-checkout
329git remote add origin https://gitee.com/openharmony/applications_app_samples.git
330git pull origin master
331```