Advertisement

鸿蒙APP实战案例分析——列表项交换案例

阅读量:

📌往期推文全新看点(文中附带最新·鸿蒙全栈学习笔记)

📃 鸿蒙(HarmonyOS)北向开发知识点记录~

📃 鸿蒙(OpenHarmony)南向开发保姆级知识点汇总~

在评估"鸿蒙应用的构建"与"鸿蒙系统的研发"哪一个更具发展潜力时?

📃 嵌入式开发适不适合做鸿蒙南向开发?看完这篇你就了解了~

在大前端开发领域中选择鸿蒙开发是否带来好运或带来挑战?

📃 鸿蒙岗位需求突增!移动端、PC端、IoT到底该怎么选?

分享一次鸿蒙开发岗位面试的经历与感受~

📃 持续更新中……


介绍

本案例基于List组件这一核心组件,在动态手势交互中结合了GESTURE_GROUP类型,并运用swipeAction属性和attributeModifier属性等技术手段来支持灵活的数据操作功能。

效果图预览

使用说明

  1. 打开页面后,请对列表中的某一项进行双击,并开始拖拽操作。当拖拽距离超过该列表项当前高度的一半时,默认情况下会交换位置。
  2. 对于当前的某条目,在其左侧发生滑动时(或滑动方向为左),系统将弹出一个删除按钮。点击该按钮后即可永久移除该条目。

实现思路

在操作中对流程进行优化时, 首先会对该流程进行初始化, 集成一个名为modifier的数组, 用于集成自定义属性组件. 该模块将根据组合手势GestureGroup来管理自定义属性的值, 并利用attributeModifier绑定机制将这些信息动态加载到相应的位置. 此外, 为了完成组件生命周期管理功能, 将对被禁用状态下的组件进行处理: 通过swipeAction绑定机制将被禁用状态标记为已关闭状态; 当滑动至左侧时, 将显示该被禁用的状态; 点击待编辑列表中的任意一项时, 将执行对应的删除操作.

创建一个数组,并将自定义属性实体注入其中;每个自定义属性实体将映射到一个列表项

复制代码
     constructor(deductionData: Array<T>) {
       this.deductionData = deductionData;
       this.modifier = new Array<ListItemModifier>();
       deductionData.forEach(() => {
     this.modifier.push(new ListItemModifier());
       })
     }
     /** * 通过实现AttributeModifier接口,自定义属性修改器
      * 将拖拽排序相关样式封装成属性修改器,可以方便移植
      */
     export class ListItemModifier implements AttributeModifier<ListItemAttribute> {
      public hasShadow: boolean = false;
      public isDeleted: boolean = false;
      public scale: number = 1;
      public offsetY: number = 0;
      public offsetX: number = 0;
      public opacity: number = 1;
      public static instance: ListItemModifier | null = null;
    
      public static getInstance(): ListItemModifier {
    if (!ListItemModifier.instance) {
      ListItemModifier.instance = new ListItemModifier();
    }
    return ListItemModifier.instance;
      }
    
      /** * 定义组件普通状态时的样式
       * @param instance
       */
      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 });
      }
    }

首先进行属性attributeModifier的绑定以及手势组GestureGroup的结合。其中,属性attributeModifier的值指定为相应的自定义属性对象。

复制代码
    // 列表区域
    List() {
      ForEach(this.appInfoList, (item: ListInfo) => {
    ListItem() {
      DeductionView({ listItemInfo: item })
    }
    .zIndex(this.currentData === item ? 2 : 1) // 层级属性
    .swipeAction({ end: this.deleteBuilder(item) }) // 用于设置ListItem的划出组件
    .transition(TransitionEffect.OPACITY)
    .attributeModifier(this.listExchangeCtrl.getModifier(item)) //动态设置组件的属性方法, 参数为属性修改器
    .gesture(
      // 以下组合手势为顺序识别,当长按手势事件未正常触发时,则不会出发拖动手势事件
      GestureGroup(GestureMode.Sequence,
        // 长按
        LongPressGesture()
          .onAction((event: GestureEvent) => {
            this.currentData = 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);
          })
      ).onCancel(() => {
        if (!this.isLongPress) {
          return;
        }
        this.listExchangeCtrl.onDrop(item);
      })
    )
      }, (item: ListInfo) => 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%')

点击列表项并利用LongPressGesture识别长按手势后调用onLongPress函数方法以修改该列表项的scale、shadow、zIndex和opacity属性,并通过animateTo实现动画效果

复制代码
     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
       })
     }

调用changeItem方法完成列表项交换功能,在PanGestureRecognizer手势实现中,在onActionUpdate函数块中监听拖拽操作的纵向平移量变化情况,并在接收到该数据后,在onMove函数中根据平移幅度大小判断是否需要调用changeItem函数完成数据交互。

复制代码
     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: 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<T> = this.deductionData.splice(index, 1);
       this.deductionData.splice(newIndex, 0, tmp[0]);
       const tmp2: Array<ListItemModifier> = this.modifier.splice(index, 1);
       this.modifier.splice(newIndex, 0, tmp2[0]);
     }

使用swipeAction属性将删除按钮组件与之关联,并通过点击列表项左侧的删除按钮显示对应的删除组件。随后点击该处的删除按钮后将导致对应的列表项被移除。

复制代码
    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类型
    |---model
    |   |---ListInfo.ets                         // 列表项信息
    |   |---AttributeModifier.ets                // 属性对象
    |   |---ListExchangeCtrl.ets                 // 列表项交换
    |   |---MockData.ets                         // 模拟数据
    |---view
    |   |---ListExchangeView.ets                 // 视图层-应用主页面

全部评论 (0)

还没有任何评论哟~