HarmonyOS鸿蒙Next中List组件拖拽时动画属性不生效如何解决

HarmonyOS鸿蒙Next中List组件拖拽时动画属性不生效如何解决

【问题现象】

在List列表里进行组件拖拽交换,组件交换过程中想实现动画效果,但是设置的supportAnimation动画属性没生效。

【背景知识】

  1. 拖拽事件指组件被长按后拖拽时触发的事件。
  2. 组合手势由多种单一手势组合而成,通过在GestureGroup中使用不同GestureMod来声明该组合手势的类型,支持连续识别、并行识别和互斥识别三种类型。
  3. 手势处理分为:长按、单击、双击、多次点击、拖动、捏合、旋转、滑动。
  4. Grid支持supportAnimation动画,List组件不支持,可以通过animateTo显式动画接口来指定由于闭包代码导致的状态变化插入过渡动效。
  5. attributeModifier:动态设置组件的属性,支持开发者在属性设置时使用if/else语法,且根据需要使用多态样式设置属性。

【定位思路】

参考官方文档,比对GridList属性(结果如下表)可知,Grid支持supportAnimation动画,List组件不支持,也无相关属性可替代,需要结合拖拽事件、组合手势、动画效果来进行原生开发。

参数名 List是否支持 Grid是否支持 参数描述
supportAnimation × 是否支持动画。当前支持GridItem拖拽动画。默认值:false

【解决方案】

顺序识别手势触发List组件拖拽交换,利用[@ohos.curves](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-curve)动画插值曲线功能,在List组件拖拽交换过程中绑定动画效果,具体实现步骤如下:

  1. 导入动画插值曲线功能模块,对当前拖拽块的阴影设置以及偏移量应用。代码示例如下:

    import curves from '[@ohos](/user/ohos).curves';
    class ListItemModify implements AttributeModifier<ListItemAttribute> {
      public hasShadow: boolean = false
      public scale: number = 1
      public offsetY: number = 0
    
      applyNormalAttribute(instance: ListItemAttribute): void {
        if (this.hasShadow) {
          instance.shadow({
            radius: 70,
            color: '# 15000000',
            offsetX: 0,
            offsetY: 0
          })
          instance.zIndex(1)
        }
        instance.scale({ x: this.scale, y: this.scale })
        instance.translate({ y: this.offsetY })
      }
    }
    
    enum DragSortState {
      IDLE,
      PRESSING,
      MOVING,
      DROPPING,
    }
    
  2. 组件拖拽交换过程中绑定动画效果。代码示例如下:

    // 与下/上一个元素交换,offsetY(实时偏移量)需要 减/加 ITEM_INTV(交换偏移量)
    // 与下/上一个元素交换,dragRefOffset(初始偏移值) 按照交换后的位置重新计算,加/减 ITEM_INTV(交换偏移量)
    // 向下/上拖动距离超过一半时,与下/上一个元素交换位置
    if (this.offsetY > this.ITEM_INTV / 2) {
      animateTo({ curve: curves.interpolatingSpring(0, 1, 400, 38) }, () => {
        this.offsetY -= this.ITEM_INTV
        this.dragRefOffset += this.ITEM_INTV
        this.modify[index].offsetY = this.offsetY
        this.itemMove(index, index + 1)
      })
    } else if (this.offsetY < -this.ITEM_INTV / 2) {
      animateTo({ curve: curves.interpolatingSpring(0, 1, 400, 38) }, () => {
        this.offsetY += this.ITEM_INTV
        this.dragRefOffset -= this.ITEM_INTV
        this.modify[index].offsetY = this.offsetY
        this.itemMove(index, index - 1)
      })
    }
    
  3. 顺序识别手势触发组件拖拽交换,代码示例如下:

    [@Entry](/user/Entry)
    [@Component](/user/Component)
    struct ListItemExample {
      @State private arr: Array<number> = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
      @State dragSortCtrl: DragSortCtrl<number> = new DragSortCtrl<number>(this.arr, 120)
    
      build() {
        Stack() {
          List({ space: 20, initialIndex: 0 }) {
            ForEach(this.arr, (item: number) => {
              ListItem() {
                Text('' + item)
                  .width('100%')
                  .height(100)
                  .fontSize(16)
                  .textAlign(TextAlign.Center)
                  .backgroundColor(0xFFFFFF)
              }
              .clip(true)
              .attributeModifier(this.dragSortCtrl.getModify(item))
              .borderRadius(10)
              .margin({ left: 12, right: 12 })
              .gesture(
                // 以下组合手势为顺序识别,当长按手势事件未正常触发时则不会触发拖动手势事件
                // Sequence代表顺序识别,按照手势的注册顺序识别手势,直到所有手势识别成功。
                GestureGroup(GestureMode.Sequence,
                  // 用于触发长按手势事件,触发长按手势的最少手指数为1,最短长按时间为500毫秒
                  LongPressGesture({ repeat: false })
                    .onAction((event?: GestureEvent) => {
                      this.dragSortCtrl.onLongPress(item)
                    }),
                  // 用于触发拖动手势事件,滑动的最小距离为5vp时拖动手势识别成功。
                  PanGesture({ fingers: 1, direction: null, distance: 0 })
                    .onActionUpdate((event: GestureEvent) => {
                      this.dragSortCtrl.onMove(item, event.offsetY)
                    })
                    .onActionEnd((event: GestureEvent) => {
                      this.dragSortCtrl.onDrop(item)
                    })
                )
                  .onCancel(() => {
                    this.dragSortCtrl.onDrop(item)
                  })
              )
            }, (item: number) => item.toString())
          }
        }.width('100%').height('100%').backgroundColor(0xDCDCDC).padding({ top: 5 })
      }
    }
    

【总结】

对列表的列表项进行拖动时,实现其他列表项自动补位和动态排列的效果可采用如下方法:

  1. 为列表或宫格项(item)添加拖拽能力,使能 draggable,并注册onDragStart;
  2. 在 onDragStart 回调中将所拖条目设置visibility为 HIDDEN 状态;
  3. 在列表或宫格项(item)上注册 onDragMove 监听拖起的移动事件;
  4. 拖动过程中,通过 onDragMove 的 event 参数获取到拖拽跟手点坐标;
  5. 计算跟手点坐标与item中线的距离关系,当重合时,启动挤位动效;
  6. Item布局信息可通过 componentUtils API获取到;
  7. 挤位动效通过animateTo来改变datasource里的index,触发list的排序动效;
  8. 落位动效可通过自定义动效完成。

更多关于HarmonyOS鸿蒙Next中List组件拖拽时动画属性不生效如何解决的实战教程也可以访问 https://www.itying.com/category-93-b0.html

1 回复

更多关于HarmonyOS鸿蒙Next中List组件拖拽时动画属性不生效如何解决的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


List组件不支持supportAnimation动画属性

List组件不支持supportAnimation动画属性,需通过animateTo显式动画接口实现拖拽动画效果。步骤如下:

  1. 导入动画插值曲线模块,设置拖拽块的阴影和偏移量。
  2. 在拖拽交换过程中绑定动画效果,使用animateTo接口。
  3. 通过顺序识别手势触发拖拽交换,使用GestureGroupLongPressGesturePanGesture组合手势。

代码示例

import curves from '@ohos.curves';

class ListItemModify implements AttributeModifier<ListItemAttribute> {
  public hasShadow: boolean = false;
  public scale: number = 1;
  public offsetY: number = 0;

  applyNormalAttribute(instance: ListItemAttribute): void {
    if (this.hasShadow) {
      instance.shadow({ radius: 70, color: '#15000000', offsetX: 0, offsetY: 0 });
      instance.zIndex(1);
    }
    instance.scale({ x: this.scale, y: this.scale });
    instance.translate({ y: this.offsetY });
  }
}

enum DragSortState {
  IDLE,
  PRESSING,
  MOVING,
  DROPPING,
}

if (this.offsetY > this.ITEM_INTV / 2) {
  animateTo({ curve: curves.interpolatingSpring(0, 1, 400, 38) }, () => {
    this.offsetY -= this.ITEM_INTV;
    this.dragRefOffset += this.ITEM_INTV;
    this.modify[index].offsetY = this.offsetY;
    this.itemMove(index, index + 1);
  });
} else if (this.offsetY < -this.ITEM_INTV / 2) {
  animateTo({ curve: curves.interpolatingSpring(0, 1, 400, 38) }, () => {
    this.offsetY += this.ITEM_INTV;
    this.dragRefOffset -= this.ITEM_INTV;
    this.modify[index].offsetY = this.offsetY;
    this.itemMove(index, index - 1);
  });
}

@Entry
@Component
struct ListItemExample {
  @State private arr: Array<number> = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
  @State dragSortCtrl: DragSortCtrl<number> = new DragSortCtrl<number>(this.arr, 120);

  build() {
    Stack() {
      List({ space: 20, initialIndex: 0 }) {
        ForEach(this.arr, (item: number) => {
          ListItem() {
            Text('' + item)
              .width('100%')
              .height(100)
              .fontSize(16)
              .textAlign(TextAlign.Center)
              .backgroundColor(0xFFFFFF);
          }
          .clip(true)
          .attributeModifier(this.dragSortCtrl.getModify(item))
          .borderRadius(10)
          .margin({ left: 12, right: 12 })
          .gesture(
            GestureGroup(GestureMode.Sequence,
              LongPressGesture({ repeat: false })
                .onAction((event?: GestureEvent) => {
                  this.dragSortCtrl.onLongPress(item);
                }),
              PanGesture({ fingers: 1, direction: null, distance: 0 })
                .onActionUpdate((event: GestureEvent) => {
                  this.dragSortCtrl.onMove(item, event.offsetY);
                })
                .onActionEnd((event: GestureEvent) => {
                  this.dragSortCtrl.onDrop(item);
                })
            )
            .onCancel(() => {
              this.dragSortCtrl.onDrop(item);
            })
          );
        }, (item: number) => item.toString());
      }
    }.width('100%').height('100%').backgroundColor(0xDCDCDC).padding({ top: 5 });
  }
}
回到顶部