HarmonyOS鸿蒙Next中请问有什么方法可以实现组件一动画结束后在执行组件二动画的效果

HarmonyOS鸿蒙Next中请问有什么方法可以实现组件一动画结束后在执行组件二动画的效果

setInterval(() => {
  this.getUIContext().animateTo({
    duration: 1000,
    onFinish: () => {
      this.topRotateValue = 0
      this.getUIContext().animateTo({
        duration:1000,
        onFinish:()=>{
          this.bottomRotateValue = -90
        }
      }, () => {
        this.bottomRotateValue = 0
      })
    },
  }, () => {
    this.topRotateValue = -90
  })
}, 2500)

animateTo这种嵌套,第一个animateTo已经finish,再去嵌套使用另一个animateTo,但是第一个animateTo也会生效执行,最后会变成两个组件的动画同时执行


更多关于HarmonyOS鸿蒙Next中请问有什么方法可以实现组件一动画结束后在执行组件二动画的效果的实战教程也可以访问 https://www.itying.com/category-93-b0.html

9 回复

【背景知识】

keyframeAnimateTo为关键帧动画,keyframeAnimateTo接口来指定若干个关键帧状态,实现分段的动画,该接口第一个参数为循环的次数,当iterations设置为-1即可实现循环动画。

【解决方案】

可参考使用关键帧动画实现,实现分段循环动画,详情参考动画示例,循环播放,代码如下:

import { RectShape } from "@kit.ArkUI";

@Entry
@Component
export struct TestPage2 {
  // 第一步: 声明相关状态变量
  @State bottomRotateValue: number =-90;
  @State topRotateValue: number = 0;
  @State curValue: number = 1; // 组件一旋转角度
  uiContext: UIContext | undefined = undefined;

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

  // startAnim1() {
  //   this.topRotateValue = 0
  //   this.bottomRotateValue =-90
  //   this.getUIContext().animateTo({
  //     duration: 500,
  //     onFinish: () => {
  //       this.topRotateValue = -90
  //       this.bottomRotateValue = -90
  //       this.startAnim2()
  //     },
  //   }, () => {
  //     this.topRotateValue = -90
  //   })
  //
  // }
  //
  // startAnim2() {
  //   this.getUIContext().animateTo({
  //     duration: 500,
  //     onFinish: () => {
  //       this.bottomRotateValue = -90
  //       this.topRotateValue = 0
  //       this.startAnim1()
  //     }
  //   }, () => {
  //     this.bottomRotateValue = 0
  //   })
  // }

  build() {
    Stack() {
      Text((this.curValue).toString()) {
      }
      .fontSize(80)
      .fontColor(Color.White)
      .fontWeight(FontWeight.Bold)
      .width(150)
      .textAlign(TextAlign.Center)
      .height(100)
      .backgroundColor(Color.Black)
      .rotate({ x: 1, angle: this.topRotateValue })
      .borderRadius(10)
      .clipShape(new RectShape({ width: "100%", height: "49%", }).radiusWidth(10))


      Text((this.curValue).toString()) {
      }
      .fontSize(80)
      .fontColor(Color.White)
      .fontWeight(FontWeight.Bold)
      .width(150)
      .textAlign(TextAlign.Center)
      .height(100)
      .backgroundColor(Color.Black)
      .rotate({ x: -1, angle: this.bottomRotateValue })
      .borderRadius(10)
      .onClick(() => {
        if (!this.uiContext) {
          console.info("no uiContext, keyframe failed");
          return;
        }
        this.topRotateValue = 0
        this.bottomRotateValue =-90
        this.uiContext.keyframeAnimateTo({
          iterations: -1,
          expectedFrameRateRange: {
            min: 10,
            max: 120,
            expected: 60,
          }
        }, [
          {
            duration: 500,
            event: () => {
              this.topRotateValue = -90
              this.bottomRotateValue = -90
            },
          },
          {
            duration: 500,
            event: () => {
              this.topRotateValue = -90
              this.bottomRotateValue = -90
            }
          },
          {
            duration: 500,
            event: () => {
              this.topRotateValue = -90
              this.bottomRotateValue = 0
            }
          }
        ]);
      })
      .clipShape(new RectShape({ width: "100%", height: "50%", }).offset({ x: 0, y: "51%" }).radiusWidth(10))

    }
    .width('100%')
    .height('100%')
  }
}

更多关于HarmonyOS鸿蒙Next中请问有什么方法可以实现组件一动画结束后在执行组件二动画的效果的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


使用显式动画(animateTo)嵌套回调

通过animateTo的闭包函数执行第一个组件动画,并在其onFinish回调中触发第二个组件的动画
参考:显式动画 (animateTo)-动画-ArkTS组件-ArkUI(方舟UI框架)-应用框架 - 华为HarmonyOS开发者

可以尝试使用 Promise 包装 animateTo

Demo 代码

@Entry
@Component
struct AnimationExample {
  @State topRotateValue: number = 0;
  @State bottomRotateValue: number = 0;
  private intervalId: number | null = null;

  /**
   * 将animateTo包装成Promise
   */
  private animateToPromise(
    duration: number,
    onUpdate: () => void,
    curve?: Curve
  ): Promise<void> {
    return new Promise((resolve) => {
      this.getUIContext().animateTo({
        duration: duration,
        curve: curve || Curve.EaseInOut,
        onFinish: () => {
          resolve();
        }
      }, () => {
        onUpdate();
      });
    });
  }

  /**
   * 顺序执行动画序列
   */
  private async playAnimationSequence(): Promise<void> {
    try {
      // 步骤1:topRotateValue 从 0 到 -90
      await this.animateToPromise(1000, () => {
        this.topRotateValue = -90;
      });

      // 步骤2:topRotateValue 从 -90 到 0
      await this.animateToPromise(1000, () => {
        this.topRotateValue = 0;
      });

      // 步骤3:bottomRotateValue 从 -90 到 0
      await this.animateToPromise(1000, () => {
        this.bottomRotateValue = 0;
      });

      // 步骤4:bottomRotateValue 从 0 到 -90
      await this.animateToPromise(1000, () => {
        this.bottomRotateValue = -90;
      });

      console.info('动画序列完成');
    } catch (error) {
      console.error('动画执行失败:', error);
    }
  }

  /**
   * 开始循环动画
   */
  startAnimationLoop(): void {
    if (this.intervalId !== null) {
      return; // 已经在运行
    }

    // 立即执行一次
    this.playAnimationSequence();

    // 每2.5秒执行一次
    this.intervalId = setInterval(() => {
      this.playAnimationSequence();
    }, 2500);
  }

  /**
   * 停止循环动画
   */
  stopAnimationLoop(): void {
    if (this.intervalId !== null) {
      clearInterval(this.intervalId);
      this.intervalId = null;
    }
  }

  aboutToAppear() {
    this.startAnimationLoop();
  }

  aboutToDisappear() {
    this.stopAnimationLoop();
  }

  build() {
    Column() {
      // 顶部组件
      Column() {
        Text('Top')
          .fontSize(20)
      }
      .width(100)
      .height(100)
      .backgroundColor('#FF6B6B')
      .rotate({ angle: this.topRotateValue })
      .margin({ bottom: 50 })

      // 底部组件
      Column() {
        Text('Bottom')
          .fontSize(20)
      }
      .width(100)
      .height(100)
      .backgroundColor('#4ECDC4')
      .rotate({ angle: this.bottomRotateValue })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

找HarmonyOS工作还需要会Flutter的哦,有需要Flutter教程的可以学学大地老师的教程,很不错,B站免费学的哦:https://www.bilibili.com/video/BV1S4411E7LY/?p=17

@Component
export struct TestPage2 {
  // 第一步: 声明相关状态变量
  @State bottomRotateValue: number =-90;
  @State topRotateValue: number = 0;
  @State curValue: number = 1; // 组件一旋转角度


  startAnim1() {
    this.topRotateValue = 0
    this.bottomRotateValue =-90
    this.getUIContext().animateTo({
      duration: 500,
      onFinish: () => {
        this.topRotateValue = -90
        this.bottomRotateValue = -90
        this.startAnim2()
      },
    }, () => {
      this.topRotateValue = -90
    })

  }

  startAnim2() {
    this.getUIContext().animateTo({
      duration: 500,
      onFinish: () => {
        this.bottomRotateValue = -90
        this.topRotateValue = 0
        this.startAnim1()
      }
    }, () => {
      this.bottomRotateValue = 0
    })
  }

  aboutToAppear(): void {
    this.startAnim1()

  }

  build() {
    Stack() {
      Text((this.curValue).toString()) {
      }
      .fontSize(80)
      .fontColor(Color.White)
      .fontWeight(FontWeight.Bold)
      .width(150)
      .textAlign(TextAlign.Center)
      .height(100)
      .backgroundColor(Color.Black)
      .rotate({ x: 1, angle: this.topRotateValue })
      .borderRadius(10)
      .clipShape(new RectShape({ width: "100%", height: "49%", }).radiusWidth(10))


      Text((this.curValue).toString()) {
      }
      .fontSize(80)
      .fontColor(Color.White)
      .fontWeight(FontWeight.Bold)
      .width(150)
      .textAlign(TextAlign.Center)
      .height(100)
      .backgroundColor(Color.Black)
      .rotate({ x: -1, angle: this.bottomRotateValue })
      .borderRadius(10)
      .onClick(() => {

      })
      .clipShape(new RectShape({ width: "100%", height: "50%", }).offset({ x: 0, y: "51%" }).radiusWidth(10))

    }
    .width('100%')
    .height('100%')
  }
}

使用v1状态管理,嵌套使用可以实现想要的效果,但是v2的会抽风

由于当前animateTo与V2在刷新机制上暂不兼容,在执行动画前额外的修改并不会生效。 可以参考 https://developer.huawei.com/consumer/cn/doc/architecture-guides/common-v1_26-ts_88-0000002312256121#section17454203417490 中的“二、状态管理V2的局限性及注意事项。”,

let num = setInterval()

setInterval应该会生成一个序号,你可以试试把这个序号用变量存起来,然后在第一个定时器结束时开启第二个定时器,同时把第一个的序号删除

在HarmonyOS Next中,可以使用Animation组件的onFinish回调。在组件一的动画定义中设置onFinish,触发组件二的动画执行。例如,通过状态变量控制,当组件一动画完成时,改变状态以启动组件二的动画。

在HarmonyOS Next中,要实现组件一动画结束后再执行组件二动画,关键在于正确使用animateTo的回调函数。你提供的代码中嵌套方式有误,导致两个动画同时执行。

正确的方法是:

  1. 在第一个animateToonFinish回调中,启动第二个animateTo动画。
  2. 确保第二个动画的状态变更逻辑(() => { this.bottomRotateValue = 0 })是放在其动画参数对象中,而不是作为animateTo的第二个参数(即立即执行的回调)。

以下是修正后的代码逻辑:

// 假设这是触发动画的某个时机,例如按钮点击或生命周期函数
startSequentialAnimation() {
  // 执行第一个组件(例如top)的动画
  this.getUIContext().animateTo({
    duration: 1000,
    onFinish: () => {
      // 第一个动画结束后,更新其状态(如果需要)
      this.topRotateValue = 0;
      // 紧接着开始执行第二个组件(例如bottom)的动画
      this.getUIContext().animateTo({
        duration: 1000,
        onFinish: () => {
          // 第二个动画结束后,更新其状态
          this.bottomRotateValue = -90;
        }
      }, () => {
        // 这是第二个animateTo的“动画过程”回调,用于设置动画起始值
        this.bottomRotateValue = 0; // 第二个动画从0度开始
      });
    }
  }, () => {
    // 这是第一个animateTo的“动画过程”回调,用于设置动画起始值
    this.topRotateValue = -90; // 第一个动画从-90度开始
  });
}

代码解析:

  • 第一个animateTo:驱动topRotateValue从-90度变化到其当前值(假设在onFinish里你设为了0)。当这个1000ms的动画完成后,触发其onFinish
  • 第二个animateTo:在第一个动画的onFinish被调用。它驱动bottomRotateValue从0度变化到-90度,耗时1000ms。这样就实现了串行效果。

你原代码的问题: 你将第二个animateTo的起始值设置(() => { this.bottomRotateValue = 0 })放在了第一个animateToonFinish回调函数体外部,作为了第一个animateTo的第二个参数。这导致bottomRotateValue = 0这个状态变更在第一个动画启动时立即执行了,而不是作为第二个动画的起始状态。因此,两个动画的“驱动变量”几乎同时发生变化,视觉上就是同时执行。

核心要点:

  • animateTo的第二个参数(一个函数)是动画起始帧回调,会立即执行以确定动画起点。
  • onFinish是动画结束后的回调,在这里启动下一个动画是确保顺序执行的关键。
  • 使用setInterval进行循环动画时,确保每次循环都按此顺序触发。

按照上述修正,即可保证组件一的动画完全结束后,组件二的动画才开始。

回到顶部