• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2025 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16import { ListItemModifier } from '../model/AttributeModifier';
17import curves from '@ohos.curves';
18import { logger } from '../utils/Logger';
19import { CommonConstants } from '../common/commonConstants';
20
21const ITEM_HEIGHT: number = 50; // 行高
22
23// 操作状态枚举
24enum OperationStatus {
25  IDLE,
26  PRESSING,
27  MOVING,
28  DROPPING,
29  DELETE
30}
31
32/**
33 * 列表项切换控制
34 */
35@Observed
36export class ListExchangeCtrl<T> {
37  private deductionData: Array<T> = []; // 列表数据
38  private modifier: Array<ListItemModifier> = []; // 属性数据
39  private dragRefOffset: number = 0;
40  private offsetY: number = 0;
41  private state: OperationStatus = OperationStatus.IDLE;
42
43  initData(deductionData: Array<T>) {
44    this.deductionData = deductionData;
45    deductionData.forEach(() => {
46      this.modifier.push(new ListItemModifier());
47    })
48  }
49
50  /**
51   * 获取ListItem的属性
52   * @param item
53   * @returns 返回自定义属性对象
54   */
55  getModifier(item: T): ListItemModifier {
56    const index: number = this.deductionData.indexOf(item);
57    return this.modifier[index];
58  }
59
60  /**
61   * ListItem长按函数
62   * @param item
63   */
64  onLongPress(item: T): void {
65    const index: number = this.deductionData.indexOf(item);
66    this.dragRefOffset = 0;
67    // TODO:知识点:长按当前列表项透明度和放大动画
68    animateTo({ curve: Curve.Friction, duration: CommonConstants.ANIMATE_DURATION }, () => {
69      this.state = OperationStatus.PRESSING;
70      this.modifier[index].hasShadow = true;
71      this.modifier[index].scale = 1.04; // 放大比例为1.04
72    })
73  }
74
75  /**
76   * ListItem移动函数
77   * @param item
78   * @param offsetY
79   */
80  onMove(item: T, offsetY: number): void {
81    try {
82      const index: number = this.deductionData.indexOf(item);
83      this.offsetY = offsetY - this.dragRefOffset;
84      this.modifier[index].offsetY = this.offsetY;
85      const direction: number = this.offsetY > 0 ? 1 : -1;
86      // 触发拖动时,被覆盖子组件缩小与恢复的动画
87      const curveValue: ICurve = curves.initCurve(Curve.Sharp);
88      const value: number = curveValue.interpolate(Math.abs(this.offsetY) / ITEM_HEIGHT);
89      const shrinkScale: number = 1 - value / 10; // 计算缩放比例,value值缩小10倍
90      if (index < this.modifier.length - 1) { // 当拖拽的时候,被交换的对象会缩放
91        this.modifier[index + 1].scale = direction > 0 ? shrinkScale : 1;
92      }
93      if (index > 0) {
94        this.modifier[index - 1].scale = direction > 0 ? 1 : shrinkScale;
95      }
96      // TODO:知识点:处理列表项的切换操作
97      if (Math.abs(this.offsetY) > ITEM_HEIGHT / 2) {
98        if (index === 0 && direction === -1) {
99          return;
100        }
101        if (index === this.deductionData.length - 1 && direction === 1) {
102          return;
103        }
104        animateTo({ curve: Curve.Friction, duration: CommonConstants.ANIMATE_DURATION }, () => {
105          this.offsetY -= direction * ITEM_HEIGHT;
106          this.dragRefOffset += direction * ITEM_HEIGHT;
107          this.modifier[index].offsetY = this.offsetY;
108          const target = index + direction // 目标位置索引
109          if (target !== -1 && target <= this.modifier.length) {
110            this.changeItem(index, target);
111          }
112        })
113      }
114    } catch (err) {
115      logger.error(`onMove err:${JSON.stringify(err)}`);
116    }
117  }
118
119  /**
120   * ListItem放置函数
121   * @param item
122   */
123  onDrop(item: T): void {
124    logger.info(`onDrop start`);
125    try {
126      const index: number = this.deductionData.indexOf(item);
127      this.dragRefOffset = 0;
128      this.offsetY = 0;
129      /**
130       * 恢复拖动中,被缩小的子组件,并提供动画。
131       * 通过interpolatingSpring(0, 1, 400, 38)构造插值器弹簧曲线对象初始速度为0,质量为1,刚度为400,阻尼为38
132       */
133      animateTo({ curve: curves.interpolatingSpring(0, 1, 400, 38) }, () => {
134        this.state = OperationStatus.DROPPING;
135        if (index < this.modifier.length - 1) {
136          this.modifier[index + 1].scale = 1;
137        }
138        if (index > 0) {
139          this.modifier[index - 1].scale = 1;
140        }
141      })
142      /**
143       * 恢复被拖拽子组件的放大与阴影效果,并提供动画。
144       * 通过interpolatingSpring(0, 1, 400, 38)构造插值器弹簧曲线对象初始速度为14,质量为1,刚度为170,阻尼为17
145       */
146      animateTo({ curve: curves.interpolatingSpring(14, 1, 170, 17) }, () => {
147        this.state = OperationStatus.IDLE;
148        this.modifier[index].hasShadow = false;
149        this.modifier[index].scale = 1; // 初始化缩放比例
150        this.modifier[index].offsetY = 0; // 初始化偏移量
151      })
152      logger.info(`onDrop end`);
153    } catch (err) {
154      console.error(`onDrop err:${JSON.stringify(err)}`);
155    }
156  }
157
158  /**
159   * Item交换位置
160   * @param index
161   * @param newIndex
162   */
163  changeItem(index: number, newIndex: number): void {
164    const tmp: Array<T> = this.deductionData.splice(index, 1);
165    this.deductionData.splice(newIndex, 0, tmp[0]);
166    const tmp2: Array<ListItemModifier> = this.modifier.splice(index, 1);
167    this.modifier.splice(newIndex, 0, tmp2[0]);
168  }
169
170  /**
171   * 删除列表项
172   * @param item: 列表项
173   */
174  deleteItem(item: T): void {
175    try {
176      const index: number = this.deductionData.indexOf(item);
177      this.dragRefOffset = 0;
178      // TODO:知识点:左偏移以及透明度动画
179      animateTo({
180        // 总时间300ms
181        curve: Curve.Friction, duration: 300, onFinish: () => {
182          // TODO:知识点:列表项删除动画
183          animateTo({
184            // 总时间500ms
185            curve: Curve.Friction, duration: 500, onFinish: () => {
186              this.state = OperationStatus.IDLE;
187            }
188          }, () => {
189            this.modifier.splice(index, 1);
190            this.deductionData.splice(index, 1);
191          })
192        }
193      }, () => {
194        this.state = OperationStatus.DELETE;
195        this.modifier[index].offsetX = 150; // 列表项左偏移150
196        this.modifier[index].opacity = 0; // 列表项透明度为0
197      })
198    } catch (err) {
199      console.error(`delte err:${JSON.stringify(err)}`);
200    }
201  }
202
203  /**
204   * 添加列表项
205   * @param item: 列表项
206   */
207  addItem(item: T): void {
208    this.deductionData.push(item);
209    this.modifier.push(new ListItemModifier());
210  }
211}