[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也有不足的地方,希望后面能更加完善吧。

相关推荐
dalancon26 分钟前
VSYNC 信号流程分析 (Android 14)
android
dalancon36 分钟前
VSYNC 信号完整流程2
android
dalancon38 分钟前
SurfaceFlinger 上帧后 releaseBuffer 完整流程分析
android
不爱吃糖的程序媛1 小时前
OpenHarmony 工程结构剖析
harmonyos
用户69371750013842 小时前
不卷AI速度,我卷自己的从容——北京程序员手记
android·前端·人工智能
程序员Android2 小时前
Android 刷新一帧流程trace拆解
android
墨狂之逸才3 小时前
解决 Android/Gradle 编译报错:Comparison method violates its general contract!
android
阿明的小蝴蝶3 小时前
记一次Gradle环境的编译问题与解决
android·前端·gradle
汪海游龙4 小时前
开源项目 Trending AI 招募 Google Play 内测人员(12 名)
android·github
qq_283720055 小时前
MySQL技巧(四): EXPLAIN 关键参数详细解释
android·adb