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

相关推荐
zhangphil2 小时前
Android绘图Path基于LinearGradient线性动画渐变,Kotlin(2)
android·kotlin
watl03 小时前
【Android】unzip aar删除冲突classes再zip
android·linux·运维
键盘上的蚂蚁-3 小时前
PHP爬虫类的并发与多线程处理技巧
android
Freerain993 小时前
鸿蒙Next类属性观测器V2
华为·harmonyos
喜欢猪猪4 小时前
Java技术专家视角解读:SQL优化与批处理在大数据处理中的应用及原理
android·python·adb
yg_小小程序员5 小时前
鸿蒙开发(16)使用DevEco Studio上的Git工具进行多远程仓管理
git·华为·harmonyos
JasonYin~5 小时前
HarmonyOS NEXT 实战之元服务:静态案例效果---每日玩机技巧
harmonyos
轻口味5 小时前
【每日学点鸿蒙知识】多线程限制、axios组件下载进度问题、lpx问题、Web组件全局代理、ArrayList问题
华为·harmonyos
yuanlaile5 小时前
纯Dart Flutter库适配HarmonyOS
flutter·华为·harmonyos·flutter开发鸿蒙·harmonyos教程
yuanlaile5 小时前
Flutter开发HarmonyOS 鸿蒙App的好处、能力以及把Flutter项目打包成鸿蒙应用
flutter·华为·harmonyos·flutter开发鸿蒙