diff --git a/OAT.xml b/OAT.xml index e36ebac38a93f40671c67ac114bceaf8b3cc7757..f4250d13dd79af0a31a59ceb2c9be33d567109a7 100644 --- a/OAT.xml +++ b/OAT.xml @@ -103,6 +103,11 @@ Note:If the text contains special characters, please escape them according to th + + + + + @@ -2053,6 +2058,13 @@ Note:If the text contains special characters, please escape them according to th + + + + + + + diff --git a/code/UI/ListBeExchange/.gitignore b/code/UI/ListBeExchange/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..d2ff20141ceed86d87c0ea5d99481973005bab2b --- /dev/null +++ b/code/UI/ListBeExchange/.gitignore @@ -0,0 +1,12 @@ +/node_modules +/oh_modules +/local.properties +/.idea +**/build +/.hvigor +.cxx +/.clangd +/.clang-format +/.clang-tidy +**/.test +/.appanalyzer \ No newline at end of file diff --git a/code/UI/ListBeExchange/AppScope/app.json5 b/code/UI/ListBeExchange/AppScope/app.json5 new file mode 100644 index 0000000000000000000000000000000000000000..b360573b97d476682e53305cba89dd49e05b428f --- /dev/null +++ b/code/UI/ListBeExchange/AppScope/app.json5 @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +{ + "app": { + "bundleName": "com.samples.ListExchange", + "vendor": "example", + "versionCode": 1000000, + "versionName": "1.0.0", + "icon": "$media:app_icon", + "label": "$string:app_name" + } +} diff --git a/code/UI/ListBeExchange/AppScope/resources/base/element/string.json b/code/UI/ListBeExchange/AppScope/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..66d68b87864cfece36031613d291e00378a8ec1f --- /dev/null +++ b/code/UI/ListBeExchange/AppScope/resources/base/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "app_name", + "value": "new" + } + ] +} diff --git a/code/UI/ListBeExchange/AppScope/resources/base/media/app_icon.png b/code/UI/ListBeExchange/AppScope/resources/base/media/app_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..a39445dc87828b76fed6d2ec470dd455c45319e3 Binary files /dev/null and b/code/UI/ListBeExchange/AppScope/resources/base/media/app_icon.png differ diff --git a/code/UI/ListBeExchange/ListExchange/.gitignore b/code/UI/ListBeExchange/ListExchange/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..e2713a2779c5a3e0eb879efe6115455592caeea5 --- /dev/null +++ b/code/UI/ListBeExchange/ListExchange/.gitignore @@ -0,0 +1,6 @@ +/node_modules +/oh_modules +/.preview +/build +/.cxx +/.test \ No newline at end of file diff --git a/code/UI/ListBeExchange/ListExchange/Index.ets b/code/UI/ListBeExchange/ListExchange/Index.ets new file mode 100644 index 0000000000000000000000000000000000000000..b3e3dd70f01ee42aadf1b81f6a2aa399b25f0434 --- /dev/null +++ b/code/UI/ListBeExchange/ListExchange/Index.ets @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { ListExchangeView } from './src/main/ets/FeatureComponent' diff --git a/code/UI/ListBeExchange/ListExchange/build-profile.json5 b/code/UI/ListBeExchange/ListExchange/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..881fc3637e5df365b5777ec6341505218ce70905 --- /dev/null +++ b/code/UI/ListBeExchange/ListExchange/build-profile.json5 @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +{ + "apiType": "stageMode", + "buildOption": { + }, + "buildOptionSet": [ + { + "name": "release", + "arkOptions": { + "obfuscation": { + "ruleOptions": { + "enable": false, + "files": [ + "./obfuscation-rules.txt" + ] + }, + "consumerFiles": [ + "./consumer-rules.txt" + ] + } + }, + }, + ], + "targets": [ + { + "name": "default" + }, + { + "name": "ohosTest" + } + ] +} diff --git a/code/UI/ListBeExchange/ListExchange/consumer-rules.txt b/code/UI/ListBeExchange/ListExchange/consumer-rules.txt new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/code/UI/ListBeExchange/ListExchange/hvigorfile.ts b/code/UI/ListBeExchange/ListExchange/hvigorfile.ts new file mode 100644 index 0000000000000000000000000000000000000000..70842da662501ca9b88d435d29a1e58c51e8646d --- /dev/null +++ b/code/UI/ListBeExchange/ListExchange/hvigorfile.ts @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { harTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: harTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ +} diff --git a/code/UI/ListBeExchange/ListExchange/obfuscation-rules.txt b/code/UI/ListBeExchange/ListExchange/obfuscation-rules.txt new file mode 100644 index 0000000000000000000000000000000000000000..272efb6ca3f240859091bbbfc7c5802d52793b0b --- /dev/null +++ b/code/UI/ListBeExchange/ListExchange/obfuscation-rules.txt @@ -0,0 +1,23 @@ +# Define project specific obfuscation rules here. +# You can include the obfuscation configuration files in the current module's build-profile.json5. +# +# For more details, see +# https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/source-obfuscation-V5 + +# Obfuscation options: +# -disable-obfuscation: disable all obfuscations +# -enable-property-obfuscation: obfuscate the property names +# -enable-toplevel-obfuscation: obfuscate the names in the global scope +# -compact: remove unnecessary blank spaces and all line feeds +# -remove-log: remove all console.* statements +# -print-namecache: print the name cache that contains the mapping from the old names to new names +# -apply-namecache: reuse the given cache file + +# Keep options: +# -keep-property-name: specifies property names that you want to keep +# -keep-global-name: specifies names that you want to keep in the global scope + +-enable-property-obfuscation +-enable-toplevel-obfuscation +-enable-filename-obfuscation +-enable-export-obfuscation \ No newline at end of file diff --git a/code/UI/ListBeExchange/ListExchange/oh-package.json5 b/code/UI/ListBeExchange/ListExchange/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..7e2f768204f6c37dd5f428cb707505a8b8d324a0 --- /dev/null +++ b/code/UI/ListBeExchange/ListExchange/oh-package.json5 @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +{ + "name": "listexchange", + "version": "1.0.0", + "description": "Please describe the basic information.", + "main": "Index.ets", + "author": "", + "license": "Apache-2.0", + "dependencies": {} +} diff --git a/code/UI/ListBeExchange/ListExchange/src/main/ets/FeatureComponent.ets b/code/UI/ListBeExchange/ListExchange/src/main/ets/FeatureComponent.ets new file mode 100644 index 0000000000000000000000000000000000000000..8b1a72bb48c0a3bf6579de75c763af64fb365923 --- /dev/null +++ b/code/UI/ListBeExchange/ListExchange/src/main/ets/FeatureComponent.ets @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ListExchangeViewComponent } from './view/ListExchangeView'; + +@Component +export struct ListExchangeView { + build() { + ListExchangeViewComponent() + } +} \ No newline at end of file diff --git a/code/UI/ListBeExchange/ListExchange/src/main/ets/common/commonConstants.ets b/code/UI/ListBeExchange/ListExchange/src/main/ets/common/commonConstants.ets new file mode 100644 index 0000000000000000000000000000000000000000..564db0fe6f9a85f72f6ec0f415b2768fb3bcbb8f --- /dev/null +++ b/code/UI/ListBeExchange/ListExchange/src/main/ets/common/commonConstants.ets @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export class CommonConstants { + // 初始化列表项高度 + public static readonly LIST_ITEM_HEIGHT = 50; + // 初始化动画时间 + public static readonly ANIMATE_DURATION = 300; + // 初始化列表项名称 + public static readonly LIST_NAME = '标题1'; +} \ No newline at end of file diff --git a/code/UI/ListBeExchange/ListExchange/src/main/ets/generated/RouterBuilder.ets b/code/UI/ListBeExchange/ListExchange/src/main/ets/generated/RouterBuilder.ets new file mode 100644 index 0000000000000000000000000000000000000000..67166b18a662da44ae46425323afa39a529fe04f --- /dev/null +++ b/code/UI/ListBeExchange/ListExchange/src/main/ets/generated/RouterBuilder.ets @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// auto-generated +import { DynamicsRouter, AppRouterInfo } from 'routermodule/Index'; +import { ListExchangeView } from '../FeatureComponent' + +@Builder +function listExchangeViewBuilder() { + ListExchangeView(); +} + +export function listExchangeViewRegister(routerInfo: AppRouterInfo) { + DynamicsRouter.registerAppRouterPage(routerInfo, wrapBuilder(listExchangeViewBuilder)); +} + diff --git a/code/UI/ListBeExchange/ListExchange/src/main/ets/model/AttributeModifier.ets b/code/UI/ListBeExchange/ListExchange/src/main/ets/model/AttributeModifier.ets new file mode 100644 index 0000000000000000000000000000000000000000..105c2117333b3307f84763f51983db8c6e8e3247 --- /dev/null +++ b/code/UI/ListBeExchange/ListExchange/src/main/ets/model/AttributeModifier.ets @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * 通过实现AttributeModifier接口,自定义属性修改器 + * 将拖拽排序相关样式封装成属性修改器,可以方便移植 + */ +export class ListItemModifier implements AttributeModifier { + // 阴影 + public hasShadow: boolean = false; + // 缩放 + public scale: number = 1; + // 纵轴偏移量 + public offsetY: number = 0; + // 横轴偏移量 + public offsetX: number = 0; + // 透明度 + public opacity: number = 1; + // 是否被删除 + public isDeleted: boolean = false; + public static instance: ListItemModifier | null = null; + + public static getInstance(): ListItemModifier { + if (!ListItemModifier.instance) { + ListItemModifier.instance = new ListItemModifier(); + } + return ListItemModifier.instance; + } + + /** + * 定义组件普通状态时的样式 + * @param instance: ListItem属性 + */ + applyNormalAttribute(instance: ListItemAttribute): void { + if (this.hasShadow) { + instance.shadow({ radius: $r('app.integer.list_exchange_shadow_radius'), color: $r('app.color.list_exchange_box_shadow') }); + instance.zIndex(1); + instance.opacity(0.5); + } else { + instance.opacity(this.opacity); + } + instance.translate({ x: this.offsetX, y: this.offsetY }); + instance.scale({ x: this.scale, y: this.scale }); + } +} \ No newline at end of file diff --git a/code/UI/ListBeExchange/ListExchange/src/main/ets/model/ListExchangeCtrl.ets b/code/UI/ListBeExchange/ListExchange/src/main/ets/model/ListExchangeCtrl.ets new file mode 100644 index 0000000000000000000000000000000000000000..b9fa9eeb0d81d0bd45908e5b01e662bfa5b53501 --- /dev/null +++ b/code/UI/ListBeExchange/ListExchange/src/main/ets/model/ListExchangeCtrl.ets @@ -0,0 +1,211 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ListItemModifier } from '../model/AttributeModifier'; +import curves from '@ohos.curves'; +import { logger } from '../utils/Logger'; +import { CommonConstants } from '../common/commonConstants'; + +const ITEM_HEIGHT: number = 50; // 行高 + +// 操作状态枚举 +enum OperationStatus { + IDLE, + PRESSING, + MOVING, + DROPPING, + DELETE +} + +/** + * 列表项切换控制 + */ +@Observed +export class ListExchangeCtrl { + private deductionData: Array = []; // 列表数据 + private modifier: Array = []; // 属性数据 + private dragRefOffset: number = 0; + private offsetY: number = 0; + private state: OperationStatus = OperationStatus.IDLE; + + initData(deductionData: Array) { + this.deductionData = deductionData; + deductionData.forEach(() => { + this.modifier.push(new ListItemModifier()); + }) + } + + /** + * 获取ListItem的属性 + * @param item + * @returns 返回自定义属性对象 + */ + getModifier(item: T): ListItemModifier { + const index: number = this.deductionData.indexOf(item); + return this.modifier[index]; + } + + /** + * ListItem长按函数 + * @param item + */ + onLongPress(item: T): void { + const index: number = this.deductionData.indexOf(item); + this.dragRefOffset = 0; + // TODO:知识点:长按当前列表项透明度和放大动画 + animateTo({ curve: Curve.Friction, duration: CommonConstants.ANIMATE_DURATION }, () => { + this.state = OperationStatus.PRESSING; + this.modifier[index].hasShadow = true; + this.modifier[index].scale = 1.04; // 放大比例为1.04 + }) + } + + /** + * ListItem移动函数 + * @param item + * @param offsetY + */ + onMove(item: T, offsetY: number): void { + try { + const index: number = this.deductionData.indexOf(item); + this.offsetY = offsetY - this.dragRefOffset; + this.modifier[index].offsetY = this.offsetY; + const direction: number = this.offsetY > 0 ? 1 : -1; + // 触发拖动时,被覆盖子组件缩小与恢复的动画 + const curveValue: ICurve = curves.initCurve(Curve.Sharp); + const value: number = curveValue.interpolate(Math.abs(this.offsetY) / ITEM_HEIGHT); + const shrinkScale: number = 1 - value / 10; // 计算缩放比例,value值缩小10倍 + if (index < this.modifier.length - 1) { // 当拖拽的时候,被交换的对象会缩放 + this.modifier[index + 1].scale = direction > 0 ? shrinkScale : 1; + } + if (index > 0) { + this.modifier[index - 1].scale = direction > 0 ? 1 : shrinkScale; + } + // TODO:知识点:处理列表项的切换操作 + if (Math.abs(this.offsetY) > ITEM_HEIGHT / 2) { + if (index === 0 && direction === -1) { + return; + } + if (index === this.deductionData.length - 1 && direction === 1) { + return; + } + animateTo({ curve: Curve.Friction, duration: CommonConstants.ANIMATE_DURATION }, () => { + this.offsetY -= direction * ITEM_HEIGHT; + this.dragRefOffset += direction * ITEM_HEIGHT; + this.modifier[index].offsetY = this.offsetY; + const target = index + direction // 目标位置索引 + if (target !== -1 && target <= this.modifier.length) { + this.changeItem(index, target); + } + }) + } + } catch (err) { + logger.error(`onMove err:${JSON.stringify(err)}`); + } + } + + /** + * ListItem放置函数 + * @param item + */ + onDrop(item: T): void { + logger.info(`onDrop start`); + try { + const index: number = this.deductionData.indexOf(item); + this.dragRefOffset = 0; + this.offsetY = 0; + /** + * 恢复拖动中,被缩小的子组件,并提供动画。 + * 通过interpolatingSpring(0, 1, 400, 38)构造插值器弹簧曲线对象初始速度为0,质量为1,刚度为400,阻尼为38 + */ + animateTo({ curve: curves.interpolatingSpring(0, 1, 400, 38) }, () => { + this.state = OperationStatus.DROPPING; + if (index < this.modifier.length - 1) { + this.modifier[index + 1].scale = 1; + } + if (index > 0) { + this.modifier[index - 1].scale = 1; + } + }) + /** + * 恢复被拖拽子组件的放大与阴影效果,并提供动画。 + * 通过interpolatingSpring(0, 1, 400, 38)构造插值器弹簧曲线对象初始速度为14,质量为1,刚度为170,阻尼为17 + */ + animateTo({ curve: curves.interpolatingSpring(14, 1, 170, 17) }, () => { + this.state = OperationStatus.IDLE; + this.modifier[index].hasShadow = false; + this.modifier[index].scale = 1; // 初始化缩放比例 + this.modifier[index].offsetY = 0; // 初始化偏移量 + }) + logger.info(`onDrop end`); + } catch (err) { + console.error(`onDrop err:${JSON.stringify(err)}`); + } + } + + /** + * Item交换位置 + * @param index + * @param newIndex + */ + changeItem(index: number, newIndex: number): void { + const tmp: Array = this.deductionData.splice(index, 1); + this.deductionData.splice(newIndex, 0, tmp[0]); + const tmp2: Array = this.modifier.splice(index, 1); + this.modifier.splice(newIndex, 0, tmp2[0]); + } + + /** + * 删除列表项 + * @param item: 列表项 + */ + deleteItem(item: T): void { + try { + const index: number = this.deductionData.indexOf(item); + this.dragRefOffset = 0; + // TODO:知识点:左偏移以及透明度动画 + animateTo({ + // 总时间300ms + curve: Curve.Friction, duration: 300, onFinish: () => { + // TODO:知识点:列表项删除动画 + animateTo({ + // 总时间500ms + curve: Curve.Friction, duration: 500, onFinish: () => { + this.state = OperationStatus.IDLE; + } + }, () => { + this.modifier.splice(index, 1); + this.deductionData.splice(index, 1); + }) + } + }, () => { + this.state = OperationStatus.DELETE; + this.modifier[index].offsetX = 150; // 列表项左偏移150 + this.modifier[index].opacity = 0; // 列表项透明度为0 + }) + } catch (err) { + console.error(`delte err:${JSON.stringify(err)}`); + } + } + + /** + * 添加列表项 + * @param item: 列表项 + */ + addItem(item: T): void { + this.deductionData.push(item); + this.modifier.push(new ListItemModifier()); + } +} \ No newline at end of file diff --git a/code/UI/ListBeExchange/ListExchange/src/main/ets/model/ListInfo.ets b/code/UI/ListBeExchange/ListExchange/src/main/ets/model/ListInfo.ets new file mode 100644 index 0000000000000000000000000000000000000000..025520398d3269276fe805bd74b1055463b68552 --- /dev/null +++ b/code/UI/ListBeExchange/ListExchange/src/main/ets/model/ListInfo.ets @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * 列表信息,包括图标、名字、是否可见 + * icon: 列表项图标 + * name: 列表项名称 + */ +@Observed +export class ListInfo { + public icon: ResourceStr = ''; + public name: ResourceStr = ''; + + constructor(icon: ResourceStr = '', name: ResourceStr = '') { + this.icon = icon; + this.name = name; + } +} \ No newline at end of file diff --git a/code/UI/ListBeExchange/ListExchange/src/main/ets/model/MockData.ets b/code/UI/ListBeExchange/ListExchange/src/main/ets/model/MockData.ets new file mode 100644 index 0000000000000000000000000000000000000000..e803776af459c3c4b24dcfbbad330661c1e91974 --- /dev/null +++ b/code/UI/ListBeExchange/ListExchange/src/main/ets/model/MockData.ets @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ListInfo } from './ListInfo'; + +// Mock数据 +export const MEMO_DATA: ListInfo[] = [ + new ListInfo($r('app.media.list_exchange_ic_public_cards_filled'), '账户余额'), + new ListInfo($r('app.media.list_exchange_ic_public_cards_filled2'), 'xx银行储蓄卡(1234)'), + new ListInfo($r('app.media.list_exchange_ic_public_cards_filled3'), 'xx银行储蓄卡(1238)'), + new ListInfo($r('app.media.list_exchange_ic_public_cards_filled4'), 'xx银行储蓄卡(1236)') +]; \ No newline at end of file diff --git a/code/UI/ListBeExchange/ListExchange/src/main/ets/utils/ListExchange.ets b/code/UI/ListBeExchange/ListExchange/src/main/ets/utils/ListExchange.ets new file mode 100644 index 0000000000000000000000000000000000000000..a533d303d291eb21b4da62d1fb1e0cc36d054f04 --- /dev/null +++ b/code/UI/ListBeExchange/ListExchange/src/main/ets/utils/ListExchange.ets @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ListExchangeCtrl } from '../model/ListExchangeCtrl'; +import { ListInfo } from '../model/ListInfo'; +import { logger } from './Logger'; +import { CommonConstants } from '../common/commonConstants'; + +/** + * 功能描述: + * 1. 长按列表项,向上或向下拖动,当前列表项和目标列表项位置互换。 + * 2. 列表项向左滑动,显示删除按钮。 + * 3. 点击删除按钮,当前列表项删除。 + * + * 实现原理: + * 1. 创建一个数组modifier来添加自定义属性对象,根据组合手势GestureGroup来控制自定义属性的值并通过attributeModifier绑定自定义属性对象来动态加载属性。 + * 2. 然后swipeAction属性绑定删除组件,左滑显示此删除组件,点击实现列表项的删除。 + * + * @param appInfoList - 列表数据 + * @param appInfoList - 列表元素交换类 + * @param deductionView - 列表项UI模块 + */ +@Component +export struct ListExchange { + // -------------------对外暴露变量----------------------- + @Link appInfoList: Object[]; + // 控制列表项交换的类 + @Link listExchangeCtrl: ListExchangeCtrl; + // 列表项Builder + @BuilderParam deductionView: (listInfo: Object) => void; + // --------------------私有属性---------------------------- + // 当前选定的列表项 + @State currentListItem: Object | undefined = undefined; + // 初始化长按状态 + @State isLongPress: boolean = false; + + build() { + Column() { + List() { + ForEach(this.appInfoList, (item: Object, index: number) => { + ListItem() { + this.deductionView(item) + } + .id('list_exchange_' + index) + .zIndex(this.currentListItem === item ? 2 : 1) // 层级属性 + .swipeAction({ end: this.defaultDeleteBuilder(item) }) // 用于设置ListItem的划出组件 + .transition(TransitionEffect.OPACITY) + .attributeModifier(this.listExchangeCtrl.getModifier(item)) //动态设置组件的属性方法, 参数为属性修改器 + .gesture( + // 以下组合手势为顺序识别,当长按手势事件未正常触发时,则不会出发拖动手势事件 + GestureGroup(GestureMode.Sequence, + // 长按 + LongPressGesture() + .onAction((event: GestureEvent) => { + this.currentListItem = item; + this.isLongPress = true; + this.listExchangeCtrl.onLongPress(item); + }), + // 拖动 + PanGesture() + .onActionUpdate((event: GestureEvent) => { + this.listExchangeCtrl.onMove(item, event.offsetY); + }) + .onActionEnd((event: GestureEvent) => { + this.listExchangeCtrl.onDrop(item); + this.isLongPress = false; + }) + ).onCancel(() => { + if (!this.isLongPress) { + return; + } + this.listExchangeCtrl.onDrop(item); + })) + }, (item: Object) => JSON.stringify(item)) + } + .divider({ strokeWidth: '1px', color: 0xeaf0ef }) + .scrollBar(BarState.Off) + .border({ + radius: { + bottomLeft: $r('app.string.ohos_id_corner_radius_default_l'), + bottomRight: $r('app.string.ohos_id_corner_radius_default_l') + } + }) + .backgroundColor(Color.White) + .width('100%') + + } + } + + /** + * 删除按钮 + * @param item: 删除的列表项 + */ + @Builder + defaultDeleteBuilder(item: Object) { + Image($r('app.media.list_exchange_icon_delete')) + .width($r('app.integer.list_exchange_icon_size')) + .height($r('app.integer.list_exchange_icon_size')) + .objectFit(ImageFit.Cover) + .margin({ right: $r('app.integer.list_exchange_delete_icon_margin_right') }) + .interpolation(ImageInterpolation.High)// 抗锯齿 + .id('delete_button') + .onClick(() => { + this.listExchangeCtrl.deleteItem(item); + }) + } + + /** + * ListItem自定义组件(默认) + */ + @Builder + defaultDeductionView(listItemInfo: ListInfo) { + Row() { + Image(listItemInfo.icon) + .width($r('app.integer.list_exchange_icon_size')) + .height($r('app.integer.list_exchange_icon_size')) + .draggable(false) // TODO:知识点:保持默认值true时,图片有默认拖拽效果,会影响Grid子组件拖拽判断,所以修改为false + Text(listItemInfo.name) + .margin({ left: $r('app.string.ohos_id_elements_margin_vertical_l') }) + Blank() + Image($r('app.media.list_exchange_ic_public_drawer')) + .width($r('app.integer.list_exchange_icon_size')) + .height($r('app.integer.list_exchange_icon_size')) + .objectFit(ImageFit.Cover) + .draggable(false) // TODO:知识点:保持默认值true时,图片有默认拖拽效果,会影响Grid子组件拖拽判断,所以修改为false + } + .width('100%') + .height(CommonConstants.LIST_ITEM_HEIGHT) + .backgroundColor(Color.White) + .padding({ + left: $r('app.string.ohos_id_card_padding_start'), + right: $r('app.string.ohos_id_card_padding_start') + }) + } + + aboutToAppear(): void { + this.checkParam(); + } + + /** + * 检查输入参数合法性 + */ + checkParam() { + logger.info('checkParam start'); + if (!this.appInfoList.length) { + this.appInfoList.push(new ListInfo($r('app.media.list_exchange_ic_public_cards_filled'), CommonConstants.LIST_NAME)) + } + if (!this.deductionView) { + this.deductionView = this.deductionView; + } + if (!this.listExchangeCtrl) { + this.listExchangeCtrl = new ListExchangeCtrl(); + this.listExchangeCtrl.initData(this.appInfoList); + } + } +} diff --git a/code/UI/ListBeExchange/ListExchange/src/main/ets/utils/Logger.ets b/code/UI/ListBeExchange/ListExchange/src/main/ets/utils/Logger.ets new file mode 100644 index 0000000000000000000000000000000000000000..17d1f64c68d74a9f6b8f3326047b865397e5ddc2 --- /dev/null +++ b/code/UI/ListBeExchange/ListExchange/src/main/ets/utils/Logger.ets @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import hilog from '@ohos.hilog'; + +/** + * 日志打印类 + */ +class Logger { + private domain: number; + private prefix: string; + private format: string = '%{public}s, %{public}s'; + + constructor(prefix: string) { + this.prefix = prefix; + this.domain = 0xFF00; + this.format.toUpperCase(); + } + + debug(...args: string[]) { + hilog.debug(this.domain, this.prefix, this.format, args); + } + + info(...args: string[]) { + hilog.info(this.domain, this.prefix, this.format, args); + } + + warn(...args: string[]) { + hilog.warn(this.domain, this.prefix, this.format, args); + } + + error(...args: string[]) { + hilog.error(this.domain, this.prefix, this.format, args); + } +} + +export let logger = new Logger('[CommonAppDevelopment]') \ No newline at end of file diff --git a/code/UI/ListBeExchange/ListExchange/src/main/ets/view/ListExchangeView.ets b/code/UI/ListBeExchange/ListExchange/src/main/ets/view/ListExchangeView.ets new file mode 100644 index 0000000000000000000000000000000000000000..f9282aa49a68436644446f873f74d091a40489b5 --- /dev/null +++ b/code/UI/ListBeExchange/ListExchange/src/main/ets/view/ListExchangeView.ets @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ListInfo } from '../model/ListInfo'; +import { MEMO_DATA } from '../model/MockData'; +import { ListExchange } from '../utils/ListExchange'; +import { ListExchangeCtrl } from '../model/ListExchangeCtrl'; + +const ITEM_HEIGHT: number = 50; // 列表项高度 + +/** + * 功能说明: 本示例通过List组件、组合手势GestureGroup、swipeAction属性以及attributeModifier属性等实现了列表项的交换和删除。 + * 推荐场景: 扣款列表 + * + * 核心组件: + * 1. ListExchange: 自定义列表交换视图组件 + * 2. ListInfo: 设置列表项元素的类 + * + * 实现步骤: + * 1.设置列表项元素的类。开发者可以根据自身业务列表项的需求场景自行配置。 + * class ListInfo { + icon: ResourceStr = ''; + name: ResourceStr = ''; + + constructor(icon: ResourceStr = '', name: ResourceStr = '') { + this.icon = icon; + this.name = name; + } + } + * + * 2. 数据准备。首先构建一个ListInfo数组,然后向其中传入对应的内容数据。 + * @example + * const MEMO_DATA: ListInfo[] = [ + new ListInfo($r("app.media.list_exchange_ic_public_cards_filled"), '账户余额'), + new ListInfo($r("app.media.list_exchange_ic_public_cards_filled2"), 'xx银行储蓄卡(1234)'), + new ListInfo($r("app.media.list_exchange_ic_public_cards_filled3"), 'xx银行储蓄卡(1238)'), + new ListInfo($r("app.media.list_exchange_ic_public_cards_filled4"), 'xx银行储蓄卡(1236)')]; + * @State appInfoList: ListInfo[] = MEMO_DATA; + * + * 3. 声明管理列表项交换的类ListExchangeCtrl。通过ListExchangeCtrl来实现列表项与列表项之间的位置交换。具体实现请看后续的实现步骤章节。 + * 列表项交换类 + * @State listExchangeCtrl: ListExchangeCtrl = new ListExchangeCtrl(); + + * 4.自定义列表项组件。 + * listItemInfo: 列表项数据信息 + * @Builder deductionView(listItemInfo: ListInfo) {...} + * + * 5. 构建自定义列表视图。在代码合适的位置使用ListExchange组件并传入对应的参数。 + * @example + * 列表交换视图 + * appInfoList: 数据源 + * listExchangeCtrl: 列表项交换类 + * deductionView: 列表项视图 + * ListExchange({ + appInfoList: this.appInfoList, + listExchangeCtrl: this.listExchangeCtrl, + deductionView: (listItemInfo: Object) => { + this.deductionView(listItemInfo as Listinfo) + } + }) + */ + +@Component +export struct ListExchangeViewComponent { + // 初始化列表数据 + @State appInfoList: ListInfo[] = MEMO_DATA; + // 列表项元素交换类 + @State listExchangeCtrl: ListExchangeCtrl = new ListExchangeCtrl(); + + aboutToAppear(): void { + this.listExchangeCtrl.initData(this.appInfoList); + } + + build() { + Column() { + // 标题栏 + Row() { + Text($r('app.string.list_exchange_deduction_sort')) + .textAlign(TextAlign.Start) + Blank() + Text($r('app.string.list_exchange_custom_sort')) + } + .backgroundColor(Color.White) + .border({ + radius: { + topLeft: $r('app.string.ohos_id_corner_radius_default_l'), + topRight: $r('app.string.ohos_id_corner_radius_default_l') + } + }) + .padding({ + left: $r('app.string.ohos_id_card_padding_start'), + right: $r('app.string.ohos_id_card_padding_start') + }) + .width('100%') + .height($r('app.integer.list_exchange_title_height')) + + /** + * 列表交换视图 + * appInfoList: 数据源 + * listExchangeCtrl: 列表项交换类 + * deductionView: 列表项视图 + */ + ListExchange({ + appInfoList: this.appInfoList, + listExchangeCtrl: this.listExchangeCtrl, + deductionView: (listItemInfo: Object) => { + this.deductionView(listItemInfo as ListInfo) + } + }) + } + .height('100%') + .width('100%') + .justifyContent(FlexAlign.Center) + .backgroundColor($r('app.color.list_exchange_background_color')) + .padding({ left: $r('app.string.ohos_id_card_padding_start'), right: $r('app.string.ohos_id_card_padding_start') }) + } + + /** + * ListItem自定义组件(开发者可以根据自己的需求设置列表项的UI) + */ + @Builder + deductionView(listItemInfo: ListInfo) { + Row() { + Image(listItemInfo.icon) + .width($r('app.integer.list_exchange_icon_size')) + .height($r('app.integer.list_exchange_icon_size')) + .draggable(false) // TODO:知识点:保持默认值true时,图片有默认拖拽效果,会影响Grid子组件拖拽判断,所以修改为false + Text(listItemInfo.name) + .margin({ left: $r('app.string.ohos_id_elements_margin_vertical_l') }) + Blank() + Image($r('app.media.list_exchange_ic_public_drawer')) + .width($r('app.integer.list_exchange_icon_size')) + .height($r('app.integer.list_exchange_icon_size')) + .objectFit(ImageFit.Cover) + .draggable(false) // TODO:知识点:保持默认值true时,图片有默认拖拽效果,会影响Grid子组件拖拽判断,所以修改为false + } + .width('100%') + .height(ITEM_HEIGHT) + .backgroundColor(Color.White) + .padding({ + left: $r('app.string.ohos_id_card_padding_start'), + right: $r('app.string.ohos_id_card_padding_start') + }) + } +} \ No newline at end of file diff --git a/code/UI/ListBeExchange/ListExchange/src/main/module.json5 b/code/UI/ListBeExchange/ListExchange/src/main/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..bad96831410726e9332d78c25b3df14d206ef9f3 --- /dev/null +++ b/code/UI/ListBeExchange/ListExchange/src/main/module.json5 @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +{ + "module": { + "name": "listexchange", + "type": "har", + "deviceTypes": [ + "default", + "tablet" + ] + } +} \ No newline at end of file diff --git a/code/UI/ListBeExchange/ListExchange/src/main/resources/base/element/color.json b/code/UI/ListBeExchange/ListExchange/src/main/resources/base/element/color.json new file mode 100644 index 0000000000000000000000000000000000000000..b29c30fe5d3a05cec81e40c4ee71100e1398849c --- /dev/null +++ b/code/UI/ListBeExchange/ListExchange/src/main/resources/base/element/color.json @@ -0,0 +1,12 @@ +{ + "color": [ + { + "name": "list_exchange_background_color", + "value": "#F3F3F3" + }, + { + "name": "list_exchange_box_shadow", + "value": "#ffd2d2d2" + } + ] +} \ No newline at end of file diff --git a/code/UI/ListBeExchange/ListExchange/src/main/resources/base/element/integer.json b/code/UI/ListBeExchange/ListExchange/src/main/resources/base/element/integer.json new file mode 100644 index 0000000000000000000000000000000000000000..9bbc9744fed9976aa0f59ec8b0212041ddd9e43f --- /dev/null +++ b/code/UI/ListBeExchange/ListExchange/src/main/resources/base/element/integer.json @@ -0,0 +1,24 @@ +{ + "integer": [ + { + "name": "list_exchange_icon_size", + "value": 30 + }, + { + "name": "list_exchange_title_height", + "value": 50 + }, + { + "name": "list_exchange_list_height", + "value": 210 + }, + { + "name": "list_exchange_delete_icon_margin_right", + "value": 12 + }, + { + "name": "list_exchange_shadow_radius", + "value": 70 + } + ] +} \ No newline at end of file diff --git a/code/UI/ListBeExchange/ListExchange/src/main/resources/base/element/string.json b/code/UI/ListBeExchange/ListExchange/src/main/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..3f782e5c34fe4d8229d22156970c477d6d1d36d6 --- /dev/null +++ b/code/UI/ListBeExchange/ListExchange/src/main/resources/base/element/string.json @@ -0,0 +1,28 @@ +{ + "string": [ + { + "name": "list_exchange_deduction_sort", + "value": "扣款排序" + }, + { + "name": "list_exchange_custom_sort", + "value": "自定义排序" + }, + { + "name": "list_exchange_operation_text", + "value": "长按拖动/左滑删除" + }, + { + "name": "ohos_id_corner_radius_default_l", + "value": "16" + }, + { + "name": "ohos_id_card_padding_start", + "value": "12" + }, + { + "name": "ohos_id_elements_margin_vertical_l", + "value": "16" + } + ] +} diff --git a/code/UI/ListBeExchange/ListExchange/src/main/resources/base/media/list_exchange.gif b/code/UI/ListBeExchange/ListExchange/src/main/resources/base/media/list_exchange.gif new file mode 100644 index 0000000000000000000000000000000000000000..80dc8b65e8d29ce3e4a489c00caff366447cb639 Binary files /dev/null and b/code/UI/ListBeExchange/ListExchange/src/main/resources/base/media/list_exchange.gif differ diff --git a/code/UI/ListBeExchange/ListExchange/src/main/resources/base/media/list_exchange_ic_public_cards_filled.svg b/code/UI/ListBeExchange/ListExchange/src/main/resources/base/media/list_exchange_ic_public_cards_filled.svg new file mode 100644 index 0000000000000000000000000000000000000000..8d910e35a3ed226dcf0ccc610f23035b767e1d76 --- /dev/null +++ b/code/UI/ListBeExchange/ListExchange/src/main/resources/base/media/list_exchange_ic_public_cards_filled.svg @@ -0,0 +1,13 @@ + + + Public/ic_public_cards_filled + + + + + + + + + + \ No newline at end of file diff --git a/code/UI/ListBeExchange/ListExchange/src/main/resources/base/media/list_exchange_ic_public_cards_filled2.svg b/code/UI/ListBeExchange/ListExchange/src/main/resources/base/media/list_exchange_ic_public_cards_filled2.svg new file mode 100644 index 0000000000000000000000000000000000000000..819f5858da9d49528acefa2266c5e64b7c661f71 --- /dev/null +++ b/code/UI/ListBeExchange/ListExchange/src/main/resources/base/media/list_exchange_ic_public_cards_filled2.svg @@ -0,0 +1,13 @@ + + + Public/ic_public_cards_filled + + + + + + + + + + \ No newline at end of file diff --git a/code/UI/ListBeExchange/ListExchange/src/main/resources/base/media/list_exchange_ic_public_cards_filled3.svg b/code/UI/ListBeExchange/ListExchange/src/main/resources/base/media/list_exchange_ic_public_cards_filled3.svg new file mode 100644 index 0000000000000000000000000000000000000000..68741e5c4e6466c621d73882f37876a86aa4fc96 --- /dev/null +++ b/code/UI/ListBeExchange/ListExchange/src/main/resources/base/media/list_exchange_ic_public_cards_filled3.svg @@ -0,0 +1,13 @@ + + + Public/ic_public_cards_filled + + + + + + + + + + \ No newline at end of file diff --git a/code/UI/ListBeExchange/ListExchange/src/main/resources/base/media/list_exchange_ic_public_cards_filled4.svg b/code/UI/ListBeExchange/ListExchange/src/main/resources/base/media/list_exchange_ic_public_cards_filled4.svg new file mode 100644 index 0000000000000000000000000000000000000000..516d7a471d5b5c7a1021c73e01e24444e528731e --- /dev/null +++ b/code/UI/ListBeExchange/ListExchange/src/main/resources/base/media/list_exchange_ic_public_cards_filled4.svg @@ -0,0 +1,13 @@ + + + Public/ic_public_cards_filled + + + + + + + + + + \ No newline at end of file diff --git a/code/UI/ListBeExchange/ListExchange/src/main/resources/base/media/list_exchange_ic_public_drawer.svg b/code/UI/ListBeExchange/ListExchange/src/main/resources/base/media/list_exchange_ic_public_drawer.svg new file mode 100644 index 0000000000000000000000000000000000000000..17f797310c6623c4c579dc94318ee664bf4d1d5c --- /dev/null +++ b/code/UI/ListBeExchange/ListExchange/src/main/resources/base/media/list_exchange_ic_public_drawer.svg @@ -0,0 +1,13 @@ + + + Public/ic_public_drawer + + + + + + + + + + \ No newline at end of file diff --git a/code/UI/ListBeExchange/ListExchange/src/main/resources/base/media/list_exchange_icon_delete.svg b/code/UI/ListBeExchange/ListExchange/src/main/resources/base/media/list_exchange_icon_delete.svg new file mode 100644 index 0000000000000000000000000000000000000000..4d89a27edced52c810e81e9b99ce3bde97eec7e5 --- /dev/null +++ b/code/UI/ListBeExchange/ListExchange/src/main/resources/base/media/list_exchange_icon_delete.svg @@ -0,0 +1,35 @@ + + + icon_delete + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/code/UI/ListBeExchange/ListExchange/src/main/resources/en_US/element/string.json b/code/UI/ListBeExchange/ListExchange/src/main/resources/en_US/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..2ff9c1095199f2583f055ba7a357d164520ae200 --- /dev/null +++ b/code/UI/ListBeExchange/ListExchange/src/main/resources/en_US/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "list_exchange_deduction_sort", + "value": "deduction sort" + }, + { + "name": "list_exchange_custom_sort", + "value": "custom sort" + }, + { + "name": "list_exchange_operation_text", + "value": "operation text" + } + ] +} diff --git a/code/UI/ListBeExchange/ListExchange/src/main/resources/rawfile/routerMap/listexchange.json b/code/UI/ListBeExchange/ListExchange/src/main/resources/rawfile/routerMap/listexchange.json new file mode 100644 index 0000000000000000000000000000000000000000..9249bc2f0a304f40a07638fb8e05276ee583b543 --- /dev/null +++ b/code/UI/ListBeExchange/ListExchange/src/main/resources/rawfile/routerMap/listexchange.json @@ -0,0 +1,10 @@ +{ + "routerMap": [ + { + "name": "listexchange/ListExchangeView", + "pageModule": "listexchange", + "pageSourceFile": "src/main/ets/generated/RouterBuilder.ets", + "registerFunction": "listExchangeViewRegister" + } + ] +} \ No newline at end of file diff --git a/code/UI/ListBeExchange/ListExchange/src/main/resources/zh_CN/element/string.json b/code/UI/ListBeExchange/ListExchange/src/main/resources/zh_CN/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..e1bffde5229c40ff694d8cc96301bfe313c1cdfa --- /dev/null +++ b/code/UI/ListBeExchange/ListExchange/src/main/resources/zh_CN/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "list_exchange_deduction_sort", + "value": "扣款排序" + }, + { + "name": "list_exchange_custom_sort", + "value": "自定义排序" + }, + { + "name": "list_exchange_operation_text", + "value": "长按拖动/左滑删除" + } + ] +} diff --git a/code/UI/ListBeExchange/README.md b/code/UI/ListBeExchange/README.md new file mode 100644 index 0000000000000000000000000000000000000000..ee4e38a8d9234b007aad9d0b9b2cb95169d1db46 --- /dev/null +++ b/code/UI/ListBeExchange/README.md @@ -0,0 +1,332 @@ +# 列表项交换案例 + +### 介绍 + +本案例通过List组件、组合手势GestureGroup、swipeAction属性以及attributeModifier属性等实现了列表项的交换和删除。 + +### 效果图预览 + +![](ListExchange/src/main/resources/base/media/list_exchange.gif) + +**使用说明**: + +1. 进入页面,长按列表项,执行拖拽操作,当拖拽长度大于列表项所占高度一半的时候,列表项进行交换。 +2. 列表项左滑,显示删除按钮,点击删除按钮,此列表项被删除。 + + +### 下载安装 + +1.模块oh-package.json5文件中引入依赖。 +```typescript +"dependencies": { + "listexchange": "har包地址" +} +``` + +2.ets文件import自定义视图实现列表视图。 + +```typescript +import { ListExchange } from 'listexchange'; +``` +### 快速使用 + +本章节主要介绍了如何快速上手自定义视图实现列表切换效果组件。 + +1. 设置列表项元素的类。开发者可以根据自身业务列表项的需求场景自行变更或者拓展属性,但是需要自定义列表项元素视图。 + +```typescript +class ListInfo { + icon: ResourceStr = ''; + name: ResourceStr = ''; + + constructor(icon: ResourceStr = '', name: ResourceStr = '') { + this.icon = icon; + this.name = name; + } + } +``` + +2. 数据准备。首先构建一个ListInfo类型的数组,然后向其中传入对应的内容数据。 + +```typescript +const MEMO_DATA: ListInfo[] = [ + new ListInfo($r("app.media.list_exchange_ic_public_cards_filled"), '账户余额'), + new ListInfo($r("app.media.list_exchange_ic_public_cards_filled2"), 'xx银行储蓄卡(1234)'), + new ListInfo($r("app.media.list_exchange_ic_public_cards_filled3"), 'xx银行储蓄卡(1238)'), + new ListInfo($r("app.media.list_exchange_ic_public_cards_filled4"), 'xx银行储蓄卡(1236)')]; + +@State appInfoList: ListInfo[] = MEMO_DATA; +``` +3. 声明管理列表项交换的类ListExchangeCtrl。通过ListExchangeCtrl来实现列表项与列表项之间的位置交换。具体实现请看后续的实现步骤章节。 + +```typescript +// 列表项交换类 +@State listExchangeCtrl: ListExchangeCtrl = new ListExchangeCtrl(); +``` +4. 自定义列表项组件。开发者可以自定义列表项的UI。 + +```typescript + + // 列表项数据信息 + @Builder deductionView(listItemInfo: ListInfo) {...} + +``` +5. 构建自定义列表视图。在代码合适的位置使用ListExchange组件并传入对应的参数。 + +```typescript +/** + * 列表交换视图 + * appInfoList: 数据源 + * listExchangeCtrl: 列表项交换类 + * deductionView: 自定义列表项元素视图 + */ +ListExchange({ + appInfoList: this.appInfoList, + listExchangeCtrl: this.listExchangeCtrl, + deductionView: (listItemInfo: Object) => { + this.deductionView(listItemInfo as Listinfo) + } +}) + +``` + +### 属性(接口)说明 + +ListInfo类属性(开发者可以自行拓展或者更改列表的属性元素) + +| 属性 | 类型 | 释义 | 默认值 | +|:-------------:|:-----------:|:----:|:---:| +| icon | ResourceStr | 列表图片 | - | +| name | ResourceStr | 列表名称 | - | + + +ListExchange组件属性 + +| 属性 | 类型 | 释义 | 默认值 | +|:----------------:|:----------------:|:----------:|:---:| +| appInfoList | ListInfo[] | 列表数据源 | - | +| listExchangeCtrl | ListExchangeCtrl | 列表项元素交换类 | - | +| deductionView | void | 自定义列表项元素视图 | - | + +### 实现思路 + +首先创建一个数组modifier来添加自定义属性对象,根据组合手势GestureGroup来控制自定义属性的值并通过attributeModifier绑定自定义属性对象来动态加载属性。 +然后通过swipeAction属性绑定删除组件,左滑显示此删除组件,点击实现列表项的删除。 +1. 声明一个数组,添加自定义属性对象,每个自定义属性对象对应一个列表项,源码参考[AttributeModifier.ets](ListExchange/src/main/ets/model/AttributeModifier.ets)和[ListExchangeCtrl.ets](ListExchange/src/main/ets/model/ListExchangeCtrl.ets)。 +```typescript + initData(deductionData: Array) { + this.deductionData = deductionData; + deductionData.forEach(() => { + this.modifier.push(new ListItemModifier()); + }) +} + /** + * 通过实现AttributeModifier接口,自定义属性修改器 + * 将拖拽排序相关样式封装成属性修改器,可以方便移植 + */ + export class ListItemModifier implements AttributeModifier { + // 阴影 + public hasShadow: boolean = false; + // 缩放 + public scale: number = 1; + // 纵轴偏移量 + public offsetY: number = 0; + // 横轴偏移量 + public offsetX: number = 0; + // 透明度 + public opacity: number = 1; + // 是否被删除 + + public static getInstance(): ListItemModifier { + if (!ListItemModifier.instance) { + ListItemModifier.instance = new ListItemModifier(); + } + return ListItemModifier.instance; + } + + /** + * 定义组件普通状态时的样式 + * @param instance: ListItem属性 + */ + applyNormalAttribute(instance: ListItemAttribute): void { + if (this.hasShadow) { + instance.shadow({ radius: $r('app.integer.list_exchange_shadow_radius'), color: $r('app.color.box_shadow') }); + instance.zIndex(1); + instance.opacity(0.5); + } else { + instance.opacity(this.opacity); + } + instance.translate({ x: this.offsetX, y: this.offsetY }); + instance.scale({ x: this.scale, y: this.scale }); + } +} +``` +2. 绑定attributeModifier属性以及组合手势GestureGroup,attributeModifier属性的值为对应的自定义属性对象。源码参考[ListExchangeView.ets](ListExchange/src/main/ets/view/ListExchangeView.ets)。 +```typescript +// 列表区域 +List() { + ForEach(this.appInfoList, (item: Object) => { + ListItem() { + this.deductionView(item) + } + .zIndex(this.currentListItem === item ? 2 : 1) // 层级属性 + .swipeAction({ end: this.defaultDeleteBuilder(item) }) // 用于设置ListItem的划出组件 + .transition(TransitionEffect.OPACITY) + .attributeModifier(this.listExchangeCtrl.getModifier(item)) //动态设置组件的属性方法, 参数为属性修改器 + .gesture( + // 以下组合手势为顺序识别,当长按手势事件未正常触发时,则不会出发拖动手势事件 + GestureGroup(GestureMode.Sequence, + // 长按 + LongPressGesture() + .onAction((event: GestureEvent) => { + this.currentListItem = item; + this.isLongPress = true; + this.listExchangeCtrl.onLongPress(item); + }), + // 拖动 + PanGesture() + .onActionUpdate((event: GestureEvent) => { + this.listExchangeCtrl.onMove(item, event.offsetY); + }) + .onActionEnd((event: GestureEvent) => { + this.listExchangeCtrl.onDrop(item); + this.isLongPress = false; + }) + ).onCancel(() => { + if (!this.isLongPress) { + return; + } + this.listExchangeCtrl.onDrop(item); + })) + }, (item: Object) => JSON.stringify(item)) +} +.divider({ strokeWidth: '1px', color: 0xeaf0ef }) +.scrollBar(BarState.Off) +.border({ + radius: { + bottomLeft: $r('app.string.ohos_id_corner_radius_default_l'), + bottomRight: $r('app.string.ohos_id_corner_radius_default_l') + } +}) +.backgroundColor(Color.White) +.width('100%') + +``` + +3. 长按列表项,通过LongPressGesture识别长按手势,执行onLongPress函数方法更改此列表项的scale、shadow、zIndex和opacity等属性,并通过animateTo来实现动画效果,源码参考[ListExchangeCtrl.ets](ListExchange/src/main/ets/model/ListExchangeCtrl.ets)。 + +```typescript + onLongPress(item: T) { + const index: number = this.deductionData.indexOf(item); + this.dragRefOffset = 0; + // TODO:知识点:长按当前列表项透明度和放大动画 + animateTo({ curve: Curve.Friction, duration: ANIMATE_DURATION }, () => { + this.state = OperationStatus.PRESSING; + this.modifier[index].hasShadow = true; + this.modifier[index].scale = 1.04; // 放大比例为1.04 + }) + } +``` +4. 交换列表项,通过PanGesture手势的onActionUpdate方法监听拖动的纵轴移动长度,然后执行onMove方法,根据移动长度的大小来判断是否执行列表项交换方法changeItem,源码参考[ListExchangeCtrl.ets](ListExchange/src/main/ets/model/ListExchangeCtrl.ets)。 +```typescript + onMove(item: T, offsetY: number) { + const index: number = this.deductionData.indexOf(item); + this.offsetY = offsetY - this.dragRefOffset; + this.modifier[index].offsetY = this.offsetY; + const direction: number = this.offsetY > 0 ? 1 : -1; + // 触发拖动时,被覆盖子组件缩小与恢复的动画 + const curveValue: ICurve = curves.initCurve(Curve.Sharp); + const value: number = curveValue.interpolate(Math.abs(this.offsetY) / ITEM_HEIGHT); + const shrinkScale: number = 1 - value / 10; // 计算缩放比例,value值缩小10倍 + if (index < this.modifier.length - 1) { // 当拖拽的时候,被交换的对象会缩放 + this.modifier[index + 1].scale = direction > 0 ? shrinkScale : 1; + } + if (index > 0) { + this.modifier[index - 1].scale = direction > 0 ? 1 : shrinkScale; + } + // TODO:知识点:处理列表项的切换操作 + if (Math.abs(this.offsetY) > ITEM_HEIGHT / 2) { + animateTo({ curve: Curve.Friction, duration: commonConstants.ANIMATE_DURATION }, () => { + this.offsetY -= direction * ITEM_HEIGHT; + this.dragRefOffset += direction * ITEM_HEIGHT; + this.modifier[index].offsetY = this.offsetY; + this.changeItem(index, index + direction); + }) + } + } + + changeItem(index: number, newIndex: number): void { + const tmp: Array = this.deductionData.splice(index, 1); + this.deductionData.splice(newIndex, 0, tmp[0]); + const tmp2: Array = this.modifier.splice(index, 1); + this.modifier.splice(newIndex, 0, tmp2[0]); + } +``` +5. 通过swipeAction属性绑定删除按钮组件,列表项左滑显示删除组件,点击删除按钮,列表项删除。源码参考[ListExchangeCtrl.ets](ListExchange/src/main/ets/model/ListExchangeCtrl.ets)。 +```typescript +deleteItem(item: T): void { + const index = this.deductionData.indexOf(item); + this.dragRefOffset = 0; + // TODO:知识点:左偏移以及透明度动画 + animateTo({ + curve: Curve.Friction, onFinish: () => { + // TODO:知识点:列表项删除动画 + animateTo({ + curve: Curve.Friction, onFinish: () => { + this.state = OperationStatus.IDLE; + } + }, () => { + this.modifier.splice(index, 1); + this.deductionData.splice(index, 1); + }) + } + }, () => { + this.state = OperationStatus.DELETE; + this.modifier[index].offsetX = 150; // 列表项左偏移150 + this.modifier[index].opacity = 0; // 列表项透明度为0 + }) +} +``` + +### 工程结构&模块类型 + +``` +listexchange // har类型 +|---common +| |---commonConstants.ets // 常量 +|---model +| |---AttributeModifier.ets // 属性对象 +| |---ListExchangeCtrl.ets // 列表项交换 +| |---ListInfo.ets // 列表项信息 +| |---MockData.ets // 模拟数据 +|---util +| |---ListExchange.ets // 自定义列表视图 +| |---Logger.ets // 日志 +|---view +| |---ListExchangeView.ets // 视图层-应用主页面 +``` + +### 参考资料 + +[List](https://docs.openharmony.cn/pages/v5.0/zh-cn/application-dev/reference/apis-arkui/arkui-ts/ts-container-list.md) + +[GestureGroup](https://docs.openharmony.cn/pages/v5.0/zh-cn/application-dev/reference/apis-arkui/arkui-ts/ts-combined-gestures.md) + +### 约束与限制 + +1.本示例仅支持标准系统上运行。 + +2.本示例已适配API version 12版本SDK。 + +3.本示例需要使用DevEco Studio 5.0.0 Release及以上版本才可编译运行。 + +### 下载 + +如需单独下载本工程,执行如下命令: +```javascript +git init +git config core.sparsecheckout true +echo /code/UI/listexchange/ > .git/info/sparse-checkout +git remote add origin https://gitee.com/openharmony/applications_app_samples.git +git pull origin master +``` \ No newline at end of file diff --git a/code/UI/ListBeExchange/build-profile.json5 b/code/UI/ListBeExchange/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..c88f8e87b6754ed8ad4d9ca5eaf5efec7bd353ba --- /dev/null +++ b/code/UI/ListBeExchange/build-profile.json5 @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +{ + "app": { + "signingConfigs": [], + "products": [ + { + "name": "default", + "signingConfig": "default", + "compatibleSdkVersion": 12, + "compileSdkVersion": 12, + "runtimeOS": "OpenHarmony", + "buildOption": { + "strictMode": { + "caseSensitiveCheck": true, + "useNormalizedOHMUrl": true + } + } + } + ], + "buildModeSet": [ + { + "name": "debug", + }, + { + "name": "release" + } + ] + }, + "modules": [ + { + "name": "entry", + "srcPath": "./entry", + "targets": [ + { + "name": "default", + "applyToProducts": [ + "default" + ] + } + ] + }, + { + "name": "listexchange", + "srcPath": "./ListExchange" + } + ] +} \ No newline at end of file diff --git a/code/UI/ListBeExchange/code-linter.json5 b/code/UI/ListBeExchange/code-linter.json5 new file mode 100644 index 0000000000000000000000000000000000000000..28586467ee7a761c737d8654a73aed6fddbc3c71 --- /dev/null +++ b/code/UI/ListBeExchange/code-linter.json5 @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +{ + "files": [ + "**/*.ets" + ], + "ignore": [ + "**/src/ohosTest/**/*", + "**/src/test/**/*", + "**/src/mock/**/*", + "**/node_modules/**/*", + "**/oh_modules/**/*", + "**/build/**/*", + "**/.preview/**/*" + ], + "ruleSet": [ + "plugin:@performance/recommended", + "plugin:@typescript-eslint/recommended" + ], + "rules": { + } +} \ No newline at end of file diff --git a/code/UI/ListBeExchange/entry/.gitignore b/code/UI/ListBeExchange/entry/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..e2713a2779c5a3e0eb879efe6115455592caeea5 --- /dev/null +++ b/code/UI/ListBeExchange/entry/.gitignore @@ -0,0 +1,6 @@ +/node_modules +/oh_modules +/.preview +/build +/.cxx +/.test \ No newline at end of file diff --git a/code/UI/ListBeExchange/entry/build-profile.json5 b/code/UI/ListBeExchange/entry/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..e7569e3056e27af38e9991b7ea73ec10f3ba8a05 --- /dev/null +++ b/code/UI/ListBeExchange/entry/build-profile.json5 @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +{ + "apiType": "stageMode", + "buildOption": { + }, + "buildOptionSet": [ + { + "name": "release", + "arkOptions": { + "obfuscation": { + "ruleOptions": { + "enable": false, + "files": [ + "./obfuscation-rules.txt" + ] + } + } + } + }, + ], + "targets": [ + { + "name": "default" + }, + { + "name": "ohosTest", + } + ] +} \ No newline at end of file diff --git a/code/UI/ListBeExchange/entry/hvigorfile.ts b/code/UI/ListBeExchange/entry/hvigorfile.ts new file mode 100644 index 0000000000000000000000000000000000000000..e4f43d54667f8327c367c8096bd08bb8c75aff54 --- /dev/null +++ b/code/UI/ListBeExchange/entry/hvigorfile.ts @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { hapTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: hapTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ +} diff --git a/code/UI/ListBeExchange/entry/obfuscation-rules.txt b/code/UI/ListBeExchange/entry/obfuscation-rules.txt new file mode 100644 index 0000000000000000000000000000000000000000..272efb6ca3f240859091bbbfc7c5802d52793b0b --- /dev/null +++ b/code/UI/ListBeExchange/entry/obfuscation-rules.txt @@ -0,0 +1,23 @@ +# Define project specific obfuscation rules here. +# You can include the obfuscation configuration files in the current module's build-profile.json5. +# +# For more details, see +# https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/source-obfuscation-V5 + +# Obfuscation options: +# -disable-obfuscation: disable all obfuscations +# -enable-property-obfuscation: obfuscate the property names +# -enable-toplevel-obfuscation: obfuscate the names in the global scope +# -compact: remove unnecessary blank spaces and all line feeds +# -remove-log: remove all console.* statements +# -print-namecache: print the name cache that contains the mapping from the old names to new names +# -apply-namecache: reuse the given cache file + +# Keep options: +# -keep-property-name: specifies property names that you want to keep +# -keep-global-name: specifies names that you want to keep in the global scope + +-enable-property-obfuscation +-enable-toplevel-obfuscation +-enable-filename-obfuscation +-enable-export-obfuscation \ No newline at end of file diff --git a/code/UI/ListBeExchange/entry/oh-package.json5 b/code/UI/ListBeExchange/entry/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..41c30351d458951a613a63ff0654af285e2dd99f --- /dev/null +++ b/code/UI/ListBeExchange/entry/oh-package.json5 @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +{ + "name": "entry", + "version": "1.0.0", + "description": "Please describe the basic information.", + "main": "", + "author": "", + "license": "", + "dependencies": { + "listexchange": "file:../ListExchange" + } +} + diff --git a/code/UI/ListBeExchange/entry/src/main/ets/entryability/EntryAbility.ets b/code/UI/ListBeExchange/entry/src/main/ets/entryability/EntryAbility.ets new file mode 100644 index 0000000000000000000000000000000000000000..23a5f218909827e91bf6f66ce3d472c99df7c7c2 --- /dev/null +++ b/code/UI/ListBeExchange/entry/src/main/ets/entryability/EntryAbility.ets @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit'; +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { window } from '@kit.ArkUI'; + +export default class EntryAbility extends UIAbility { + onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate'); + } + + onDestroy(): void { + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy'); + } + + onWindowStageCreate(windowStage: window.WindowStage): void { + // Main window is created, set main page for this ability + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate'); + + windowStage.loadContent('pages/Index', (err) => { + if (err.code) { + hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? ''); + return; + } + hilog.info(0x0000, 'testTag', 'Succeeded in loading the content.'); + }); + } + + onWindowStageDestroy(): void { + // Main window is destroyed, release UI related resources + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageDestroy'); + } + + onForeground(): void { + // Ability has brought to foreground + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground'); + } + + onBackground(): void { + // Ability has back to background + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground'); + } +} diff --git a/code/UI/ListBeExchange/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets b/code/UI/ListBeExchange/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets new file mode 100644 index 0000000000000000000000000000000000000000..69a47ce6bf1d1e2d0c5a0f0432a8bb83ef1daae4 --- /dev/null +++ b/code/UI/ListBeExchange/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { BackupExtensionAbility, BundleVersion } from '@kit.CoreFileKit'; + +export default class EntryBackupAbility extends BackupExtensionAbility { + async onBackup() { + hilog.info(0x0000, 'testTag', 'onBackup ok'); + } + + async onRestore(bundleVersion: BundleVersion) { + hilog.info(0x0000, 'testTag', 'onRestore ok %{public}s', JSON.stringify(bundleVersion)); + } +} \ No newline at end of file diff --git a/code/UI/ListBeExchange/entry/src/main/ets/pages/Index.ets b/code/UI/ListBeExchange/entry/src/main/ets/pages/Index.ets new file mode 100644 index 0000000000000000000000000000000000000000..5cec16578f68cb9db9af388cb15b2944267e89f0 --- /dev/null +++ b/code/UI/ListBeExchange/entry/src/main/ets/pages/Index.ets @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ListExchangeView } from 'listexchange' + +@Entry +@Component +struct Index { + build() { + Column() { + ListExchangeView() + } + .height('100%') + .width('100%') + } +} \ No newline at end of file diff --git a/code/UI/ListBeExchange/entry/src/main/module.json5 b/code/UI/ListBeExchange/entry/src/main/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..429e8691043569e75c3e54d034bc9e0d173171d1 --- /dev/null +++ b/code/UI/ListBeExchange/entry/src/main/module.json5 @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +{ + "module": { + "name": "entry", + "type": "entry", + "description": "$string:module_desc", + "mainElement": "EntryAbility", + "deviceTypes": [ + "default", + "tablet" + ], + "deliveryWithInstall": true, + "installationFree": false, + "pages": "$profile:main_pages", + "abilities": [ + { + "name": "EntryAbility", + "srcEntry": "./ets/entryability/EntryAbility.ets", + "description": "$string:EntryAbility_desc", + "icon": "$media:layered_image", + "label": "$string:EntryAbility_label", + "startWindowIcon": "$media:startIcon", + "startWindowBackground": "$color:start_window_background", + "exported": true, + "skills": [ + { + "entities": [ + "entity.system.home" + ], + "actions": [ + "action.system.home" + ] + } + ] + } + ], + "extensionAbilities": [ + { + "name": "EntryBackupAbility", + "srcEntry": "./ets/entrybackupability/EntryBackupAbility.ets", + "type": "backup", + "exported": false, + "metadata": [ + { + "name": "ohos.extension.backup", + "resource": "$profile:backup_config" + } + ] + } + ] + } +} \ No newline at end of file diff --git a/code/UI/ListBeExchange/entry/src/main/resources/base/element/color.json b/code/UI/ListBeExchange/entry/src/main/resources/base/element/color.json new file mode 100644 index 0000000000000000000000000000000000000000..3c712962da3c2751c2b9ddb53559afcbd2b54a02 --- /dev/null +++ b/code/UI/ListBeExchange/entry/src/main/resources/base/element/color.json @@ -0,0 +1,8 @@ +{ + "color": [ + { + "name": "start_window_background", + "value": "#FFFFFF" + } + ] +} \ No newline at end of file diff --git a/code/UI/ListBeExchange/entry/src/main/resources/base/element/string.json b/code/UI/ListBeExchange/entry/src/main/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..93439ad219f0dc9caeafe5dd1039f786234255c0 --- /dev/null +++ b/code/UI/ListBeExchange/entry/src/main/resources/base/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "module description" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "ListExchange" + } + ] +} \ No newline at end of file diff --git a/code/UI/ListBeExchange/entry/src/main/resources/base/media/background.png b/code/UI/ListBeExchange/entry/src/main/resources/base/media/background.png new file mode 100644 index 0000000000000000000000000000000000000000..f939c9fa8cc8914832e602198745f592a0dfa34d Binary files /dev/null and b/code/UI/ListBeExchange/entry/src/main/resources/base/media/background.png differ diff --git a/code/UI/ListBeExchange/entry/src/main/resources/base/media/foreground.png b/code/UI/ListBeExchange/entry/src/main/resources/base/media/foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..4483ddad1f079e1089d685bd204ee1cfe1d01902 Binary files /dev/null and b/code/UI/ListBeExchange/entry/src/main/resources/base/media/foreground.png differ diff --git a/code/UI/ListBeExchange/entry/src/main/resources/base/media/layered_image.json b/code/UI/ListBeExchange/entry/src/main/resources/base/media/layered_image.json new file mode 100644 index 0000000000000000000000000000000000000000..fb49920440fb4d246c82f9ada275e26123a2136a --- /dev/null +++ b/code/UI/ListBeExchange/entry/src/main/resources/base/media/layered_image.json @@ -0,0 +1,7 @@ +{ + "layered-image": + { + "background" : "$media:background", + "foreground" : "$media:foreground" + } +} \ No newline at end of file diff --git a/code/UI/ListBeExchange/entry/src/main/resources/base/media/startIcon.png b/code/UI/ListBeExchange/entry/src/main/resources/base/media/startIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..205ad8b5a8a42e8762fbe4899b8e5e31ce822b8b Binary files /dev/null and b/code/UI/ListBeExchange/entry/src/main/resources/base/media/startIcon.png differ diff --git a/code/UI/ListBeExchange/entry/src/main/resources/base/profile/backup_config.json b/code/UI/ListBeExchange/entry/src/main/resources/base/profile/backup_config.json new file mode 100644 index 0000000000000000000000000000000000000000..78f40ae7c494d71e2482278f359ec790ca73471a --- /dev/null +++ b/code/UI/ListBeExchange/entry/src/main/resources/base/profile/backup_config.json @@ -0,0 +1,3 @@ +{ + "allowToBackupRestore": true +} \ No newline at end of file diff --git a/code/UI/ListBeExchange/entry/src/main/resources/base/profile/main_pages.json b/code/UI/ListBeExchange/entry/src/main/resources/base/profile/main_pages.json new file mode 100644 index 0000000000000000000000000000000000000000..1898d94f58d6128ab712be2c68acc7c98e9ab9ce --- /dev/null +++ b/code/UI/ListBeExchange/entry/src/main/resources/base/profile/main_pages.json @@ -0,0 +1,5 @@ +{ + "src": [ + "pages/Index" + ] +} diff --git a/code/UI/ListBeExchange/entry/src/main/resources/en_US/element/string.json b/code/UI/ListBeExchange/entry/src/main/resources/en_US/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..a376e30c1b1735411fb63f2a29be4f43d5b4f57e --- /dev/null +++ b/code/UI/ListBeExchange/entry/src/main/resources/en_US/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "module description" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "ListExChange" + } + ] +} \ No newline at end of file diff --git a/code/UI/ListBeExchange/entry/src/main/resources/zh_CN/element/string.json b/code/UI/ListBeExchange/entry/src/main/resources/zh_CN/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..011067b3ad0754fb547749ea99767407701621c5 --- /dev/null +++ b/code/UI/ListBeExchange/entry/src/main/resources/zh_CN/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "模块描述" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "ListExChange" + } + ] +} \ No newline at end of file diff --git a/code/UI/ListBeExchange/entry/src/ohosTest/ets/test/Ability.test.ets b/code/UI/ListBeExchange/entry/src/ohosTest/ets/test/Ability.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..0f8ce9a2c012f8fe36114cef65216ef0b6254f41 --- /dev/null +++ b/code/UI/ListBeExchange/entry/src/ohosTest/ets/test/Ability.test.ets @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; + +export default function abilityTest() { + describe('ActsAbilityTest', () => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + }) + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + }) + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + }) + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }) + it('assertContain', 0, () => { + // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. + hilog.info(0x0000, 'testTag', '%{public}s', 'it begin'); + let a = 'abc'; + let b = 'b'; + // Defines a variety of assertion methods, which are used to declare expected boolean conditions. + expect(a).assertContain(b); + expect(a).assertEqual(a); + }) + }) +} \ No newline at end of file diff --git a/code/UI/ListBeExchange/entry/src/ohosTest/ets/test/List.test.ets b/code/UI/ListBeExchange/entry/src/ohosTest/ets/test/List.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..1eac52fcebe8958e19a7b8fed2e8f39c520a3e42 --- /dev/null +++ b/code/UI/ListBeExchange/entry/src/ohosTest/ets/test/List.test.ets @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import abilityTest from './Ability.test'; + +export default function testsuite() { + abilityTest(); +} \ No newline at end of file diff --git a/code/UI/ListBeExchange/entry/src/ohosTest/module.json5 b/code/UI/ListBeExchange/entry/src/ohosTest/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..c3fd9dda3040d888d9d8b0b62bcb5d3b6fbeb614 --- /dev/null +++ b/code/UI/ListBeExchange/entry/src/ohosTest/module.json5 @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +{ + "module": { + "name": "entry_test", + "type": "feature", + "deviceTypes": [ + "default", + "tablet" + ], + "deliveryWithInstall": true, + "installationFree": false + } +} diff --git a/code/UI/ListBeExchange/entry/src/test/List.test.ets b/code/UI/ListBeExchange/entry/src/test/List.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..f1186b1f53c3a70930921c5dbd1417332bec56c9 --- /dev/null +++ b/code/UI/ListBeExchange/entry/src/test/List.test.ets @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import localUnitTest from './LocalUnit.test'; + +export default function testsuite() { + localUnitTest(); +} \ No newline at end of file diff --git a/code/UI/ListBeExchange/entry/src/test/LocalUnit.test.ets b/code/UI/ListBeExchange/entry/src/test/LocalUnit.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..7fc57c77dbf76d8df08a2b802a55b948e3fcf968 --- /dev/null +++ b/code/UI/ListBeExchange/entry/src/test/LocalUnit.test.ets @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; + +export default function localUnitTest() { + describe('localUnitTest', () => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + }); + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + }); + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + }); + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }); + it('assertContain', 0, () => { + // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. + let a = 'abc'; + let b = 'b'; + // Defines a variety of assertion methods, which are used to declare expected boolean conditions. + expect(a).assertContain(b); + expect(a).assertEqual(a); + }); + }); +} \ No newline at end of file diff --git a/code/UI/ListBeExchange/hvigor/hvigor-config.json5 b/code/UI/ListBeExchange/hvigor/hvigor-config.json5 new file mode 100644 index 0000000000000000000000000000000000000000..06b2783670a348f95533b352c1ceda909a842bbc --- /dev/null +++ b/code/UI/ListBeExchange/hvigor/hvigor-config.json5 @@ -0,0 +1,22 @@ +{ + "modelVersion": "5.0.0", + "dependencies": { + }, + "execution": { + // "analyze": "normal", /* Define the build analyze mode. Value: [ "normal" | "advanced" | false ]. Default: "normal" */ + // "daemon": true, /* Enable daemon compilation. Value: [ true | false ]. Default: true */ + // "incremental": true, /* Enable incremental compilation. Value: [ true | false ]. Default: true */ + // "parallel": true, /* Enable parallel compilation. Value: [ true | false ]. Default: true */ + // "typeCheck": false, /* Enable typeCheck. Value: [ true | false ]. Default: false */ + }, + "logging": { + // "level": "info" /* Define the log level. Value: [ "debug" | "info" | "warn" | "error" ]. Default: "info" */ + }, + "debugging": { + // "stacktrace": false /* Disable stacktrace compilation. Value: [ true | false ]. Default: false */ + }, + "nodeOptions": { + // "maxOldSpaceSize": 8192 /* Enable nodeOptions maxOldSpaceSize compilation. Unit M. Used for the daemon process. Default: 8192*/ + // "exposeGC": true /* Enable to trigger garbage collection explicitly. Default: true*/ + } +} diff --git a/code/UI/ListBeExchange/hvigorfile.ts b/code/UI/ListBeExchange/hvigorfile.ts new file mode 100644 index 0000000000000000000000000000000000000000..2a5e543f190732c159beb574dfc9fa37bc94e156 --- /dev/null +++ b/code/UI/ListBeExchange/hvigorfile.ts @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { appTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: appTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ +} diff --git a/code/UI/ListBeExchange/oh-package.json5 b/code/UI/ListBeExchange/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..93f097993a458e967d6d5239ea0580e79b5d6998 --- /dev/null +++ b/code/UI/ListBeExchange/oh-package.json5 @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +{ + "modelVersion": "5.0.0", + "description": "Please describe the basic information.", + "dependencies": { + }, + "devDependencies": { + "@ohos/hypium": "1.0.19", + "@ohos/hamock": "1.0.0" + } +} diff --git a/code/UI/ListBeExchange/ohosTest.md b/code/UI/ListBeExchange/ohosTest.md new file mode 100644 index 0000000000000000000000000000000000000000..dc59094df4366d6bca653666e3fd8425c1f61f3a --- /dev/null +++ b/code/UI/ListBeExchange/ohosTest.md @@ -0,0 +1,8 @@ +# 列表项交换案例 测试用例归档 + +## 用例表 + +| 测试功能 | 预置条件 | 输入 | 预期输出 | 是否自动 |测试结果| +|-----------------------|--------------------|----------------|-------------------|------|--------------------------------| +| 拖动实现列表项之间的位置交换 | 成功拉起应用,进入案例页面 | 长按列表项,上下拖动 | 当前选中的列表项可以插入到目标位置 | 否 |Pass| +| 侧滑,显示删除按钮,点击删除按钮删除列表项 | 成功拉起应用,进入案例页面 | 列表项向左滑动,点击删除按钮 | 该列表项被删除 | 否 |Pass|