鸿蒙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.动画效果如下:

相关推荐
yuanManGan6 分钟前
走进Linux的世界:初识操作系统(Operator System)
android·linux·运维
爱笑的眼睛116 分钟前
HarmonyOS 应用开发:深入探索截屏与录屏API的创新实践
华为·harmonyos
叶羽西20 分钟前
Android15跟踪函数调用关系
android
爱笑的眼睛111 小时前
深入探讨HarmonyOS中ListItem的滑动操作实现与优化
华为·harmonyos
消失的旧时光-19432 小时前
webView 的canGoBack/goBack 回退栈
android·webview
SHEN_ZIYUAN2 小时前
Flow 责任链模式图解
android
沐怡旸4 小时前
【底层机制】LeakCanary深度解析:从对象监控到内存泄漏分析的完整技术体系
android·面试
又菜又爱coding4 小时前
Android + Flutter打包出来的APK体积太大
android·flutter
LiuYaoheng4 小时前
【Android】Drawable 基础
android·java
爱笑的眼睛115 小时前
可可图片编辑 HarmonyOS 上架应用分享
华为·harmonyos