鸿蒙NEXT开发动画案例9

1.创建空白项目

2.Page文件夹下面新建Spin.ets文件,代码如下:

复制代码
interface GridItemConfig {
  translate:Translate
}

interface Translate {
    x?: number;
    y?: number;
}

/**
 * TODO SpinKit动画组件(重构版)
 * author: 鸿蒙布道师
 * since: 2025/05/15
 */
@ComponentV2
export struct SpinNine {
  @Require @Param spinSize: number = 36;
  @Require @Param spinColor: ResourceColor;

  // 动画相关状态
  @Local scaleSize: number = this.spinSize * 0.75;
  @Local tran1: number = 0;
  private oldSize: number = this.spinSize;

  aboutToAppear(): void {
    this.oldSize = this.spinSize;
    this.scaleSize = this.spinSize * 0.75;
  }

  build() {
    Stack() {
      Grid() {
        ForEach(this.getGridItems(), (item:GridItemConfig, index) => {
          GridItem() {
            Canvas()
              .chunkStyle()
          }
          .translate(item.translate)
        });
      }
      .rowsTemplate('1fr 1fr 1fr')
      .columnsTemplate('1fr 1fr 1fr')
      .width(this.scaleSize)
      .height(this.scaleSize)
    }
    .width(this.oldSize)
    .height(this.oldSize)
    .alignContent(Alignment.Center)
    .onAppear(() => {
      this.startAnimation();
    })
  }

  // 获取 GridItem 配置数据
  private getGridItems():GridItemConfig[] {
    return [
      { translate: { x: this.tran1 } },
      { translate: { x: this.tran1 } },
      { translate: { y: this.tran1 } },
      { translate: { y: -this.tran1 } },
      { translate: {} }, // 中心无位移
      { translate: { y: this.tran1 } },
      { translate: { y: -this.tran1 } },
      { translate: { x: -this.tran1 } },
      { translate: { x: -this.tran1 } },
    ];
  }

  // 启动动画
  private startAnimation() {
    this.getUIContext().keyframeAnimateTo({ iterations: -1, delay: 0 }, [
      {
        duration: 600,
        curve: Curve.EaseInOut,
        event: () => {
          this.tran1 = 0;
          this.scaleSize = this.oldSize * 1.1;
        }
      },
      {
        duration: 600,
        curve: Curve.EaseIn,
        event: () => {
          this.scaleSize = this.oldSize * 0.75;
          this.tran1 = 0;
        }
      }
    ]);
  }

  @Styles
  chunkStyle(){
    .width(this.oldSize * 0.25)
    .height(this.oldSize * 0.25)
    .backgroundColor(this.spinColor)
    .shadow(ShadowStyle.OUTER_DEFAULT_XS)
  }
}
复制代码
代码如下:
TypeScript 复制代码
interface GridItemConfig {
  translate:Translate
}

interface Translate {
    x?: number;
    y?: number;
}

/**
 * TODO SpinKit动画组件(重构版)
 * author: 鸿蒙布道师
 * since: 2025/05/15
 */
@ComponentV2
export struct SpinNine {
  @Require @Param spinSize: number = 36;
  @Require @Param spinColor: ResourceColor;

  // 动画相关状态
  @Local scaleSize: number = this.spinSize * 0.75;
  @Local tran1: number = 0;
  private oldSize: number = this.spinSize;

  aboutToAppear(): void {
    this.oldSize = this.spinSize;
    this.scaleSize = this.spinSize * 0.75;
  }

  build() {
    Stack() {
      Grid() {
        ForEach(this.getGridItems(), (item:GridItemConfig, index) => {
          GridItem() {
            Canvas()
              .chunkStyle()
          }
          .translate(item.translate)
        });
      }
      .rowsTemplate('1fr 1fr 1fr')
      .columnsTemplate('1fr 1fr 1fr')
      .width(this.scaleSize)
      .height(this.scaleSize)
    }
    .width(this.oldSize)
    .height(this.oldSize)
    .alignContent(Alignment.Center)
    .onAppear(() => {
      this.startAnimation();
    })
  }

  // 获取 GridItem 配置数据
  private getGridItems():GridItemConfig[] {
    return [
      { translate: { x: this.tran1 } },
      { translate: { x: this.tran1 } },
      { translate: { y: this.tran1 } },
      { translate: { y: -this.tran1 } },
      { translate: {} }, // 中心无位移
      { translate: { y: this.tran1 } },
      { translate: { y: -this.tran1 } },
      { translate: { x: -this.tran1 } },
      { translate: { x: -this.tran1 } },
    ];
  }

  // 启动动画
  private startAnimation() {
    this.getUIContext().keyframeAnimateTo({ iterations: -1, delay: 0 }, [
      {
        duration: 600,
        curve: Curve.EaseInOut,
        event: () => {
          this.tran1 = 0;
          this.scaleSize = this.oldSize * 1.1;
        }
      },
      {
        duration: 600,
        curve: Curve.EaseIn,
        event: () => {
          this.scaleSize = this.oldSize * 0.75;
          this.tran1 = 0;
        }
      }
    ]);
  }

  @Styles
  chunkStyle(){
    .width(this.oldSize * 0.25)
    .height(this.oldSize * 0.25)
    .backgroundColor(this.spinColor)
    .shadow(ShadowStyle.OUTER_DEFAULT_XS)
  }
}

3.修改Index.ets文件,代码如下:

复制代码
import { SpinEight } from './Spin';

@Entry
@Component
struct Index {
  @State message: string = 'Hello World';

  build() {
    Column() {
      SpinNine({
        spinSize: 60,
        spinColor: '#FF0000'
      })
    }
    .alignItems(HorizontalAlign.Center)
    .justifyContent(FlexAlign.Center)
    .height('100%')
    .width('100%')
  }
}
复制代码
代码如下:
TypeScript 复制代码
import { SpinEight } from './Spin';

@Entry
@Component
struct Index {
  @State message: string = 'Hello World';

  build() {
    Column() {
      SpinNine({
        spinSize: 60,
        spinColor: '#FF0000'
      })
    }
    .alignItems(HorizontalAlign.Center)
    .justifyContent(FlexAlign.Center)
    .height('100%')
    .width('100%')
  }
}

4.运行项目,登录华为账号,需进行签名

5.动画效果如下:

相关推荐
用户4639897543215 分钟前
Harmony os——AbilityStage 组件管理器:我理解的 Module 级「总控台」
harmonyos
明君8799715 分钟前
Flutter 图纸标注功能的实现:踩坑与架构设计
android·ios
成都大菠萝23 分钟前
Android Auto开发(3)-Audio Integration
android
成都大菠萝30 分钟前
Android Auto开发(5)-Audio Integration
android
用户4639897543234 分钟前
Harmony os——UIAbility 组件基本用法:启动页、Context、终止与拉起方信息全流程
harmonyos
用户4639897543239 分钟前
Harmony os——启动应用内的 UIAbility:跨 Ability 跳转、回传结果 & 指定页面全流程
harmonyos
用户4639897543240 分钟前
Harmony os——UIAbility 组件生命周期|我按自己的理解梳了一遍
harmonyos
江东小bug王1 小时前
深入理解 UINavigationController:生命周期、动画优化与性能调优
ios
泡沫·1 小时前
7.LAMPLNMP 最佳实践
android
码码宁1 小时前
六个故事搞懂Fragment 故事1-初识Fragment - NewsHub的模块化革命
android