Advertisement

鸿蒙5.0开发进阶:列表项交换案例

阅读量:

往期鸿蒙全套实战文章必看:

[鸿蒙系统的核心技术要点]( "鸿蒙开发核心知识点, 快速掌握所需知识"))

全新版本!鸿蒙HarmonyOS Next应用开发实战学习路线

鸿蒙HarmonyOS NEXT开发技术最详细的系统学习路径指南

鸿蒙应用开发实战项目全面开启!深入学习这篇文章即可掌握相关知识(所有参与项目的同学均可获取源代码支持)


介绍

在此案例中,我们利用List组件、手势组件GestureGroup、滑动操作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, 该属性的取值对应相应的自定义属性对象

复制代码
        1.  List() {

    
        2. ForEach(this.appInfoList, (item: AppInfo) => {
    
        3.    ListItem() {
    
        4.      DeductionView({ app: item })
    
        5.    }
    
        6.    .zIndex(this.currentData === item ? 2 : 1)
    
        7.    .swipeAction({ end: this.deleteBuilder(item) })
    
        8.    .transition(TransitionEffect.OPACITY)
    
        9.    .attributeModifier(this.listExchangeCtrl.getModifier(item)) //动态设置组件的属性方法, 参数为属性修改器
    
        10.    .gesture(
    
        11.      // 以下组合手势为顺序识别,当长按手势事件未正常触发时,则不会出发拖动手势事件
    
        12.      GestureGroup(GestureMode.Sequence,
    
        13.        LongPressGesture()
    
        14.          .onAction((event: GestureEvent) => {
    
        15.            this.currentData = item;
    
        16.            this.isLongPress = true;
    
        17.            this.listExchangeCtrl.onLongPress(item);
    
        18.          }),
    
        19.        PanGesture()
    
        20.          .onActionUpdate((event: GestureEvent) => {
    
        21.            this.listExchangeCtrl.onMove(item, event.offsetY);
    
        22.          })
    
        23.          .onActionEnd((event: GestureEvent) => {
    
        24.            this.listExchangeCtrl.onDrop(item);
    
        25.          })
    
        26.      ).onCancel(() => {
    
        27.        if (!this.isLongPress) {
    
        28.          return;
    
        29.        }
    
        30.        this.listExchangeCtrl.onDrop(item);
    
        31.      })
    
        32.    )
    
        33. }, (item: AppInfo) => JSON.stringify(item))
    
        34.  }
    
        35.  .divider({ strokeWidth: '1px', color: 0xeaf0ef })
    
        36.  .scrollBar(BarState.Off)
    
        37.  .padding({ left: 10, right: 10 })
    
        38.  .backgroundColor(Color.White)
    
        39.  .width('100%')

对列表项进行长按操作并解析其对应的长按手势类型,并触发onLongPress函数方法以设置相关属性参数;根据动画效果需求生成动画过渡过程并完成动画效果应用

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

在拖动过程中被 PanGesture手势的 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)

还没有任何评论哟~