想了解更多内容,自之请访问:
和华为官方合作共建的定义度条鸿蒙技术社区
https://harmonyos.51cto.com
在我们日常开发的里面,很多场景经常会用到进度条,控件而系统提供的自之进度条样式又无法满足我们的使用,这时候我们就需要自定义一个进度条,定义度条自定义JS进度条主要涉及以下知识点:
如何自定义组件及引用 如何自定义绘制图形(draw) 如何创建并执行动画(animation) 如何设置自定义组件的控件参数(setter) 如何监听自定义组件的参数(getter)1.Js自定义组件,只需要新创建一个包,自之直接在里面编写界面,定义度条样式,控件逻辑代码即可。自之如果需要使用该组件,定义度条将其完整拷贝到自己的控件项目结构下进行引用即可。我们自定义一个Progress进度条控件,自之项目结构如下图:
2.使用的定义度条时候,我们需要给自定义组件进行标签声明,控件然后就可以使用该标签了:
<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() }这部分逻辑并不复杂,都有注释,就是绘制一个圆环背景,然后根据进度参数在圆环上绘制一个圆弧进度,相信做过自定义控件的同学都能够非常熟悉。
1.首先我们需要在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) } ... },2.接着我们需要在特定的时候开启动画,例如我们在接收到外部传进来的进度参数后,我们需要更新动画的起始值和终止值,并且开始执行动画:
<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() },1.我们自定义组件,并不能像之前一样简单的暴露个公开方法给外部调用。由于其数据驱动的设计,我们可以定义一些自定义属性参数,当外部修改参数时我们就可以接收到信息进行主动动作(setter):
<progress.js>
props: [ progress, // 进度 backgroundColor, // 圆环背景颜色 progressColor // 进度前景颜色 ], ...2.监听这些对外暴露的亿华云计算属性值变化(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() },3..外部设置参数,当外部改变这些参数时,我们自定义组件内部的回调方法就会触发,并执行刷新逻辑:
<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>上面我们说到了外部如何改变自定义组件内部的属性,本质上就是一个典型观察者模式。同理,外部调用者需要监听我们自定义组件的参数变化,也是通过这种方式:
1.首先我们在自定义组件中需要定义一个被观察者对象(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) + "%" },2.外部使用者需要注册监听回调方法,对被观察者对象(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) }1.<canvas>标签的绘制内容默认是不显示的,我们可以在初始化的时候监听首帧回调,网站模板主动进行刷新一次:
<progress.js>
onInit() { ... // 监听首帧,触发首次绘制,类似attachToWindow的触发时机 requestAnimationFrame(function () { _this.initWidget() _this.draw(_this.ctx) }) },2.自定义组件如何获取宽高信息,在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) } },3.canvas画布和我们通常理解的是不同的,它是存在绘制缓存的,所以每一帧刷新时,我们需要在绘制前先清空之前的绘制内容。目前鸿蒙清空画布时会概率出现闪屏问题。
以上就是实现一个自定义JS进度条的核心代码了,源代码:JsProgress
想了解更多内容,请访问:
和华为官方合作共建的鸿蒙技术社区
https://harmonyos.51cto.com