根据功能需求,花了很长时间,终于完成了我的第一个小程序自定义组件的开发,用于音频播放和暂停控制,同时按钮外面的圆环用于显示播放进度。 疗效图如下:
第一个图标等待播放,第二个图标正在播放,圆环按比例显示进度。 由于是自己开发的第一个自定义组件,所以还是遇到了很多坑,记录如下:
1. 自定义组件
进度环组件有两个主要想法。 第一种是使用canvas在视口上绘制,另一种是使用CSS生成。 这两个组件我都试过了,还是觉得用canvas来控制比较好。 小程序定制部分的四个文件如下。
1.1wxml文件
一、根据调用组件上传的铃声盒大小
第一个组件是图中的播放和暂停按钮,根据状态动态显示。 根据所画线(2)的长度圆环进度条 css3,经过反复测试,尺寸比盒子小10,显示更协调。
这个理论非常有用。 经过反复比较,采用了cover-image。 使用image在地图上显示图标是有问题的,但是cover-image会影响一些参数的传输,所以要非常小心。
还要注意图像文件名的大小写。 开发工具调试的时候大小写不敏感,可以正常显示,但是在真机环境下是区分大小写的,导致我在真机上时显示异常。 暂停按钮不显示,说明上面还是认为父子组件参数传递不正常,或者子组件没有动态刷新,或者cover-image阻止了参数传递,导致解决时走了很多弯路这个问题。
第二个组件是环形背景,它是白色的,在js文件中定义
第三个组件根据播放进度在一列中显示,可以是亮色的,也可以是彩色的圆环进度条 css3,在js文件中定义
wxml文件中还有一个draw参数,该参数是从调用组件传过来的,这样当同一个页面有多个环时,可以通过ID来区分,这样就可以方便的通过控制不同的子组件父组件。
1.2wxss文件
样式文件不多,定义如下。
.circle_box,.circle_draw{ position: relative; }
.circle_bg{position: absolute;}
.circle_box{
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
}
.audioImg{
position: absolute;
}
1.3json文件
一定要把自己定义为一个组件,可以在你面前使用。
{
"component": true,
"usingComponents": {}
}
1.4js文件
程序文件注释:
1. 组件有属性和数据,并且数据仅在生命周期的attached中加载一次。 可以根据父组件的参数更改属性。 该子组件有4个属性参数,分别是画板元素代码draw、显示比例per、直径r,默认为30px,无论是否播放。
子组件的数据数据不会直接随父组件改变。 要更改它,需要获取子组件的id并通过init进行更改。
2. 圆圈动态变化。 该子组件中定义了一个 updateCircle 函数,用于更新圆显示的比例。
/* components/circle/circle.js */
Component({
options: {
multipleSlots: true // 在组件定义时的选项中启用多slot支持
},
properties: {
draw: {//画板元素名称id
type: String,
value: 'draw'
},
per:{ //百分比 通过此值转换成step
type: String,
value: '0'
},
r:{//半径
type: String,
value: '30'
},
isPlay:{//是否播放状态
type: Boolean,
value: false,
},
},
data: { /* 私有数据,可用于模版渲染 */
step: 1, //用来算圆的弧度0-2
size:0, //画板大小
screenWidth:750, //实际设备的宽度
txt:0,
},
methods: {
/**
* el:画圆的元素
* r:圆的半径
* w:圆的宽度
* 功能:画背景
*/
drawCircleBg: function (el, r, w) {
const ctx = wx.createCanvasContext(el,this);
ctx.setLineWidth(w);// 设置圆环的宽度
ctx.setStrokeStyle('#E5E5E5'); // 设置圆环的颜色
ctx.setLineCap('round') // 设置圆环端点的形状
ctx.beginPath();//开始一个新的路径
ctx.arc(r, r, r - w, 0, 2 * Math.PI, false);
//设置一个原点(110,110),半径为100的圆的路径到当前路径
ctx.stroke();//对当前路径进行描边
ctx.draw();
},
/**
* el:画圆的元素
* r:圆的半径
* w:圆的宽度
* step:圆的弧度 (0-2)
* 功能:彩色圆环
*/
drawCircle: function (el, r, w, step) {
var context = wx.createCanvasContext(el,this);
// 设置渐变
var gradient = context.createLinearGradient(2 * r, r, 0);
gradient.addColorStop("0", "#ff3f39");
gradient.addColorStop("0.5", "#ff3f39");
gradient.addColorStop("1.0", "#ff3f39");
context.setLineWidth(w);
context.setStrokeStyle(gradient);
context.setLineCap('round')
context.beginPath();//开始一个新的路径
// step 从0到2为一周
context.arc(r, r, r - w, -Math.PI / 2, step * Math.PI - Math.PI / 2, false);
context.stroke();//对当前路径进行描边
context.draw()
},
updateCircle: function (per) {
let _this = this;
//初始化
let el = _this.data.draw; //画板元素
let r = Number(_this.data.r); //圆形半径
let step = (2 * Number(per)) / 100;
let rpx = (_this.data.screenWidth / 750) * r;
const w = 2;//圆形的宽度
//组件入口,调用下面即可绘制 背景圆环和彩色圆环。
_this.drawCircle(el, rpx, w, step);//绘制 彩色圆环
},
},
lifetimes: {
// 生命周期函数,可以为函数,或一个在methods段中定义的方法名
attached: function () {
const _this = this;
//获取屏幕宽度
wx.getSystemInfo({
success: function(res) {
_this.setData({
screenWidth: res.windowWidth
});
},
});
//初始化
const el = _this.data.draw; //画板元素
const per = _this.data.per; //圆形进度
const r = Number(_this.data.r); //圆形半径
_this.setData({
step: (2 * Number(_this.data.per)) / 100,
txt: _this.data.per
});
//获取屏幕宽度(并把真正的半径px转成rpx)
let rpx = (_this.data.screenWidth / 750) * r;
//计算出画板大小
this.setData({
size: rpx * 2
});
const w = 2;//圆形的宽度
//组件入口,调用下面即可绘制 背景圆环和彩色圆环。
_this.drawCircleBg(el + 'bg', rpx, w);//绘制 背景圆环
_this.drawCircle(el, rpx, w, _this.data.step);//绘制 彩色圆环
}
},
})
2、子组件引用2.1json中的语句
首先在json文件中声明引用组件,如下所示
{
"navigationBarTitleText": "景区导游",
"usingComponents": {
"circle": "/pages/components/circle/circle"
}
}
2.2 wxml中的引用
1.这里用在地图上,用cover-view引用
2、本页面只引用一次,draw采用静态定义,定义了per和isplay两个动态参数。
3.特别注意,因为viewport的关系,请将bindtap定义在outer cover-view上,而不是在circle上。 点击圆圈非常有效,而且经常没有反应。 这个坑坑了我好久。
2.3 同一页面的多次引用
此页面使用循环和多个引用。 id 使用公园代码作为后缀。 速率和状态控制需要判断当前播放是否是公园的音频。
3. 子组件显示比例控制
项目中分别使用了wx.createInnerAudioContext和wx.getBackgroundAudioManager两种模式。 我觉得背景音乐比较容易控制,虽然同时只能有一个音频。以下是createInnerAudioContext的控制案例
3.1. 创建音频页面常量
由于需要播放多个音频,因此创建一个页面常量,防止随机播放多个音频。
//创建音频上下文
constmyAudio=wx.createInnerAudioContext();
3.2 音频初始化与销毁
onLoad: function (e) {
var that = this;
const version = wx.getSystemInfoSync().SDKVersion;
if (util.compareVersion(version, '2.3.0') >= 0) {
wx.setInnerAudioOption({
obeyMuteSwitch: false
})
}else{
wx.showModal({
title: '提示',
content: '当前微信版本过低,静音模式下可能会导致播放音频失败。'
})
}
that.initInnerAudioContext();
},
// 页面卸载时停止播放
onUnload() {
//停止播放
//停止播放
myAudio.stop();
myAudio.destroy();
},
3.3 播放和暂停按钮响应
1、通过以下方法获取被点击的组件,方便对被点击的组件进行操作。
varid=event.currentTarget.id
that.data.currentCircle=that.selectComponent('#myCircle'+id);
2、注意:播放完毕后,延迟获取paused变量,以保证onTimeUpdate能够生效。 这个问题也坑了我很久。
//播放与暂停音频,
playAudio: function (event) {
var that = this
if (that.data.currentCircle != ""){
that.data.currentCircle.updateCircle(0);
}
//获取点击事件的组件,以便于准确操作
var id = event.currentTarget.id
that.data.currentCircle = that.selectComponent('#myCircle'+id);
//切换时如果在另一景区在播放状态下,也切换音乐重新播放
if (that.data.isPlay && event.currentTarget.id == that.data.playId ){
myAudio.pause();
this.setData({
isPlay: false,
playId: id
});
}else{
var src = OSS_PREFIX + 'scenicfile/' + app.globalData.adcode + '/' + id + '/000.mp3'
myAudio.src = src
myAudio.play()
//延时0.5秒读取一下paused,否则后面的onTimeUpdate不会生效
setTimeout(() => {
console.log(myAudio.paused)
}, 500)
that.setData({
isPlay: true,
playId: id,
});
}
},
3.4 音频状态监控
内容很多,程序里有详细的注释。 onTimeUpdate 风暴动态更新
initInnerAudioContext(src) {
let that = this;
// 监听音频播放事件
myAudio.onPlay(function () {
console.log('onPlay');
that.setData({
isplay: true
});
});
// 监听音频播放进度更新事件
myAudio.onTimeUpdate(() => {
let per = myAudio.currentTime/myAudio.duration * 100
//console.log(per)
if (isNaN(per)) {
that.data.currentCircle.updateCircle(0); // 通过实例调用组件事件
that.setData({
per: 0,
});
}else {
that.data.currentCircle.updateCircle(per); // 通过实例调用组件事件
that.setData({
per: per,
});
};
});
myAudio.onWaiting(function () {
console.log('onWaiting');
//myAudio.pause(); //这个方法有问题,真机上不需求,会有时不自动播放问题,调试机没有问题
that.setData({
isloading: true
});
});
myAudio.onCanplay(function () {
console.log('onCanplay')
that.setData({
isloading: false
});
//确保调用 后能自动播放
myAudio.play();
});
// 监听音频暂停事件
myAudio.onPause(function () {
console.log('onPause');
that.setData({
isplay: false
});
});
// 监听音频停止事件
myAudio.onStop(function () {
console.log('onStop');
that.setData({
isplay: false
});
myAudio.seek(0);
});
// 监听音频自然播放至结束的事件
myAudio.onEnded(function () {
console.log('onEnded');
that.setData({
isplay: false
});
myAudio.seek(0);
});
// 监听音频播放错误事件
myAudio.onError(err => {
console.log('监听音频播放错误事件', err, myAudio.src);
that.setData({
isplay: false,
isloading: false
});
wx.showToast({
icon: 'none',
title: '音频播放出错!',
});
});
},
四。 概括
这是我的第一个自定义小程序组件。 网上没有完整的参考案例。 有些案例使用了框架,所以不太容易直接引用。 我遇到了很多陷阱,在这个过程中我想放弃。 主要如下:
1、父子组件参数传递,本例使用属性和自定义方法
2、onTimeUpdate不起作用的问题,小程序的音乐播放器组件有bug,问题很多,网上也没有完整的答案,所以只能自己反复测试,最后就可以了是完美的。