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

相关推荐
千里马学框架3 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台3 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone3 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
youyin3 天前
HarmonyOS ArkUI 组件与自定义组件零基础:从搭页面到组件化开发
华为·harmonyos
致远ccc3 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo3 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077003 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼3 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
HwJack203 天前
【HarmonyOS开发小实践】ArkTS 从 TypeScript 到方舟语言的演进
华为·harmonyos
AFinalStone3 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui