1.创建空白项目


2.Page文件夹下面新建Spin.ets文件,代码如下:
/**
* TODO SpinKit动画组件 - 双粒子旋转缩放动画
* author: CSDN-鸿蒙布道师
* since: 2025/05/08
*/
@ComponentV2
export struct SpinFour {
// 参数定义
@Require @Param spinSize: number = 36;
@Require @Param spinColor: ResourceColor = '#209ED8';
// 局部状态
@Local x1: number = 0;
@Local y1: number = 0;
@Local scale1: number = 1;
@Local angle1: number = 0;
@Local x2: number = this.spinSize * 0.65;
@Local y2: number = this.spinSize * 0.65;
@Local scale2: number = 1;
@Local angle2: number = 0;
aboutToAppear(): void {
this.x2 = this.spinSize * 0.65;
this.y2 = this.spinSize * 0.65;
}
build() {
RelativeContainer() {
Canvas()
.chunkStyle()
.translate({ x: this.x1, y: this.y1 })
.scale({ x: this.scale1, y: this.scale1 })
.rotate({ angle: this.angle1 })
Canvas()
.chunkStyle()
.translate({ x: this.x2, y: this.y2 })
.scale({ x: this.scale2, y: this.scale2 })
.rotate({ angle: this.angle2 })
}
.width(this.spinSize)
.height(this.spinSize)
.onAppear(() => {
this.startAnimation();
});
}
/**
* 启动无限循环的关键帧动画
*/
private startAnimation(): void {
const uiContext = this.getUIContext();
if (!uiContext) return;
const keyframes1 = this.createKeyframes(1);
const keyframes2 = this.createKeyframes(2);
uiContext.keyframeAnimateTo({ iterations: -1, delay: 0 }, keyframes1);
uiContext.keyframeAnimateTo({ iterations: -1, delay: 0 }, keyframes2);
}
/**
* 根据粒子编号创建对应的关键帧动画
* @param particleIndex 粒子索引(1 或 2)
*/
private createKeyframes(particleIndex: 1 | 2): Array<KeyframeState> {
const updatePositionAndScale = (step: number): void => {
if (particleIndex === 1) {
switch (step) {
case 1:
this.scale1 = 0.5;
this.angle1 = -90;
this.x1 = this.spinSize * 0.65;
break;
case 2:
this.scale1 = 1;
this.angle1 = -180;
this.x1 = this.spinSize * 0.65;
this.y1 = this.spinSize * 0.65;
break;
case 3:
this.scale1 = 0.5;
this.angle1 = -270;
this.x1 = 0;
this.y1 = this.spinSize * 0.65;
break;
case 4:
this.scale1 = 1;
this.angle1 = -360;
this.x1 = 0;
this.y1 = 0;
break;
}
} else {
switch (step) {
case 1:
this.scale2 = 0.5;
this.angle2 = -90;
this.x2 = 0;
this.y2 = this.spinSize * 0.65;
break;
case 2:
this.scale2 = 1;
this.angle2 = -180;
this.x2 = 0;
this.y2 = 0;
break;
case 3:
this.scale2 = 0.5;
this.angle2 = -270;
this.x2 = this.spinSize * 0.65;
break;
case 4:
this.scale2 = 1;
this.angle2 = -360;
this.x2 = this.spinSize * 0.65;
this.y2 = this.spinSize * 0.65;
break;
}
}
};
return [
{
duration: 500,
curve: Curve.EaseInOut,
event: (): void => updatePositionAndScale(1),
},
{
duration: 500,
curve: Curve.EaseInOut,
event: (): void => updatePositionAndScale(2),
},
{
duration: 500,
curve: Curve.EaseInOut,
event: (): void => updatePositionAndScale(3),
},
{
duration: 500,
curve: Curve.EaseInOut,
event: (): void => updatePositionAndScale(4),
},
];
}
@Styles
chunkStyle() {
.height(this.spinSize * 0.35)
.width(this.spinSize * 0.35)
.backgroundColor(this.spinColor)
.shadow(ShadowStyle.OUTER_DEFAULT_XS)
}
}
代码如下:
/**
* TODO SpinKit动画组件 - 双粒子旋转缩放动画
* author: CSDN-鸿蒙布道师
* since: 2025/05/08
*/
@ComponentV2
export struct SpinFour {
// 参数定义
@Require @Param spinSize: number = 36;
@Require @Param spinColor: ResourceColor = '#209ED8';
// 局部状态
@Local x1: number = 0;
@Local y1: number = 0;
@Local scale1: number = 1;
@Local angle1: number = 0;
@Local x2: number = this.spinSize * 0.65;
@Local y2: number = this.spinSize * 0.65;
@Local scale2: number = 1;
@Local angle2: number = 0;
aboutToAppear(): void {
this.x2 = this.spinSize * 0.65;
this.y2 = this.spinSize * 0.65;
}
build() {
RelativeContainer() {
Canvas()
.chunkStyle()
.translate({ x: this.x1, y: this.y1 })
.scale({ x: this.scale1, y: this.scale1 })
.rotate({ angle: this.angle1 })
Canvas()
.chunkStyle()
.translate({ x: this.x2, y: this.y2 })
.scale({ x: this.scale2, y: this.scale2 })
.rotate({ angle: this.angle2 })
}
.width(this.spinSize)
.height(this.spinSize)
.onAppear(() => {
this.startAnimation();
});
}
/**
* 启动无限循环的关键帧动画
*/
private startAnimation(): void {
const uiContext = this.getUIContext();
if (!uiContext) return;
const keyframes1 = this.createKeyframes(1);
const keyframes2 = this.createKeyframes(2);
uiContext.keyframeAnimateTo({ iterations: -1, delay: 0 }, keyframes1);
uiContext.keyframeAnimateTo({ iterations: -1, delay: 0 }, keyframes2);
}
/**
* 根据粒子编号创建对应的关键帧动画
* @param particleIndex 粒子索引(1 或 2)
*/
private createKeyframes(particleIndex: 1 | 2): Array<KeyframeState> {
const updatePositionAndScale = (step: number): void => {
if (particleIndex === 1) {
switch (step) {
case 1:
this.scale1 = 0.5;
this.angle1 = -90;
this.x1 = this.spinSize * 0.65;
break;
case 2:
this.scale1 = 1;
this.angle1 = -180;
this.x1 = this.spinSize * 0.65;
this.y1 = this.spinSize * 0.65;
break;
case 3:
this.scale1 = 0.5;
this.angle1 = -270;
this.x1 = 0;
this.y1 = this.spinSize * 0.65;
break;
case 4:
this.scale1 = 1;
this.angle1 = -360;
this.x1 = 0;
this.y1 = 0;
break;
}
} else {
switch (step) {
case 1:
this.scale2 = 0.5;
this.angle2 = -90;
this.x2 = 0;
this.y2 = this.spinSize * 0.65;
break;
case 2:
this.scale2 = 1;
this.angle2 = -180;
this.x2 = 0;
this.y2 = 0;
break;
case 3:
this.scale2 = 0.5;
this.angle2 = -270;
this.x2 = this.spinSize * 0.65;
break;
case 4:
this.scale2 = 1;
this.angle2 = -360;
this.x2 = this.spinSize * 0.65;
this.y2 = this.spinSize * 0.65;
break;
}
}
};
return [
{
duration: 500,
curve: Curve.EaseInOut,
event: (): void => updatePositionAndScale(1),
},
{
duration: 500,
curve: Curve.EaseInOut,
event: (): void => updatePositionAndScale(2),
},
{
duration: 500,
curve: Curve.EaseInOut,
event: (): void => updatePositionAndScale(3),
},
{
duration: 500,
curve: Curve.EaseInOut,
event: (): void => updatePositionAndScale(4),
},
];
}
@Styles
chunkStyle() {
.height(this.spinSize * 0.35)
.width(this.spinSize * 0.35)
.backgroundColor(this.spinColor)
.shadow(ShadowStyle.OUTER_DEFAULT_XS)
}
}
3.修改Index.ets文件,代码如下:
import { SpinFour } from './Spin';
@Entry
@Component
struct Index {
@State message: string = 'Hello World';
build() {
Column() {
SpinFour({
spinSize: 60,
spinColor: '#FF0000'
})
}
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center)
.height('100%')
.width('100%')
}
}
代码如下:
import { SpinFour } from './Spin';
@Entry
@Component
struct Index {
@State message: string = 'Hello World';
build() {
Column() {
SpinFour({
spinSize: 60,
spinColor: '#FF0000'
})
}
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center)
.height('100%')
.width('100%')
}
}
4.运行项目,登录华为账号,需进行签名

5.动画效果如下:




















