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

相关推荐
大锅盖11 小时前
围绕夜航深蓝与霓虹青的跨境画卷构建 HarmonyOS ArkUI 出境旅行向导平台
华为·harmonyos
骐骥110 小时前
学习:使用postMessage()建立应用侧与前端页面的数据通道
harmonyos·鸿蒙·jsbridge·postmessage·数据通道
_祝你今天愉快10 小时前
从驱动来分析服务的添加过程
android
Dovis(誓平步青云)12 小时前
折叠屏悬停看视频,上半屏和下半屏应该各做什么
android·java·服务器·开发语言·安全·音视频
思盛iOS签名上架13 小时前
App苹果签名有什么作用?
macos·ios·cocoa
alexhilton13 小时前
让架构边界变成可执行的测试
android·kotlin·android jetpack
hai_android15 小时前
SendChannel 与 ReceiveChannel 通信机制
android
CocoaKier16 小时前
Facebook广告后台收不到Adjust回传数据,我们排查了一个月终于找到了根因
ios·facebook
HwJack2016 小时前
鸿蒙开发ArkData 全景与选型决策:三种存储到底用哪个
华为·harmonyos
数据治理自习室17 小时前
AI 应用评测体系(GraphRAG)
android·大数据·人工智能·kotlin