HarmonyOS List动画效果

如果我想实现List列表组件 新增 移除 条目时,增加动画效果,请问需要怎么实现呢?有没有什么demo可以参考呢?

HarmonyOS
2024-12-25 07:09:31
浏览
收藏 0
回答 1
回答 1
按赞同
/
按时间
put_get

index.ets

import { LazyDataSource } from '../model/LazyDataSource'
@Observed
class Item {
  constructor(data: string
  ) {
    this.data = data
  }
  public data: string
  public inDeleteArea: boolean = false
  public offsetY: number = 0
}
@Component
struct CardItem {
  @ObjectLink item: Item
  build() {
    Text('item' + this.item.data)
      .fontSize(16)
      .width("100%")
      .height(74)
      .fontSize(16)
      .textAlign(TextAlign.Center)
      .borderRadius(10)
      .backgroundColor(0xFFFFFF)
      .translate({ y: this.item.offsetY })
  }
}
@Component
struct MySwipeActionItem2 {
  @ObjectLink item: Item
  @State angle: number = 0
  @State angle2: number = 0
  @State scaleDelItem: number = 1
  private selfSize: SizeResult = { width: 0, height: 0 }
  private ITEM_SIZE: number = 40
  private ITEM_SPACE: number = 8
  private SELF_HEIGHT: number = 74
  private SELF_PADDING: number = 12
  private onDelete: () => void = () => {
  }

  @Styles
  buttonStyle() {
    .borderRadius(this.ITEM_SIZE / 2)
    .height(this.ITEM_SIZE)
    .width(this.ITEM_SIZE)
  }
  @Builder
  builder() {
    Stack(){
      Image($r('sys.media.ohos_ic_public_share'))
        .width('70%')
        .height('70%')
        .fillColor($r('sys.color.ohos_id_color_primary_contrary'))
    }
    .buttonStyle()
    .backgroundColor($r('sys.color.ohos_id_color_palette11'))
    .flexGrow(this.item.inDeleteArea ? 0 : 1)
    .visibility(this.item.inDeleteArea ? Visibility.Hidden : Visibility.Visible)
    Stack() {
      Image($r('app.media.app_icon'))
        .width('70%')
        .height('70%')
        .rotate({
          x: 0,
          y: 0,
          z: 1,
          centerX: 20,
          centerY: 4,
          angle: this.angle
        })
        .animation({ curve: Curve.Friction, duration: 200 })
      Image($r('app.media.app_icon'))
        .width('70%')
        .height('70%')
        .rotate({
          x: 0,
          y: 0,
          z: 1,
          centerX: 30,
          centerY: 13,
          angle: -this.angle2
        })
        .animation({ curve: Curve.Friction, duration: 200 })
    }
    .buttonStyle()
    .backgroundColor($r('sys.color.ohos_id_color_warning'))
    .scale({ x: this.scaleDelItem, y: this.scaleDelItem })
    .animation({ curve: Curve.Friction, duration: 200 })
    .onClick(() => {
      if (this.onDelete) {
        this.onDelete()
      }
    })
  }
  build() {
    this.builder()
  }
  onMeasureSize(selfLayoutInfo: GeometryInfo, children: Array<Measurable>, constraint: ConstraintSizeOptions): SizeResult {
    children.forEach((value) => {
      value.measure({ maxWidth: this.ITEM_SIZE, maxHeight: this.ITEM_SIZE, minWidth: 0, minHeight: 0 })
    })
    let width: number = (this.ITEM_SIZE + this.ITEM_SPACE) * children.length + this.SELF_PADDING * 2
    width = Math.max(width, constraint.minWidth as number)
    width = Math.min(width, constraint.maxWidth as number)
    this.selfSize = { height: this.SELF_HEIGHT, width: width }
    return this.selfSize
  }
  onPlaceChildren(selfLayoutInfo: GeometryInfo, children: Array<Layoutable>, constraint: ConstraintSizeOptions): void {
    let a = 0
    let y = (this.selfSize.height - this.ITEM_SIZE) / 2
    let w = this.selfSize.width - this.SELF_PADDING * 2 - (this.ITEM_SIZE + this.ITEM_SPACE) * children.length
    if (!this.item.inDeleteArea) {
      let dw = w * 5 / 6 / children.length;
      let x0 = this.SELF_PADDING + this.ITEM_SPACE / 2 + dw
      let k = this.ITEM_SIZE + this.ITEM_SPACE + dw
      children.forEach((value, index) => {
        value.layout({ x: x0 + index * k, y: y })
        a = x0 + index * k
      })
      this.angle = (this.selfSize.width - 120) / 56 * 12
      this.angle2 = 0;
      this.scaleDelItem = 1 + ((this.selfSize.width - 120) / 56) / 20
    } else if (children.length > 1) {
      let k = ((this.selfSize.width - this.ITEM_SIZE - this.ITEM_SPACE) / 2 - this.SELF_PADDING) / (children.length - 1);
      let x0 = this.SELF_PADDING + (k - this.ITEM_SIZE) / 2
      children.forEach((value, index) => {
        value.layout({ x: x0 + index * k, y: y })
      })
      this.angle = 17
      this.angle2 = 10
      this.scaleDelItem = 1.10
    } else {
      children.forEach((value, index) => {
        value.layout({ x: (this.selfSize.width - this.ITEM_SIZE) / 2, y: y })
      })
      this.angle = 17
      this.angle2 = 10
      this.scaleDelItem = 1.10
    }
  }
}
@Entry
@Component
struct ListItemExample2 {
  @State dataSource: LazyDataSource<Item> = new LazyDataSource()
  @State inDeleteArea: Array<Boolean> = []
  @State scaleItem: number = 0.75
  @State offsetY: number = 0
  @State EndOffset: number = 0
  @State CurrentIndex: string = 'undefined'
  private scroller: Scroller = new Scroller()
  onDelete(item: Item) {
    this.EndOffset = 0
    let index = this.dataSource.indexOf(item)
    animateTo({
      duration: 0,
    }, () => {
      this.scaleItem = 0.75
      this.dataSource.deleteItem(item)
    })
    for (let i = 0; i < this.dataSource.totalCount(); ++i) {
      const element = this.dataSource.getData(i)
      if (i < index) {
        element.offsetY = this.EndOffset
      } else {
        element.offsetY = 60 + this.EndOffset
      }
    }
    for (let i = 0; i < this.dataSource.totalCount(); ++i) {
      const element = this.dataSource.getData(i)
      animateTo({
        curve: Curve.Friction,
        duration: 350,
        delay: Math.abs(index - i) * 16.67,
      }, () => {
        element.offsetY = 0
      })
    }
  }
  onAdd(item: Item){
    animateTo({
      duration: 0,
    }, () => {
      this.scaleItem = 0.75
      this.dataSource.addItem(item)
    })
    for (let i = 0; i < this.dataSource.totalCount(); ++i) {
      const element = this.dataSource.getData(i)
      if (i <= 0) {
        element.offsetY = this.EndOffset
      } else {
        element.offsetY = -60
      }
    }
    for (let i = 0; i < this.dataSource.totalCount(); ++i) {
      const element = this.dataSource.getData(i)
      animateTo({
        curve: Curve.Friction,
        duration: 350,
        delay: Math.abs(0 - i) * 16.67,
      }, () => {
        element.offsetY = 0
      })
    }
  }
  @Builder
  itemEnd(item: Item) {
    MySwipeActionItem2({ item: item as Item, onDelete: () => {
      this.onDelete(item)
    } })
  }
  addItem(item:Item){

    this.onAdd(item)
  }
  build() {
    Column() {
      Button('添加').onClick(()=>{
        this.addItem(new Item('新增的'))
      })
      List({ space: 10, scroller: this.scroller }) {
        LazyForEach(this.dataSource, (item: Item) => {
          ListItem() {
            CardItem({ item: item })
          }
          // .clip(true)
          .zIndex(Number(item.data))
          .transition(TransitionEffect.asymmetric(
            TransitionEffect.IDENTITY,
            TransitionEffect.scale({ x: this.scaleItem, y: this.scaleItem })
              .animation({ duration: 400, curve: Curve.Friction })
              .combine(TransitionEffect.OPACITY.animation({ duration: 100, curve: Curve.Sharp, delay: 100 }))
          ))
          .swipeAction({
            end: {
              builder: this.itemEnd(item),
              onAction: () => {
                this.onDelete(item)
                console.log("offset:" + this.scroller.currentOffset().yOffset)
              },
              actionAreaDistance: 56,
              onEnterActionArea: () => {
                animateTo({ duration: 200, curve: Curve.Friction }, () => {
                  item.inDeleteArea = true;
                })
              },
              onExitActionArea: () => {
                animateTo({ duration: 200, curve: Curve.Friction }, () => {
                  item.inDeleteArea = false;
                })
              }
            }
          })
        }, (item: Item) => item.data)
      }
      .scrollBar(BarState.Off)
      .onScroll((scrollOffset: number, scrollState: ScrollState) => {
        this.EndOffset = scrollOffset
      })
    }
    // .padding(10)
    .backgroundColor('#0D182431')
    .width('100%')
    .height('100%')
  }
  aboutToAppear() {
    for (let i = 0; i < 50; i++) {
      this.dataSource.pushItem(new Item(i.toString()))
    }
  }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.
  • 131.
  • 132.
  • 133.
  • 134.
  • 135.
  • 136.
  • 137.
  • 138.
  • 139.
  • 140.
  • 141.
  • 142.
  • 143.
  • 144.
  • 145.
  • 146.
  • 147.
  • 148.
  • 149.
  • 150.
  • 151.
  • 152.
  • 153.
  • 154.
  • 155.
  • 156.
  • 157.
  • 158.
  • 159.
  • 160.
  • 161.
  • 162.
  • 163.
  • 164.
  • 165.
  • 166.
  • 167.
  • 168.
  • 169.
  • 170.
  • 171.
  • 172.
  • 173.
  • 174.
  • 175.
  • 176.
  • 177.
  • 178.
  • 179.
  • 180.
  • 181.
  • 182.
  • 183.
  • 184.
  • 185.
  • 186.
  • 187.
  • 188.
  • 189.
  • 190.
  • 191.
  • 192.
  • 193.
  • 194.
  • 195.
  • 196.
  • 197.
  • 198.
  • 199.
  • 200.
  • 201.
  • 202.
  • 203.
  • 204.
  • 205.
  • 206.
  • 207.
  • 208.
  • 209.
  • 210.
  • 211.
  • 212.
  • 213.
  • 214.
  • 215.
  • 216.
  • 217.
  • 218.
  • 219.
  • 220.
  • 221.
  • 222.
  • 223.
  • 224.
  • 225.
  • 226.
  • 227.
  • 228.
  • 229.
  • 230.
  • 231.
  • 232.
  • 233.
  • 234.
  • 235.
  • 236.
  • 237.
  • 238.
  • 239.
  • 240.
  • 241.
  • 242.
  • 243.
  • 244.
  • 245.
  • 246.
  • 247.
  • 248.
  • 249.
  • 250.
  • 251.
  • 252.
  • 253.
  • 254.
  • 255.
  • 256.
  • 257.
  • 258.
  • 259.
  • 260.
  • 261.
  • 262.
  • 263.
  • 264.
  • 265.
  • 266.
  • 267.
  • 268.
  • 269.
  • 270.
  • 271.

LazyDataSource.ets

export class LazyDataSource<T> implements IDataSource {
  private elements: T[]
  private listeners: Set<DataChangeListener>
  constructor(elements: T[] = []) {
    this.elements = elements
    this.listeners = new Set()
  }
  public totalCount(): number {
    return this.elements.length
  }
  public getData(index: number): T {
    return this.elements[index]
  }
  public indexOf(item: T): number {
    return this.elements.indexOf(item)
  }
  public pinItem(item: T, index: number): void {
    this.elements.splice(index, 1)
    this.elements.unshift(item)
    this.listeners.forEach(listener => listener.onDataReloaded())
  }
  public addItem(item: T) {
    this.elements.unshift(item)
    this.listeners.forEach(listener => listener.onDataAdd(0))
  }
  public pushItem(item: T) {
    this.elements.push(item)
    this.listeners.forEach(listener => listener.onDataAdd(this.elements.length - 1))
  }
  public insertItem(item: T, index: number) {
    this.elements.splice(index, 0, item)
    this.listeners.forEach(listener => listener.onDataAdd(index))
  }
  public deleteItem(item: T): void {
    const index = this.elements.indexOf(item)
    if (index < 0) {
      return
    }
    this.elements.splice(index, 1)
    this.listeners.forEach(listener => listener.onDataDelete(index))
  }
  public deleteItemByIndex(index: number): void {
    this.elements.splice(index, 1)
    this.listeners.forEach(listener => listener.onDataDelete(index))
  }
  public registerDataChangeListener(listener: DataChangeListener): void {
    this.listeners.add(listener)
  }
  public unregisterDataChangeListener(listener: DataChangeListener): void {
    this.listeners.delete(listener)
  }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
分享
微博
QQ
微信
回复
2024-12-25 10:15:53


相关问题
如何实现list的折叠动画效果
945浏览 • 1回复 待解决
HarmonyOS 动画效果+手势
474浏览 • 1回复 待解决
HarmonyOS 列表动画效果
514浏览 • 1回复 待解决
HarmonyOS list编辑移动效果
469浏览 • 1回复 待解决
HarmonyOS 旋转动画效果
387浏览 • 1回复 待解决
HarmonyOS 动画效果实现
621浏览 • 1回复 待解决
HarmonyOS .scale没有动画效果
358浏览 • 1回复 待解决
HarmonyOS list选中效果实现
279浏览 • 1回复 待解决
HarmonyOS clipShape 动画效果实现
167浏览 • 0回复 待解决
属性动画如何实现宽高动画效果
2412浏览 • 1回复 待解决
如何实现动画转场效果
1363浏览 • 1回复 待解决
HarmonyOS grid拖拽和增删动画效果
533浏览 • 1回复 待解决
HarmonyOS 拍摄录制的动画效果
299浏览 • 1回复 待解决
HarmonyOS 实现按钮长按动画效果
709浏览 • 1回复 待解决
HarmonyOS list中item的交互效果处理
780浏览 • 1回复 待解决
panGesture结合动画实现fling效果
1289浏览 • 1回复 待解决
如何将List的回弹效果改为阴影效果
942浏览 • 1回复 待解决
Tabs 出现/消失转场动画效果
795浏览 • 1回复 待解决
文字动画效果如何实现
2247浏览 • 0回复 待解决
HarmonyOS Text添加动画效果与预期不符
502浏览 • 1回复 待解决
HarmonyOS grid拖拽效果如何添加动画
519浏览 • 1回复 待解决