[HarmonyOS]实现一个带动画的环形进度条

前言

最近在边学边进行现有项目的鸿蒙化,需要实现一个环形进度条,摸索了一阵最后用canvas实现了出来,刚好最近感觉博客没东西可写,借这个机会又有东西可以分享给大家了。

我会先把我的摸索过程写出来,如果想直接看实现可以跳过第二部分。

预期效果和准备工作

先来一张效果图

预期效果类似一个表盘,中间区域是一个渐变色的圆,外层是带刻度的弧形进度条,要实现这个效果并不难,先拆分一下

  1. 圆环的浅蓝色背景
  2. 在圆环背景上的颜色深一点的刻度线
  3. 颜色深一点的进度条弧形
  4. 中间区域渐变圆形
  5. 进度条起点上的深蓝色圆点

在安卓中想要实现这个效果其实是很简单的,所以只需要按照既定思路去找鸿蒙的对应API就可以

鸿蒙的绘制API

鸿蒙的绘制API分为Canvas和绘制组件,canvas和html的canvas大同小异,而绘制组件我理解为是对canvas的封装,看起来像下面这样

看起来使用绘制组件可以非常简单地实现我们需要的效果,我一开始就是这么尝试的,但是遇到了两个问题让我放弃使用它

首先没有提供弧形的绘制组件,虽然path可以做到类似的效果,但是未免太麻烦了,需要手写svg指令来绘制

第二个就是动画的问题,本来通过svg指令来绘制已经够麻烦了,要加动画就需要在指令语句里面再加上插值计算,完全不能接受。

最后还是选择用canvas,个人认为绘制组件只能满足一些简单的场景,特别的path这个东西非常的不好用

鸿蒙中的动画API

动画相关API同样分为两部分,一个是和组件强相关的组件动画,另一个是更加独立和抽象的animator

组件动画主要用于对组件的固有属性比如宽高、位置这些进行插值变换构成动画,而animator就跟安卓的animator大同小异了

很明显组件动画没办法适用于canvas,所以最后我们要使用canvas+animator来实现

如何实现

代码如下

typescript 复制代码
import animator, { AnimatorOptions, AnimatorResult } from '@ohos.animator';

@Component
@Preview
export struct ProgressPanel {
  private settings: RenderingContextSettings = new RenderingContextSettings(true)
  private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings)
  private arcWidth: number = 10
  private anim: AnimatorResult | null = null

  aboutToAppear() {
    const options: AnimatorOptions = {
      duration: 500,
      // 插值器
      easing: "ease-out",
      delay: 300,
      // 动画执行完后保持最后一帧的状态
      fill: "forwards",
      direction: "normal",
      iterations: 1,
      // 动画插值
      begin: 0,
      end: 0.5
    };
    this.anim = animator.create(options);
    this.anim.onframe = (value) => {
      this.updateProgress(value)
    };
  }

  aboutToDisappear() {
    this.anim?.cancel()
    this.anim = null
  }

  private updateProgress(progress: number) {
    this.context.clearRect(0, 0, this.context.width, this.context.height)
    const centerX = this.context.width / 2;
    const centerY = this.context.width / 2;
    const radius = Math.min(centerX, centerY) - this.arcWidth;
    this.context.save()
    this.context.save()
    this.context.save()
    
    // 进度条背景
    this.context.beginPath()
    this.context.arc(centerX, centerY, radius, 0, Math.PI * 2)
    this.context.lineWidth = this.arcWidth
    this.context.strokeStyle = '#803db7fe';
    this.context.stroke()
    
    // 进度条刻度
    this.context.beginPath()
    this.context.arc(centerX, centerY, radius, 0, Math.PI * 2)
    this.context.setLineDash([1, 5])
    this.context.lineWidth = this.arcWidth
    this.context.strokeStyle = '#4d3db7fe';
    this.context.stroke()
    
    // 进度条
    const startAngle = -Math.PI / 2;
    const endAngle = startAngle + Math.PI * 2 * progress;

    this.context.restore()
    this.context.beginPath()
    this.context.arc(centerX, centerY, radius, startAngle, endAngle);
    this.context.strokeStyle = '#3DB7FE';
    this.context.lineCap = 'round'
    this.context.lineWidth = this.arcWidth;
    this.context.stroke();
    
    // 中间圆形
    const gradient = this.context.createLinearGradient(radius, 0, radius, radius * 2)

    gradient.addColorStop(0.2, '#3DB7FE')
    gradient.addColorStop(1.0, '#399BFE')

    this.context.restore()
    this.context.beginPath()
    this.context.arc(centerX, centerY, radius - this.arcWidth, 0, Math.PI * 2);
    this.context.fillStyle = gradient
    this.context.fill();
    
    // 起点圆点
    this.context.restore()
    this.context.beginPath()
    this.context.arc(centerX, this.arcWidth, this.arcWidth / 2, 0, Math.PI * 2);
    this.context.fillStyle = '#399BFE'
    this.context.fill();
  }

  build() {
    Column() {
      Stack() {
        Canvas(this.context)
          .width(200)
          .height(200)
          .onReady(() => {
            this.anim?.play()
          })
      }
      .width('100%')
      .alignContent(Alignment.Center)
    }
    .padding({ left: 20, right: 20 })
    .margin({ top: 10, bottom: 10 })
    .width('100%')

  }
}
  • 开头调用了三次context.save()是想偷懒,不想每绘制一个不同的部分都重新设置画笔的一些属性
  • 进度条刻度在实现起来很方便,setLineDash,参数分别为填充宽度和空白间隔宽度
  • 绘制弧形一样是使用的弧度制,圆形等于2PI
  • 9点钟方向弧度为0,我们需要起点在12点方向,所以起点弧度为-Math.PI / 2
  • 中间圆形添加了一个径向渐变,通过addColorStop添加渐变的插值点
  • 动画的end值为方便演示是写死的,需要的时候改成真实进度就可以了

本来给进度条也加上渐变,但是发现径向、放射、扫射三种渐变都没办应用在弧形上面,希望以后能支持

总结

这个组件实现起来还是比较简单的,主要的时间都花在了看文档,找api,试验上面;不得不吐槽的是绘制组件目前看来确实鸡肋,而canvas也有不足的地方,希望后面能更加完善吧。

相关推荐
Estar.Lee16 分钟前
查手机号归属地免费API接口教程
android·网络·后端·网络协议·tcp/ip·oneapi
温辉_xh38 分钟前
uiautomator案例
android
工业甲酰苯胺2 小时前
MySQL 主从复制之多线程复制
android·mysql·adb
少说多做3432 小时前
Android 不同情况下使用 runOnUiThread
android·java
踏雪Vernon3 小时前
[OpenHarmony5.0][Docker][环境]OpenHarmony5.0 Docker编译环境镜像下载以及使用方式
linux·docker·容器·harmonyos
Estar.Lee3 小时前
时间操作[计算时间差]免费API接口教程
android·网络·后端·网络协议·tcp/ip
找藉口是失败者的习惯4 小时前
从传统到未来:Android XML布局 与 Jetpack Compose的全面对比
android·xml
Jinkey5 小时前
FlutterBasic - GetBuilder、Obx、GetX<Controller>、GetxController 有啥区别
android·flutter·ios
大白要努力!7 小时前
Android opencv使用Core.hconcat 进行图像拼接
android·opencv