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}