HarmonyOS鸿蒙Next中在关键帧动画的不同步骤怎么使用不同motionPath?

HarmonyOS鸿蒙Next中在关键帧动画的不同步骤怎么使用不同motionPath? 试了一些方法没成功,下面是测试代码

@Entry
@Component
struct KeyframeAnimateTo02 {
  @State myScale: number = 1.0;
  uiCtx?: UIContext = undefined;
  @State kTranslateOpt: TranslateOptions = { x: 0, y: 0, z: 0 }
  // 起点
  @State x: number = 0
  @State y: number = 0

  reset() {
    this.x = 0
    this.y = 0
    this.myScale = 1
    this.kTranslateOpt = { x: 0, y: 0, z: 0 }
  }

  keyFrameAnimation() {
    //安全校验
    if (!this.uiCtx) {
      console.info("no uiContext, keyframe failed");
      return;
    }
    // this.myScale = 1;
    // this.kTranslateOpt = { x: 0, y: 0, z: 0 }
    this.uiCtx.keyframeAnimateTo({
      iterations: 1, // 整体播放次数
      expectedFrameRateRange: { min: 10, max: 120, expected: 60, },
      // onFinish: () => this.reset()
    }, [
      {
        duration: 1000, event: () => {
        this.x = 300
      }
      }, // 第1段动画
      {
        duration: 1000, event: () => {
        this.y = 300
      }
      }// 第2段动画
    ]);
  }

  aboutToAppear() {
    this.uiCtx = this.getUIContext?.();
  }

  build() {
    Stack() {
      Text('方块')
        .textAlign(TextAlign.Center)
        .backgroundColor('#ffb5c5e1')
        .size({ width: 50, height: 50 })
        .scale({
          x: this.myScale,
          y: this.myScale,
          centerX: 0,
          centerY: 0
        })
        .position({ x: this.x, y: this.y, })
        // .motionPath({ path: this.motionPaths[this.motionSection], })
        // .attributeModifier(new Mod(this.motionSection))
    }
    .width(300)
    .height(600)
    .backgroundColor('#ffe3f1df')
    .onClick((e) => this.keyFrameAnimation())
  }
}

class Mod implements AttributeModifier<CommonAttribute> {
  type: number

  constructor(type: number) {
    this.type = type
  }

  applyNormalAttribute(instance: CommonAttribute): void {
    if (this.type == 1) {
      instance.motionPath({ path: `M0 0 Q${(vp2px(100))} 0 ${vp2px(50)} ${vp2px(50)}` })
    } else if (this.type == 2) {
      instance.motionPath({ path: `M0 0 Q${(vp2px(100))} 0 ${vp2px(100)} ${vp2px(100)}` })

    }
  }
}
@Entry
@Component
struct KeyframeAnimateTo02 {
  @State toggle: boolean = true;
  @State myColor: Color = Color.Pink
  @State pathParams: MotionPathOptions = {
    path: 'Mstart.x start.y L10 100 L0 0 Lend.x end.y',
    from: 0,
    to: 1,
    rotatable: false
  };
  @State x: number = 10;
  @State y: number = 10;

  setPosition() {
    this.x = 200
    this.y = 200
    this.myColor = Color.Blue
  }

  setPosition2() {
    this.x = 400
    this.y = 400
    this.myColor = Color.Green
  }

  keyFrameAnimation() {
    this.getUIContext().keyframeAnimateTo({
      iterations: 1, // 整体播放次数
      expectedFrameRateRange: { min: 10, max: 120, expected: 60, },
    }, [
      {
        duration: 1000, event: () => {
        this.pathParams = {
          path: 'M10 10 L10 200 L200 200',
          from: 0,
          to: 1,
          rotatable: false
        };
        this.setPosition()
      }
      }
      , // 第1段动画
      {
        duration: 1000, event: () => {
        this.pathParams = {
          path: 'M200 200 L400 400',
          // path: 'M200 200 C300 200 350 400 400 400',
          // path: `M0 0 Q${(vp2px(100))} 0 ${vp2px(50)} ${vp2px(50)}`,
          from: 0,
          to: 1,
          rotatable: false
        };
        this.setPosition2()
      }
      }// 第2段动画
    ]);
  }

  build() {
    Column() {
      Button('点击').position({ x: 200 })
        .onClick(() => {
          this.toggle = !this.toggle
          this.keyFrameAnimation()
        })

      Text('Hello')
        .margin({ top: 50 })
        .width(50)
        .height(50)
        .backgroundColor(this.myColor)
        .motionPath(this.pathParams) // 设置路径动画
        .position({ top: this.toggle ? 0 : this.y, left: this.toggle ? 0 : this.x }) // 根据toggle状态设置位置
    }.backgroundColor('#ffa4cdb0')
    .width('100%')
    .height('100%') // 设置列的宽度和高度为100%
  }
}

第2段测试代码的执行录屏见附件(先瞬移,然后执行第2段动画)

这段代码中的第2段动画换为曲线后比较明显,先瞬移,然后曲线移动,也就是第一段动画没有执行


更多关于HarmonyOS鸿蒙Next中在关键帧动画的不同步骤怎么使用不同motionPath?的实战教程也可以访问 https://www.itying.com/category-93-b0.html

14 回复

可以在keyframeAnimateTo中分段设置motionPath移动路径,绑定文字路径动画,并使用状态变量设置位置,具体实现如下:

@Entry
@Component
struct MotionPathDemo {
  @State toggle: boolean = true;
  @State myColor: Color = Color.Pink
  @State pathParams: MotionPathOptions = {
    path: 'Mstart.x start.y L10 100 L0 0 Lend.x end.y',
    from: 0,
    to: 1,
    rotatable: false
  };
  @State x: number = 10;
  @State y: number = 10;

  setPosition() {
    this.x = 200
    this.y = 200
    this.myColor = Color.Blue
  } 

  setPosition2() {
    this.x = 400
    this.y = 400
    this.myColor = Color.Green
  }

  keyFrameAnimation() {
    this.getUIContext().keyframeAnimateTo({
      iterations: 1, // 整体播放次数
      expectedFrameRateRange: { min: 10, max: 120, expected: 60, },
    }, [
      {
        duration: 1000, event: () => {
        this.pathParams = {
          path: 'M10 10 L10 200 L200 200',
          from: 0,
          to: 1,
          rotatable: false
        };
        this.setPosition()
      }
      }
      , // 第1段动画
      {
        duration: 1000, event: () => {
        this.pathParams = {
          path: 'M200 200 L200 400 L400 400',
          from: 0,
          to: 1,
          rotatable: false
        };
        this.setPosition2()
      }
      } // 第2段动画
    ]);
  }

  build() {
    Column() {
      Button('点击')
        .onClick(() => {
          this.toggle = !this.toggle
          this.keyFrameAnimation()
        })

      Text('Hello')
        .margin({ top: 50 })
        .width(50)
        .height(50)
        .backgroundColor(this.myColor)
        .motionPath(this.pathParams) // 设置路径动画
        .position({ top: this.toggle ? 10 : this.y, left: this.toggle ? 10 : this.x }) // 根据toggle状态设置位置
    }
    .width('100%')
    .height('100%') // 设置列的宽度和高度为100%
  }
}

更多关于HarmonyOS鸿蒙Next中在关键帧动画的不同步骤怎么使用不同motionPath?的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


测试代码的执行录屏见附件(先瞬移,然后执行第2段动画) 这段代码中的第2段动画换为曲线后比较明显,先瞬移,然后曲线移动,也就是第一段动画没有执行,

Button('点击')
    .onClick(() => {
      // this.keyFrameAnimation()
      this.getUIContext().animateTo({
        duration: 1000,
        onFinish: () => {
          this.getUIContext().animateTo({
            duration: 1000,
          },() => {
            this.pathParams = {
              path: 'M200 200 L200 400 L400 400',
              from: 0,
              to: 1,
              rotatable: false
            };
            this.setPosition2()
          })
        }
      }, () => {
        this.toggle = !this.toggle
        this.pathParams = {
          path: 'M10 10 L10 200 L200 200',
          from: 0,
          to: 1,
          rotatable: false 
        };
        this.setPosition()
      })
    })

有要学HarmonyOS AI的同学吗,联系我:https://www.itying.com/goods-1206.html

开发者您好,为了更快解决您的问题,请您这边提供下动画的效果视频吧

这里不是发不了视频吗?,

您可以编辑下帖子,发帖子的时候是支持发送视频的,但是需要申请下视频上传权限,权限通过之后就可以发送视频了,如果您觉得申请权限麻烦,您这边也可以提供下gif动图效果也是可以的。

视频是我失败的效果还是期望的效果啊?失败的效果的话就是示例代码的运行效果,期望的效果就是【关键帧动画每个步骤对应不同的motionPath】,我想确认一下示例代码+之前的描述是不是不太清楚,我的理解是从代码应该足够表达,不行的话我再运行然后录个屏吧,

试一下这样是否可以

@Entry
@Component
struct KeyframeAnimateTo02 {
  @State myScale: number = 1.0;
  uiCtx?: UIContext = undefined;
  @State kTranslateOpt: TranslateOptions = { x: 0, y: 0, z: 0 }
  // 起点
  @State x: number = 0
  @State y: number = 0
  @State pathIndex: number = 0

  reset() {
    this.x = 0
    this.y = 0
    this.myScale = 1
    this.kTranslateOpt = { x: 0, y: 0, z: 0 }
  }

  keyFrameAnimation() {
    //安全校验
    if (!this.uiCtx) {
      console.info("no uiContext, keyframe failed");
      return;
    }
    // this.myScale = 1;
    // this.kTranslateOpt = { x: 0, y: 0, z: 0 }
    this.uiCtx.keyframeAnimateTo({
      iterations: 1, // 整体播放次数
      expectedFrameRateRange: { min: 10, max: 120, expected: 60, },
      // onFinish: () => this.reset()
    }, [
      {
        duration: 1000, event: () => {
        this.x = 300
        console.log("dty =====> pathIndex 0")
        this.pathIndex = 0
      }
      }, // 第1段动画
      {
        duration: 1000, event: () => {
        this.y = 300
        console.log("dty =====> pathIndex 1")
        this.pathIndex = 1
      }
      }// 第2段动画
    ]);
  }

  aboutToAppear() {
    this.uiCtx = this.getUIContext?.();
  }

  private motionPaths: MotionPathOptions[] = [{
    path: 'Mstart.x start.y L300 200 L300 500 Lend.x end.y',
    from: 0.0,
    to: 1.0,
    rotatable: true
  }, {
    path: 'Mstart.x start.y L800 400 L1300 1500 Lend.x end.y',
    from: 0.0,
    to: 1.0,
    rotatable: true
  }
  ];

  build() {
    Stack() {
      Text('方块')
        .textAlign(TextAlign.Center)
        .backgroundColor('#ffb5c5e1')
        .size({ width: 50, height: 50 })
        .scale({
          x: this.myScale,
          y: this.myScale,
          centerX: 0,
          centerY: 0
        })
        .position({ x: this.x, y: this.y, })
        .motionPath(this.motionPaths[this.pathIndex])
      // .attributeModifier(new Mod(this.motionSection))
    }
    .width(300)
    .height(600)
    .backgroundColor('#ffe3f1df')
    .onClick((e) => this.keyFrameAnimation())
  }
}

class Mod implements AttributeModifier<CommonAttribute> {
  type: number

  constructor(type: number) {
    this.type = type
  }

  applyNormalAttribute(instance: CommonAttribute): void {
    if (this.type == 1) {
      instance.motionPath({ path: `M0 0 Q${(vp2px(100))} 0 ${vp2px(50)} ${vp2px(50)}` })
    } else if (this.type == 2) {
      instance.motionPath({ path: `M0 0 Q${(vp2px(100))} 0 ${vp2px(100)} ${vp2px(100)}` })

    }
  }
}

试了不行,

回答来自ai,

// @Entry 表示这是程序的入口组件
@Component
struct KeyframeAnimateTo02 {
  // 定义状态变量 myScale,初始值为 1.0,用于控制缩放比例
  @State myScale: number = 1.0;
  // 声明一个可选的 UI 上下文对象,默认为 undefined
  uiCtx?: UIContext = undefined;
  // 定义状态变量 kTranslateOpt,类型为 TranslateOptions,初始值为 { x: 0, y: 0, z: 0 },表示平移选项
  @State kTranslateOpt: TranslateOptions = { x: 0, y: 0, z: 0 }
  // 起点坐标的状态变量
  @State x: number = 0
  @State y: number = 0

  // 重置函数,将各状态变量恢复到初始值
  reset() {
    this.x = 0          // 将 x 坐标重置为 0
    this.y = 0          // 将 y 坐标重置为 0
    this.myScale = 1    // 将缩放比例重置为 1
    this.kTranslateOpt = { x: 0, y: 0, z: 0 }  // 将平移选项重置为初始值
  }

  // 关键帧动画函数
  keyFrameAnimation() {
    // 安全校验,如果不存在 UI 上下文则输出提示信息并返回
    if (!this.uiCtx) {
      console.info("no uiContext, keyframe failed"); // 打印日志提示没有获取到 UI 上下文,关键帧动画失败
      return;
    }
    // 使用 UI 上下文执行关键帧动画操作
    this.uiCtx.keyframeAnimateTo({
      iterations: 1, // 整体播放次数设置为 1 次
      expectedFrameRateRange: { min: 10, max: 1,

在HarmonyOS Next中,关键帧动画的不同步骤使用不同motionPath可通过在动画序列中定义多个关键帧并分别设置各自的motionPath属性实现。每个关键帧可独立配置路径数据(如Path或Vector),系统会在动画执行时自动插值过渡。使用AnimatorPath或相关动画组件,在关键帧时间点指定对应的motionPath,确保路径在动画周期内平滑切换。具体属性需参考HarmonyOS动画开发文档。

在HarmonyOS Next的关键帧动画中,不同步骤使用不同motionPath的正确方式是通过AttributeModifier动态修改路径属性。

从你的代码分析,问题在于:

  1. 关键帧事件执行时机keyframeAnimateTo中的event回调是立即执行的,不是动画过程中逐步执行的。这导致两个motionPath设置几乎同时发生。

  2. 路径起点问题:第二个motionPath的起点应该是第一个路径的终点,而不是重新从(0,0)开始。

修正方案:

class MotionPathModifier implements AttributeModifier<CommonAttribute> {
  private currentPath: number = 0;
  
  setCurrentPath(pathIndex: number) {
    this.currentPath = pathIndex;
  }
  
  applyNormalAttribute(instance: CommonAttribute): void {
    switch(this.currentPath) {
      case 0:
        instance.motionPath({ 
          path: 'M0 0 Q150 0 75 75', // 第一段贝塞尔曲线
          from: 0,
          to: 1,
          rotatable: true
        });
        break;
      case 1:
        instance.motionPath({ 
          path: 'M75 75 Q225 75 150 150', // 从第一段终点开始
          from: 0, 
          to: 1,
          rotatable: true
        });
        break;
    }
  }
}

在关键帧动画中:

const modifier = new MotionPathModifier();

// 第一帧设置第一段路径
modifier.setCurrentPath(0);
// 触发UI更新

// 第二帧设置第二段路径  
modifier.setCurrentPath(1);
// 触发UI更新

这样确保每段动画使用独立的运动路径,且路径连接自然过渡。

回到顶部