HarmonyOS canvas动画如何实现逐帧动画

HarmonyOS
1天前
浏览
收藏 0
回答 1
待解决
回答 1
按赞同
/
按时间
superinsect

参考示例如下:

@Entry
@Component
export default struct ProMode {
  // 创建渲染上下文设置和画布渲染上下文
  private settings: RenderingContextSettings = new RenderingContextSettings(true) // 初始化渲染上下文设置
  private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings) // 创建画布渲染上下文
  // 定义画布宽度和高度
  private canvasWidth: number = 0; // 画布宽度
  private canvasHeight: number = 0; // 画布高度
  private markBottom: number = 90; // 底部基线位置
  private longMarkSize: number = 50; // 中间刻度线的长度
  private midMarkSize: number = 28; // 较长的刻度线长度
  private shortMarkSize: number = 8; // 较短的刻度线长度
  private greenLineColor: string = '#6EC967'; // 绿色线的颜色
  private redLineColor: string = '#EF9D9D'; // 红色线的颜色
  private pitchDifferenceColor: string = 'rgba(245, 138, 138, 0.16)'; // 红色透明区域的颜色
  @State offsetText: string = '智能识别中' // 刻度上方文字
  @State offsetNum: number = 0 // 刻度偏移量
  @State isOffsetOk: boolean = false // 偏移量是否正常
  @State redX: number = 50;
  @State redXNew: number = 50;
  @State tickSpacing: number = 0;
  @State timeId: number = -1;
  @State isRunning: boolean = false;

  build() {
    Column() {
      // 弦刻度
      Column() {
        Text(this.offsetText)
          .fontSize('30fp')
          .fontColor(this.isOffsetOk ? '#78c371' : '#fff')
          .margin({ top: '70vp' })
      }

      // 刻度
      Column() {
        // 在这里使用 MeterView 组件
        Flex({
          direction: FlexDirection.Column,
          alignItems: ItemAlign.Center,
          justifyContent: FlexAlign.Center
        }) { // 创建Flex布局
          // 创建画布
          Canvas(this.context)
            .width('96%')// 设置画布宽度为100%
            .height('100%')// 设置画布高度为100%
            .backgroundColor('#transparent')// 设置画布背景颜色为白色
            .onReady(() => {
              this.canvasWidth = this.context.width; // 获取画布宽度
              this.canvasHeight = this.context.height; // 获取画布高度
              this.onDraw(); // 调用绘制函数
            })
            .onClick((event) => {
              console.log('event.x', event.x);
              this.redXNew = Math.floor((event.x - this.canvasWidth / 2) / this.tickSpacing) * 5;
              if (!this.isRunning) {
                clearInterval(this.timeId);
                this.isRunning = true;
                setTimeout(() => {
                  console.log('------redx =' + this.redXNew, 'this.redX=' + this.redX);
                  this.isRunning = false;
                  if (this.redXNew > this.redX) {
                    this.timeId = setInterval(() => {
                      if (this.redXNew > this.redX) {
                        this.redX++;
                        this.onDraw();
                      } else {
                        console.log('this.redXNew > this.redX')

                        this.redX = this.redXNew;
                        this.onDraw();
                        clearInterval(this.timeId)
                      }
                    }, 50)
                  }
                  if (this.redX > this.redXNew) {
                    this.timeId = setInterval(() => {
                      if (this.redXNew < this.redX) {
                        this.redX--;
                        this.onDraw();
                      } else {
                        console.log('this.redX > this.redXNew')
                        this.redX = this.redXNew;
                        this.onDraw();
                        clearInterval(this.timeId)
                      }
                    }, 50)
                  }
                }, 500)
              }
            })
            .borderColor('#5c000000')// 设置画布边框颜色
            .borderStyle(BorderStyle.Solid) // 设置画布边框样式为实线
        }
        .width('100%') // 设置Flex布局宽度为100%
        .height('100%') // 设置Flex布局高度为100%
      }
      .height('120vp')
      .width('100%')

      //浅灰色条框
      Column() {
        Row() {
          Text('-50b')
            .fontSize('16fp')
            .fontColor('#ffb5acac')
            .width('33%')
            .textAlign(TextAlign.Start);

          Text('0')
            .fontSize('16fp')
            .fontColor('#ffb5acac')
            .width('33%')
            .textAlign(TextAlign.Center);

          Text('#50')
            .fontSize('16fp')
            .fontColor('#ffb5acac')
            .width('33%')
            .textAlign(TextAlign.End);
        }
        .backgroundColor('#535353') // 浅灰色背景
        .height('20vp')
        .padding({ left: '10vp', right: '10vp' });
      }
      .height('50vp')
      .width('100%')
      .margin({ top: 0 });
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#494949')
  }

  // canvas绘制
  onDraw() {
    const context = this.context; // 获取渲染上下文
    context.clearRect(0, 0, this.canvasWidth, this.canvasHeight); // 清除画布
    context.save(); // 保存当前绘图状态
    context.translate(this.canvasWidth / 2, this.markBottom); // 平移到指定位置
    context.strokeStyle = '#79d0cece'; // 设置刻度线的笔触颜色
    context.lineWidth = 3; // 设置刻度线的笔触宽度
    context.lineCap = 'round'; // 设置刻度线末端为圆形
    // 计算刻度线的间隔
    const totalTicks = 21; // 刻度线的总数
    this.tickSpacing = (this.canvasWidth - 5) / (totalTicks - 1); // 每份刻度的宽度
    for (let i = 0; i < totalTicks; i++) { // 循环绘制21条刻度线
      const x = (i - Math.floor(totalTicks / 2)) * this.tickSpacing; // 计算刻度线位置
      context.beginPath(); // 开始新的路径
      if (i === Math.floor(totalTicks / 2)) { // 中间刻度线
        context.moveTo(x, 0); // 移动到起点
        context.lineTo(x, -this.longMarkSize); // 绘制中间最长刻度线
      } else if (i === 0 || i === 5 || i === 15 || i === 20) { // 较长的刻度线
        context.moveTo(x, 0); // 移动到起点
        context.lineTo(x, -this.midMarkSize); // 绘制较长的刻度线
      } else { // 较短的刻度线
        context.moveTo(x, 0); // 移动到起点
        context.lineTo(x, -this.shortMarkSize); // 绘制较短的刻度线
      }
      context.stroke(); // 描边路径
    }
    // 绘制绿色线(固定在中间)
    context.strokeStyle = this.greenLineColor;
    context.lineWidth = 3; // 设置绿色线的宽度
    context.beginPath();
    const greenX = 0; // 绿色线固定在中间位置
    const greenLineLength = this.canvasHeight * 0.6; // 绿色线的长度(根据画布高度的60%)
    context.moveTo(greenX, -greenLineLength / 1.4); // 从绿色线的顶部开始
    context.lineTo(greenX, greenLineLength / 7.0); // 画到绿色线的底部
    if (this.offsetNum !== 0) {
      context.stroke(); // 描边绿色线
    }
    // 计算每个刻度线的宽度
    const totalTicksInRange = 100; // 刻度总数(分成100份)
    const tickSpacingInRange = (this.canvasWidth - 5) / totalTicksInRange; // 每个刻度的宽度
    // 假设 redX 是你已经获得的值
    // const redX = 50; // 你给定的值
    animateTo({
      duration: 40000,
      curve: Curve.Ease,
      delay: 0,
      iterations: 1,
      playMode: PlayMode.Normal
    }, () => {
      // const redX = this.offsetNum; // 你给定的值
      // const redX = 40; // 你给定的值
      // 反推 redTickIndex
      const redTickIndex = Math.round(((this.redX + 50) / 100) * totalTicksInRange);
      // 确保 redTickIndex 在有效范围内
      const validRedTickIndex = Math.max(0, Math.min(redTickIndex, totalTicksInRange));
      // 计算 redLineLength(假设你需要这个变量)
      const redLineLength = this.canvasHeight * 0.6; // 红色线的长度(根据画布高度的60%)
      // 重新计算 redX(如果需要验证)
      const recalculatedRedX = (validRedTickIndex - (totalTicksInRange / 2)) * tickSpacingInRange;
      // 只有当 redTickIndex 不等于 50 时才绘制红色线
      if (validRedTickIndex !== 50) {
        // 绘制红色线
        context.strokeStyle = this.redLineColor;
        context.lineWidth = 3; // 设置红色线的宽度
        context.beginPath();
        context.moveTo(recalculatedRedX, -redLineLength / 1.4); // 从红色线的顶部开始
        context.lineTo(recalculatedRedX, redLineLength / 7); // 画到红色线的底部
        context.stroke(); // 描边红色线
        // 绘制红色透明区域
        const leftX = Math.max(-this.canvasWidth / 2, Math.min(greenX, recalculatedRedX)); // 计算左边界
        const rightX = Math.min(this.canvasWidth / 2, Math.max(greenX, recalculatedRedX)); // 计算右边界
        context.fillStyle = this.pitchDifferenceColor;
        context.beginPath();
        context.moveTo(leftX, -redLineLength / 1.4);
        context.lineTo(rightX, -redLineLength / 1.4);
        context.lineTo(rightX, redLineLength / 7);
        context.lineTo(leftX, redLineLength / 7);
        context.closePath();
        context.fill(); // 填充透明区域
      }
    })
    context.restore(); // 恢复绘图状态
  }
}
分享
微博
QQ
微信
回复
1天前
相关问题
HarmonyOS Canvas 实现动画
226浏览 • 1回复 待解决
HarmonyOS 动画用什么实现
247浏览 • 1回复 待解决
HarmonyOS Canvas动画实现
25浏览 • 1回复 待解决
HarmonyOS 动画如何主动停止
32浏览 • 1回复 待解决
HarmonyOS 动画demo
325浏览 • 1回复 待解决
HarmonyOS 有没有动画组件
247浏览 • 1回复 待解决
HarmonyOS 属性动画怎么监听回调?
537浏览 • 1回复 待解决
属性动画如何实现宽高动画效果
2167浏览 • 1回复 待解决
如何应用属性动画实现宽高的动画
522浏览 • 1回复 待解决
HarmonyOS 动画实现
236浏览 • 1回复 待解决
HarmonyOS 如何实现RippleView动画
455浏览 • 1回复 待解决
有谁知道是否支持动画
2584浏览 • 1回复 待解决
如何实现动画转场效果
1011浏览 • 1回复 待解决
HarmonyOS如何实现动态缩放动画
639浏览 • 1回复 待解决
HarmonyOS 如何实现音频声浪动画
894浏览 • 1回复 待解决
HarmonyOS 如何实现旋转动画
555浏览 • 1回复 待解决
HarmonyOS 如何实现动画集合?
423浏览 • 1回复 待解决
HarmonyOS 如何实现WaveView动画
409浏览 • 1回复 待解决