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

相关推荐
xmdy586621 分钟前
Flutter+开源鸿蒙实战|校园易生活Day1 项目初始化搭建+开发环境校验+工程目录规范+第三方库集成+多端屏幕适配+全局底部导航
flutter·开源·harmonyos
沐言人生41 分钟前
ReactNative 源码分析3——ReactActivity之初始化RN应用
android·react native
YaBingSec1 小时前
网络安全靶场WP:Grafana 任意文件读取漏洞(CVE-2021-43798)
android·笔记·安全·web安全·ssh·grafana
YF02111 小时前
彻底解决Android非SDK接口绕过限制的深度实践
android·google·app
想你依然心痛2 小时前
HarmonyOS 6(API 23)实战:打造“空间交互式AR健身私教“——基于Face AR疲劳监测 + Body AR姿态识别的沉浸光感运动系统
ar·restful·harmonyos·悬浮导航·沉浸光感
IVEN_2 小时前
Gradle 依赖下载 403 Forbidden 修复:全局镜像配置实战
android·后端
xmdy58662 小时前
Flutter+开源鸿蒙实战|校园易生活Day2 第三方库批量集成+全局Toast提示+网络状态监听+首页轮播图+资讯卡片布局
flutter·开源·harmonyos
恋猫de小郭2 小时前
Flutter 3.44 发布前夕,官方宣布 SwiftPM 将完全取代 CocoaPods
android·前端·flutter
前端不太难2 小时前
为什么说鸿蒙 App 是“状态系统”?
华为·状态模式·harmonyos
黄林晴2 小时前
重磅发布!KMP 双端订阅支付彻底封神,一套代码搞定 iOS+Android
android·kotlin