HarmonyOS自定义控件之JS进度条

开发 前端 OpenHarmony
在我们日常开发的里面,很多场景经常会用到进度条,而系统提供的进度条样式又无法满足我们的使用,这时候我们就需要自定义一个进度条。

[[422087]]

想了解更多内容,请访问:

51CTO和华为官方合作共建的鸿蒙技术社区

https://harmonyos.51cto.com

前言

在我们日常开发的里面,很多场景经常会用到进度条,而系统提供的进度条样式又无法满足我们的使用,这时候我们就需要自定义一个进度条,自定义JS进度条主要涉及以下知识点:

  • 如何自定义组件及引用
  • 如何自定义绘制图形(draw)
  • 如何创建并执行动画(animation)
  • 如何设置自定义组件的参数(setter)
  • 如何监听自定义组件的参数(getter)

效果演示

代码实现

如何自定义组件及引用

1.Js自定义组件,只需要新创建一个包,直接在里面编写界面,样式,逻辑代码即可。如果需要使用该组件,将其完整拷贝到自己的项目结构下进行引用即可。我们自定义一个Progress进度条控件,项目结构如下图:

【中软国际】HarmonyOS 自定义控件之JS进度条-鸿蒙HarmonyOS技术社区

2.使用的时候,我们需要给自定义组件进行标签声明,然后就可以使用该标签了:

<index.hml>

  1. // src表示我们引用的自定义组件的文件,name表示我们给该自定义组件声明一个标签名 
  2. <element src="../progress/progress.hml" name="progress-bar"></element> 
  3. <div class="container"
  4.     // 声明之后,就可以使用这个自定义的标签了 
  5.     <progress-bar></progress-bar> 
  6.  
  7.     ... 
  8. </div> 

如何自定义绘制图形

说到自定义绘制,自然离不开canvas,首先我们给自定义组件增加一个<canvas>标签,并在JS文件中描述绘制:

<progress.hml>

  1. <stack class="frame-layout"
  2.     <canvas id="progress-bar" class="progress-bar" ontouchmove="onTouchEvent"></canvas> 
  3.     <text if="{{ display }}" class="progress-bar">{{ progressText }}</text> 
  4. </stack> 

在JS中定义一个draw方法,并且传入一个CanvasRenderingContext2D参数,这个参数我们可以理解为canvas + paint,所有绘制都是通过它进行调用:

<progress.js>

  1. draw(ctx) { 
  2.      this.display = true 
  3.      ctx.lineWidth = this.circleWidth 
  4.      ctx.lineCap = 'round' 
  5.      // ctx可以理解为canvas + paint 
  6.      ctx.clearRect(0, 0, this.width, this.height) // 会闪屏,系统渲染问题 
  7.      ctx.save() // save1 
  8.      ctx.translate(this.width / 2, this.height / 2) 
  9.      // draw background 
  10.      ctx.beginPath() 
  11.      ctx.strokeStyle = this.backgroundColor 
  12.      ctx.arc(0, 0, 100, 0, 2 * Math.PI) // r = 100, 参数是弧度,角度 = 弧度 / PI * 180 
  13.      ctx.stroke() // 绘制 
  14.      ctx.closePath() 
  15.      // draw progress 
  16.      ctx.save() 
  17.      ctx.rotate(-90 / 180 * Math.PI) 
  18.      ctx.beginPath() 
  19.      ctx.strokeStyle = this.progressColor 
  20.      ctx.arc(0, 0, 100, 0, this.angle / 180 * Math.PI) // r = 100, 参数是弧度,角度 = 弧度 / PI * 180 
  21.      ctx.stroke() // 绘制 
  22.      ctx.closePath() 
  23.      ctx.restore() 
  24.      ctx.restore() // save1 
  25.      this.notifyChanged() 
  26.  } 

这部分逻辑并不复杂,都有注释,就是绘制一个圆环背景,然后根据进度参数在圆环上绘制一个圆弧进度,相信做过自定义控件的同学都能够非常熟悉。

如何创建并执行动画

1.首先我们需要在init的时候创建一个动画对象,并且设置好初始的动画参数:

<progress.js>

  1. onInit() { 
  2.       // 动画参数(具体参数类型和参数说明参考官方文档) 
  3.       var options = { 
  4.           duration: this.animDuration, // 动画时长 
  5.           direction: 'normal', // 播放模式 
  6.           easing: 'linear', // 差值器 
  7.           fill: 'forwards', // 动画结束后状态 
  8.           iterations: 1, // 执行次数 
  9.           begin: 0, // 起始值 
  10.           end: 360.0 // 终止值 
  11.       }; 
  12.       var _this = this 
  13.       this.animator = Animator.createAnimator(options) 
  14.       this.animator.onframe = function (value) { 
  15.           // 动画每一帧回调,类似我们熟悉的onAnimateUpdate回调 
  16.           _this.angle = value 
  17.           // 刷新绘制 
  18.           _this.draw(_this.ctx) 
  19.       } 
  20.       ... 
  21.   },  

2.接着我们需要在特定的时候开启动画,例如我们在接收到外部传进来的进度参数后,我们需要更新动画的起始值和终止值,并且开始执行动画:

<progress.js>

  1. onProgressChanged(oldV, newV) {  
  2.      console.log("onProgressChanged from:" + oldV + " to: " + newV) 
  3.      this.initWidget() 
  4.      // 进度值范围限定为[0, 1] 
  5.      if (oldV >= 1) { 
  6.          oldV = 1 
  7.      } 
  8.      if (newV >= 1) { 
  9.          newV = 1 
  10.      } 
  11.      // 更新动画的起始和终止参数 
  12.      var options = { 
  13.          duration: this.animDuration, 
  14.          direction: 'alternate-reverse'
  15.          easing: 'linear'
  16.          fill: 'forwards'
  17.          iterations: 1, 
  18.          begin: oldV * 360, 
  19.          end: newV * 360 
  20.      }; 
  21.      this.animator.update(options) 
  22.      // 开始执行动画 
  23.      this.animator.play() 
  24.  }, 

如何设置自定义组件的参数

1.我们自定义组件,并不能像之前一样简单的暴露个公开方法给外部调用。由于其数据驱动的设计,我们可以定义一些自定义属性参数,当外部修改参数时我们就可以接收到信息进行主动动作(setter):

<progress.js>

  1. props: [ 
  2.       'progress', // 进度 
  3.       'backgroundColor', // 圆环背景颜色 
  4.       'progressColor' // 进度前景颜色 
  5.   ], 
  6.   ... 

2.监听这些对外暴露的属性值变化(listener):

<progress.js>

  1. onInit() { 
  2.      ... 
  3.      // 监听自定义属性值变化 
  4.      this.$watch('progress''onProgressChanged'
  5.      this.$watch('backgroundColor''onBackgroundChanged'
  6.      this.$watch('progressColor''onForegroundChanged'
  7.      ... 
  8.  },    
  9.  // backgroundColor变化时会触发该回调 
  10.  onBackgroundChanged(oldV, newV) { 
  11.      this.backgroundColor = newV 
  12.  }, 
  13.  // progressColor变化时会触发该回调 
  14.  onForegroundChanged(oldV, newV) { 
  15.      this.progressColor = newV 
  16.  }, 
  17.  // progress变化时会触发该回调 
  18.  onProgressChanged(oldV, newV) { 
  19.      console.log("onProgressChanged from:" + oldV + " to: " + newV) 
  20.      this.initWidget() 
  21.      if (oldV >= 1) { 
  22.          oldV = 1 
  23.      } 
  24.      if (newV >= 1) { 
  25.          newV = 1 
  26.      } 
  27.      var options = { 
  28.          duration: this.animDuration, 
  29.          direction: 'alternate-reverse'
  30.          easing: 'linear'
  31.          fill: 'forwards'
  32.          iterations: 1, 
  33.          begin: oldV * 360, 
  34.          end: newV * 360 
  35.      }; 
  36.      this.animator.update(options) 
  37.      this.animator.play() 
  38.  },  

3..外部设置参数,当外部改变这些参数时,我们自定义组件内部的回调方法就会触发,并执行刷新逻辑:

<index.hml>

  1. <element src="../progress/progress.hml" name="progress-bar"></element> 
  2. <div class="container"
  3.     <progress-bar background-color="#c2f135" 
  4.                   progress-color="#6bfc33" 
  5.                   progress="{{ progress }}"
  6.     </progress-bar> 
  7.     ... 
  8. </div> 

如何监听自定义组件的参数

上面我们说到了外部如何改变自定义组件内部的属性,本质上就是一个典型观察者模式。同理,外部调用者需要监听我们自定义组件的参数变化,也是通过这种方式:

1.首先我们在自定义组件中需要定义一个被观察者对象(key),并且在该对象值变化时对外发送消息:

<progress.js>

  1. notifyChanged() { 
  2.        // currentAngle, currentProgress就是被观察者对象,key-value结构,value就是我们对外发送的值 
  3.        // 注意:驼峰命名 
  4.        this.$emit("currentAngle", this.angle) 
  5.        this.$emit("currentProgress", Math.ceil(this.angle / 3.6) / 100) 
  6.        this.progressText = Math.ceil(this.angle / 3.6) + "%" 
  7.    }, 

2.外部使用者需要注册监听回调方法,对被观察者对象(key)进行监听:

 <index.hml>

  1. <element src="../progress/progress.hml" name="progress-bar"></element> 
  2. <div class="container"
  3.     // 通过@current-angle和@current-progress进行该参数的监听,注意参数前加"@",并且参数根据驼峰命名方式拆分单词,每个词语用"-"隔开 
  4.     <progress-bar background-color="#c2f135" 
  5.                   progress-color="#6bfc33" 
  6.                   progress="{{ progress }}" 
  7.                   @current-angle="onAngleChanged" 
  8.                   @current-progress="onProgressChanged"
  9.     </progress-bar> 
  10.     ...  
  11. </div>

<index.js>

  1. // 当自定义组件内部的 currentAngle, currentProgress变化时,会触发下面的回调方法通知外部使用者 
  2.    onAngleChanged(angle) { 
  3.        console.log("onAngleChanged: " + angle.detail) 
  4.    }, 
  5.    onProgressChanged(progress) { 
  6.        console.log("onProgressChanged: " + progress.detail) 
  7.    } 

其他关键点

1.<canvas>标签的绘制内容默认是不显示的,我们可以在初始化的时候监听首帧回调,主动进行刷新一次:

<progress.js>

  1. onInit() { 
  2.     ... 
  3.     // 监听首帧,触发首次绘制,类似attachToWindow的触发时机 
  4.     requestAnimationFrame(function () { 
  5.         _this.initWidget() 
  6.         _this.draw(_this.ctx) 
  7.     }) 
  8. }, 

2.自定义组件如何获取宽高信息,在API6+系统已经提供相关的方法可以进行获取,类似onSizeChanged中读取宽高信息:

<progress.js>

  1. initWidget() { 
  2.      console.log("init widget"
  3.      if (this.ctx === null) { 
  4.          // 获取标签元素 
  5.          let widget = this.$element('progress-bar'); 
  6.          this.ctx = widget.getContext('2d', { 
  7.              antialias: true 
  8.          }) 
  9.          // 获取宽高,并计算出绘制圆环的宽高,中心点,半径信息 
  10.          this.width = widget.getBoundingClientRect().width 
  11.          this.height = widget.getBoundingClientRect().height 
  12.          this.centerX = widget.getBoundingClientRect().left + this.width / 2 
  13.          this.centerY = widget.getBoundingClientRect().top + this.height / 2 
  14.          console.log("canvas size = " + this.width + ", " + this.height) 
  15.          console.log("canvas center = " + this.centerX + ", " + this.centerY) 
  16.      } 
  17.  }, 

3.canvas画布和我们通常理解的是不同的,它是存在绘制缓存的,所以每一帧刷新时,我们需要在绘制前先清空之前的绘制内容。目前鸿蒙清空画布时会概率出现闪屏问题。

以上就是实现一个自定义JS进度条的核心代码了,源代码:JsProgress

想了解更多内容,请访问:

51CTO和华为官方合作共建的鸿蒙技术社区

https://harmonyos.51cto.com

 

责任编辑:jianghua 来源: 鸿蒙社区
相关推荐

2022-09-09 14:47:50

CircleArkUI

2021-12-30 16:10:52

鸿蒙HarmonyOS应用

2017-03-14 15:09:18

AndroidView圆形进度条

2021-12-07 18:23:50

自定义进度条分段式

2021-08-25 10:14:51

鸿蒙HarmonyOS应用

2021-08-16 14:45:38

鸿蒙HarmonyOS应用

2021-11-01 10:21:36

鸿蒙HarmonyOS应用

2022-02-17 14:51:39

HarmonyOSJSPAI开发canvas画布

2021-09-02 10:00:42

鸿蒙HarmonyOS应用

2021-08-11 14:29:20

鸿蒙HarmonyOS应用

2022-05-20 14:34:20

list组件鸿蒙操作系统

2009-12-25 17:58:12

WPF进度条

2015-08-03 11:39:20

拟物化进度条

2021-11-24 10:02:53

鸿蒙HarmonyOS应用

2022-02-21 15:16:30

HarmonyOS鸿蒙操作系统

2021-09-15 10:19:15

鸿蒙HarmonyOS应用

2021-09-27 10:43:18

鸿蒙HarmonyOS应用

2015-07-31 11:19:43

数字进度条源码

2022-06-20 15:43:45

switch开关鸿蒙

2021-10-26 10:07:02

鸿蒙HarmonyOS应用
点赞
收藏

51CTO技术栈公众号