【中软国际】HarmonyOS 自定义控件之JS进度条 原创 精华
中软国际AIoT开发者社区
发布于 2021-8-26 15:23
浏览
9收藏
前言
在我们日常开发的里面,很多场景经常会用到进度条,而系统提供的进度条样式又无法满足我们的使用,这时候我们就需要自定义一个进度条,自定义JS进度条主要涉及以下知识点:
- 如何自定义组件及引用
- 如何自定义绘制图形(draw)
- 如何创建并执行动画(animation)
- 如何设置自定义组件的参数(setter)
- 如何监听自定义组件的参数(getter)
效果演示
代码实现
如何自定义组件及引用
-
Js自定义组件,只需要新创建一个包,直接在里面编写界面,样式,逻辑代码即可。如果需要使用该组件,将其完整拷贝到自己的项目结构下进行引用即可。我们自定义一个
Progress
进度条控件,项目结构如下图:
-
使用的时候,我们需要给自定义组件进行标签声明,然后就可以使用该标签了:
<index.hml>
// src表示我们引用的自定义组件的文件,name表示我们给该自定义组件声明一个标签名
<element src="../progress/progress.hml" name="progress-bar"></element>
<div class="container">
// 声明之后,就可以使用这个自定义的标签了
<progress-bar></progress-bar>
...
</div>
如何自定义绘制图形
说到自定义绘制,自然离不开canvas
,首先我们给自定义组件增加一个<canvas>
标签,并在JS
文件中描述绘制:
<progress.hml>
<stack class="frame-layout">
<canvas id="progress-bar" class="progress-bar" ontouchmove="onTouchEvent"></canvas>
<text if="{{ display }}" class="progress-bar">{{ progressText }}</text>
</stack>
在JS中定义一个draw
方法,并且传入一个CanvasRenderingContext2D
参数,这个参数我们可以理解为canvas + paint
,所有绘制都是通过它进行调用:
<progress.js>
draw(ctx) {
this.display = true
ctx.lineWidth = this.circleWidth
ctx.lineCap = 'round'
// ctx可以理解为canvas + paint
ctx.clearRect(0, 0, this.width, this.height) // 会闪屏,系统渲染问题
ctx.save() // save1
ctx.translate(this.width / 2, this.height / 2)
// draw background
ctx.beginPath()
ctx.strokeStyle = this.backgroundColor
ctx.arc(0, 0, 100, 0, 2 * Math.PI) // r = 100, 参数是弧度,角度 = 弧度 / PI * 180
ctx.stroke() // 绘制
ctx.closePath()
// draw progress
ctx.save()
ctx.rotate(-90 / 180 * Math.PI)
ctx.beginPath()
ctx.strokeStyle = this.progressColor
ctx.arc(0, 0, 100, 0, this.angle / 180 * Math.PI) // r = 100, 参数是弧度,角度 = 弧度 / PI * 180
ctx.stroke() // 绘制
ctx.closePath()
ctx.restore()
ctx.restore() // save1
this.notifyChanged()
}
这部分逻辑并不复杂,都有注释,就是绘制一个圆环背景,然后根据进度参数在圆环上绘制一个圆弧进度,相信做过自定义控件的同学都能够非常熟悉。
如何创建并执行动画
- 首先我们需要在
init
的时候创建一个动画对象,并且设置好初始的动画参数:
<progress.js>
onInit() {
// 动画参数(具体参数类型和参数说明参考官方文档)
var options = {
duration: this.animDuration, // 动画时长
direction: 'normal', // 播放模式
easing: 'linear', // 差值器
fill: 'forwards', // 动画结束后状态
iterations: 1, // 执行次数
begin: 0, // 起始值
end: 360.0 // 终止值
};
var _this = this
this.animator = Animator.createAnimator(options)
this.animator.onframe = function (value) {
// 动画每一帧回调,类似我们熟悉的onAnimateUpdate回调
_this.angle = value
// 刷新绘制
_this.draw(_this.ctx)
}
...
},
- 接着我们需要在特定的时候开启动画,例如我们在接收到外部传进来的进度参数后,我们需要更新动画的起始值和终止值,并且开始执行动画:
<progress.js>
onProgressChanged(oldV, newV) {
console.log("onProgressChanged from:" + oldV + " to: " + newV)
this.initWidget()
// 进度值范围限定为[0, 1]
if (oldV >= 1) {
oldV = 1
}
if (newV >= 1) {
newV = 1
}
// 更新动画的起始和终止参数
var options = {
duration: this.animDuration,
direction: 'alternate-reverse',
easing: 'linear',
fill: 'forwards',
iterations: 1,
begin: oldV * 360,
end: newV * 360
};
this.animator.update(options)
// 开始执行动画
this.animator.play()
},
如何设置自定义组件的参数
- 我们自定义组件,并不能像之前一样简单的暴露个公开方法给外部调用。由于其数据驱动的设计,我们可以定义一些自定义属性参数,当外部修改参数时我们就可以接收到信息进行主动动作(setter):
<progress.js>
props: [
'progress', // 进度
'backgroundColor', // 圆环背景颜色
'progressColor' // 进度前景颜色
],
...
- 监听这些对外暴露的属性值变化(listener):
<progress.js>
onInit() {
...
// 监听自定义属性值变化
this.$watch('progress', 'onProgressChanged')
this.$watch('backgroundColor', 'onBackgroundChanged')
this.$watch('progressColor', 'onForegroundChanged')
...
},
// backgroundColor变化时会触发该回调
onBackgroundChanged(oldV, newV) {
this.backgroundColor = newV
},
// progressColor变化时会触发该回调
onForegroundChanged(oldV, newV) {
this.progressColor = newV
},
// progress变化时会触发该回调
onProgressChanged(oldV, newV) {
console.log("onProgressChanged from:" + oldV + " to: " + newV)
this.initWidget()
if (oldV >= 1) {
oldV = 1
}
if (newV >= 1) {
newV = 1
}
var options = {
duration: this.animDuration,
direction: 'alternate-reverse',
easing: 'linear',
fill: 'forwards',
iterations: 1,
begin: oldV * 360,
end: newV * 360
};
this.animator.update(options)
this.animator.play()
},
- 外部设置参数,当外部改变这些参数时,我们自定义组件内部的回调方法就会触发,并执行刷新逻辑:
<index.hml>
<element src="../progress/progress.hml" name="progress-bar"></element>
<div class="container">
<progress-bar background-color="#c2f135"
progress-color="#6bfc33"
progress="{{ progress }}">
</progress-bar>
...
</div>
如何监听自定义组件的参数
上面我们说到了外部如何改变自定义组件内部的属性,本质上就是一个典型观察者模式。同理,外部调用者需要监听我们自定义组件的参数变化,也是通过这种方式:
- 首先我们在自定义组件中需要定义一个被观察者对象(key),并且在该对象值变化时对外发送消息:
<progress.js>
notifyChanged() {
// currentAngle, currentProgress就是被观察者对象,key-value结构,value就是我们对外发送的值
// 注意:驼峰命名
this.$emit("currentAngle", this.angle)
this.$emit("currentProgress", Math.ceil(this.angle / 3.6) / 100)
this.progressText = Math.ceil(this.angle / 3.6) + "%"
},
- 外部使用者需要注册监听回调方法,对被观察者对象(key)进行监听:
<index.hml>
<element src="../progress/progress.hml" name="progress-bar"></element>
<div class="container">
// 通过@current-angle和@current-progress进行该参数的监听,注意参数前加"@",并且参数根据驼峰命名方式拆分单词,每个词语用"-"隔开
<progress-bar background-color="#c2f135"
progress-color="#6bfc33"
progress="{{ progress }}"
@current-angle="onAngleChanged"
@current-progress="onProgressChanged">
</progress-bar>
...
</div>
<index.js>
// 当自定义组件内部的 currentAngle, currentProgress变化时,会触发下面的回调方法通知外部使用者
onAngleChanged(angle) {
console.log("onAngleChanged: " + angle.detail)
},
onProgressChanged(progress) {
console.log("onProgressChanged: " + progress.detail)
}
其他关键点
<canvas>
标签的绘制内容默认是不显示的,我们可以在初始化的时候监听首帧回调,主动进行刷新一次:
<progress.js>
onInit() {
...
// 监听首帧,触发首次绘制,类似attachToWindow的触发时机
requestAnimationFrame(function () {
_this.initWidget()
_this.draw(_this.ctx)
})
},
- 自定义组件如何获取宽高信息,在
API6+
系统已经提供相关的方法可以进行获取,类似onSizeChanged
中读取宽高信息:
<progress.js>
initWidget() {
console.log("init widget")
if (this.ctx === null) {
// 获取标签元素
let widget = this.$element('progress-bar');
this.ctx = widget.getContext('2d', {
antialias: true
})
// 获取宽高,并计算出绘制圆环的宽高,中心点,半径信息
this.width = widget.getBoundingClientRect().width
this.height = widget.getBoundingClientRect().height
this.centerX = widget.getBoundingClientRect().left + this.width / 2
this.centerY = widget.getBoundingClientRect().top + this.height / 2
console.log("canvas size = " + this.width + ", " + this.height)
console.log("canvas center = " + this.centerX + ", " + this.centerY)
}
},
canvas
画布和我们通常理解的是不同的,它是存在绘制缓存的,所以每一帧刷新时,我们需要在绘制前先清空之前的绘制内容。目前鸿蒙清空画布时会概率出现闪屏问题。
以上就是实现一个自定义JS进度条的核心代码了,源代码:JsProgress
- 作者:卢日见
更多原创内容请关注:中软国际 HarmonyOS 技术学院
入门到精通、技巧到案例,系统化分享HarmonyOS开发技术,欢迎投稿和订阅,让我们一起携手前行共建鸿蒙生态。
©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
赞
13
收藏 9
回复
相关推荐
很实用
实用的小工具