做微信小程序的朋友大都接觸過或自己動手寫過自定義組件,最近我一直都在忙微信小程序的項目,這裏我分享一個項目中自己寫的一個環形進度條。
廢話不多說,先上效果圖:
是不是你想要的效果呢?下面說一下這麼個東東是咋整出來的吧!
先看下項目結構
ProgressView文件夾裏面就是環形進度條的主要代碼了!
先說下思路。整這麼個東東,毫無疑問肯定得用到畫布,畫的東西也不復雜,畫一個底環,上面覆蓋一個當前進度的弧形就行了,至於中間的文字用css定位居中就好了。思路有了,那麼,盤它~
創建自定義組件
先看看佈局代碼:
ProgressView.wxml
<view class="canvasView">
<canvas canvas-id="circleBar" style="width:{{width}}rpx;height:{{height}}rpx;">
</canvas>
<text hidden="{{!showText}}" style="font-size:{{textSize}}rpx;color:{{textColor}}">{{percentage}}%</text>
</view>
ProgressView.wxss
.canvasView{
display: flex;
align-items: center;
justify-content: center;
position: relative;
}
text{
position: absolute;
}
佈局很簡單,一個view裏面放一個canvas組件和一個text組件就好了,canvas組件用來繪製環形進度條,text組件用來展示中間的進度百分比文字。然後可以看到畫布的寬高都是引用了屬性,並且通過屬性控制了是否顯示中間的文字,及文字大小顏色,這些怎麼操作後面再說。
接下來就開始在畫布上面繪製進度條了。這裏有個問題就是,畫布的相關API的單位全都是px,爲了屏幕適配我們應該先把單位轉換一下:
var windWidth = wx.getSystemInfoSync().windowWidth;//屏幕寬度
//由於canvas的單位的px,爲了適配所有屏幕,這裏之前所有像素單位賦值之前都要換成以rpx爲單位的大小乘以xs
const xs = windWidth / 750;
這裏首先獲取屏幕的寬度windWidth ,windWidth 單位是px,然後用windWidth/750計算出每個物理單位佔用了多少像素,之後用戶就可以直接按照UI設計圖上的大小填寫rpx爲單位的數值,我們把每個數值都乘以xs在賦值給畫布的API就好了。至於這裏windWidth爲什麼是除以750?是因爲小程序框架規定屏幕寬就是750rpx。不信可以去試試,無論什麼分辨率手機,你把一個組件的寬度設爲750rpx他它正好是一個屏幕的寬度。這裏就不多說,網上有很多解釋,有興趣取百度一下。
接下來我們在ProgressView.js文件中先添加幾個屬性:
/**
* 組件的屬性列表
*/
properties: {
//畫布的寬度 單位rpx
canvasWidth: {
type: Number,
value: 400
},
//線條寬度 默認16,單位rpx
lineWidth: {
type: Number,
value: 16
},
//線條顏色 默認"#E3AF6A"
lineColor: {
type: String,
value: "#E3AF6A"
},
//進度條底色
bottomColor: {
type: String,
value: "#FFF9F1"
},
//當前的值
value: {
type: Number,
value: 36
},
//最大值 默認100
maxValue: {
type: Number,
value: 100
},
//是否顯示中間進度值文字
showText: {
type: Boolean,
value: true
},
//中間字體大小,單位rpx
textSize: {
type: Number,
value: 60
},
//中間字體顏色
textColor: {
type: String,
value: "#E3AF6A"
}
}
屬性也不少,具體用來幹嘛的我就不細說了,註釋寫的很清楚。然後寫代碼開始繪製圖形了。
一步步來,首先我們繪製一個底環,在ProgressView.js文件中添加繪製圓環的方法drawProgressBar:
/**
* 繪製環形進度條
*/
drawProgressBar(){
var canvasWidth = this.data.canvasWidth
//設置畫布寬高
this.setData({
width: canvasWidth,
height: canvasWidth
})
//作畫
var circle_r = canvasWidth * xs / 2; //畫布的一半,用來找中心點和半徑
var bottomColor = this.data.bottomColor //進度條底色
var lineColor = this.data.lineColor; //線條顏色
var lineWidth = this.data.lineWidth * xs; //線條寬度
//獲取canvas 的繪圖上下文
var ctx = this.data.ctx
if (!ctx) {
ctx = wx.createCanvasContext("circleBar", this); //創建 canvas 的繪圖上下文 CanvasContext 對象
this.setData({
ctx: ctx
})
}
ctx.translate(circle_r, circle_r);//改變座標原點的位置,將原點當做圓心作畫
//繪製底色圓弧
ctx.beginPath();//開始創建一個路徑
ctx.setStrokeStyle(bottomColor);//設置描邊顏色
ctx.setLineWidth(lineWidth);//設置線條的寬度
ctx.arc(0, 0, circle_r-lineWidth/2, 0, Math.PI*2, false);//創建一條弧線
ctx.setLineCap('round') //末端圓弧
ctx.stroke();//畫出當前路徑的邊框
ctx.closePath();//關閉一個路徑
//計算中間進度文字的值
var maxValue = this.data.maxValue; //最大值
var value = this.data.value; //當前的值
//更新進度值
this.setData({
percentage: parseInt(value / maxValue * 100)
})
ctx.draw(); //清空上次內容繪製本次內容
}
上面的代碼用來繪製底環,基本上每一句代碼我都寫了註釋,大家應該是能夠看懂的,唯一要說一下的就是Math.PI,這個Math.PI是是個什麼意思,這個只在這裏用來設置繪製的弧度大小,大家看下圖
我這裏設置Math.PI*2其實就是代表繪製整個圓的路徑,還有上面用到的API大家不瞭解的可以去翻翻官方文檔。
這段代碼繪製出來的效果圖如下:
好,這樣我們的底環也就繪製完了,接下來繪製當前進度的圓環就好了,有了繪製底環的經驗,我們依葫蘆畫瓢加上一段繪製當前進度圓環的代碼,修改drawProgressBar方法:
/**
* 繪製環形進度條
*/
drawProgressBar() {
var canvasWidth = this.data.canvasWidth
//設置畫布寬高
this.setData({
width: canvasWidth,
height: canvasWidth
})
//作畫
var circle_r = canvasWidth * xs / 2; //畫布的一半,用來找中心點和半徑
var bottomColor = this.data.bottomColor //進度條底色
var lineColor = this.data.lineColor; //線條顏色
var lineWidth = this.data.lineWidth * xs; //線條寬度
//獲取canvas 的繪圖上下文
var ctx = this.data.ctx
if (!ctx) {
ctx = wx.createCanvasContext("circleBar", this); //創建 canvas 的繪圖上下文 CanvasContext 對象
this.setData({
ctx: ctx
})
}
ctx.translate(circle_r, circle_r);//改變座標原點的位置,將原點當做圓心作畫
//繪製底色圓弧
ctx.beginPath();//開始創建一個路徑
ctx.setStrokeStyle(bottomColor);//設置描邊顏色
ctx.setLineWidth(lineWidth);//設置線條的寬度
ctx.arc(0, 0, circle_r - lineWidth / 2, 0, Math.PI * 2, false);//創建一條弧線
ctx.setLineCap('round') //末端圓弧
ctx.stroke();//畫出當前路徑的邊框
ctx.closePath();//關閉一個路徑
//計算中間進度文字的值
var maxValue = this.data.maxValue; //最大值
var value = this.data.value; //當前的值
//更新進度值
this.setData({
percentage: parseInt(value / maxValue * 100)
})
//計算當前進度弧形大小,環形總長度爲Math.PI*2
var percent = (Math.PI * 2) * (value / maxValue)
//當前進度的圓弧
ctx.beginPath();//開始創建一個路徑
ctx.setStrokeStyle(lineColor);//設置描邊顏色
ctx.setLineWidth(lineWidth);//設置線條的寬度
ctx.arc(0, 0, circle_r - lineWidth / 2, -0.5 * Math.PI, percent - 0.5 * Math.PI, false);//創建一條弧線
ctx.setLineCap('round') //末端圓弧
ctx.stroke();//畫出當前路徑的邊框
ctx.closePath();//關閉一個路徑
ctx.draw(); //清空上次內容繪製本次內容
}
加上了計算當前進度的弧度大小,並繪製當前進度的代碼,效果圖如下:
好了基本的東西我們已經全部整完了,可以滿足部分人的需求了,但有些同學需要的不是整個圓的,而是半個圓或者2/3個圓的進度條,怎麼辦?有了上面的經驗,其實我們只需要改變繪製圓環的弧度大小就好了,但我這裏不想寫死,給他設置一個屬性,用戶自己設置需要繪製多大的圓弧,這裏我們添加一個屬性radian,最小值爲0,最大值爲1,代表繪製一個圓的弧度也就是360°
//進度條的弧度大小,最小值爲0,最大值爲1,默認爲1代表繪製一個圓的弧度也就是360°
radian:{
type: Number,
value: 1
}
看看修改後的代碼:
/**
* 繪製環形進度條
* @param value 當前進度值
* @param maxValue 進度最大值
*/
drawProgressBar(value, maxValue) {
var radian=this.data.radian//弧度大小
radian=radian>1?1:radian //控制最大值爲1
radian=radian<0?0:radian //控制最小值爲0
//作畫
var bottomColor = this.data.bottomColor //進度條底色
var lineColor = this.data.lineColor; //線條顏色
var lineWidth = this.data.lineWidth * xs; //線條寬度
var canvasWidth = this.data.canvasWidth//畫布的寬
var canvasHeight=(Math.cos((1-radian)*Math.PI)+1)*canvasWidth/2+lineWidth //計算畫布的高度
//設置畫布寬高,高最低是進度框的半徑
this.setData({
width: canvasWidth,
height: radian<0.5?canvasWidth/2:canvasHeight
})
//計算弧形的起點,這是爲了保證起點和終點在同一水平線上
var startPath=Math.PI*(3/2-radian)
var endPath=startPath+2*Math.PI*radian
//獲取canvas 的繪圖上下文
var ctx = this.data.ctx
if (!ctx) {
ctx = wx.createCanvasContext("circleBar", this); //創建 canvas 的繪圖上下文 CanvasContext 對象
this.setData({
ctx: ctx
})
}
var circle_r = canvasWidth * xs / 2; //畫布的一半,用來找中心點和半徑
ctx.translate(circle_r, circle_r);//改變座標原點的位置,將原點當做圓心作畫
//繪製底色圓弧
ctx.beginPath();//開始創建一個路徑
ctx.setStrokeStyle(bottomColor);//設置描邊顏色
ctx.setLineWidth(lineWidth);//設置線條的寬度
ctx.arc(0, 0, circle_r - lineWidth / 2, startPath, endPath, false);//創建一條弧線
ctx.setLineCap('round') //末端圓弧
ctx.stroke();//畫出當前路徑的邊框
ctx.closePath();//關閉一個路徑
//計算中間進度文字的值
var maxValue = maxValue!=null?maxValue:this.data.maxValue; //最大值
var value =value!=null?value: this.data.value; //當前的值
//更新進度值
this.setData({
percentage: parseInt(value / maxValue * 100)
})
//計算當前進度弧形大小,環形總長度爲Math.PI*2
var percent = 2*Math.PI*radian* (value / maxValue)
//當前進度的圓弧
ctx.beginPath();//開始創建一個路徑
ctx.setStrokeStyle(lineColor);//設置描邊顏色
ctx.setLineWidth(lineWidth);//設置線條的寬度
ctx.arc(0, 0, circle_r - lineWidth / 2, startPath, percent +startPath, false);//創建一條弧線
ctx.setLineCap('round') //末端圓弧
ctx.stroke();//畫出當前路徑的邊框
ctx.closePath();//關閉一個路徑
ctx.draw(); //清空上次內容繪製本次內容
}
上面的代碼我修改了幾處地方,加了兩個參數,
* @param value 當前值
* @param maxValue 最大值
考慮到很多進度最大值並不是100,也許是其他數值,所以這裏加了兩個參數,maxValue傳你進度的最大值(不傳默認是100),value傳進度的當前值,通過這兩個參數計算出進度值的百分比顯示出來。
還一個是添加了通過自定義的radian屬性(最小值爲0,最大值爲1,默認爲1代表繪製一個圓的弧度也就是360°)繪製不同弧度大小的圓弧。這裏最關鍵的是怎麼就計算出圓弧的起始點和結束點:
//計算弧形的起點,這是爲了保證起點和終點在同一水平線上
var startPath=Math.PI*(3/2-radian) //起始點
var endPath=startPath+2*Math.PI*radian //結束點
這個起始點和結束點怎麼來的大家畫個圖自己算一下也就出來了,不復雜。
起始點一個半圓的大小就是PI,知道了radian屬性值就可以計算出起始點了,Math.PI - Math.PI*(radian-1/2)就是起始點,知道了起始點,起始點加上總進度大小就是結束點了startPath+2*Math.PI*radian。說的可能有點抽象,自己拿筆畫個圖算一下也不難。
使用
在需要使用進進度條界面的json文件引用組件
{
"usingComponents": {
"progressView": "/static/component/ProgressView/ProgressView"
}
}
在wxml界面加上組件
<progressView id="progressView2" canvasWidth="500" radian="{{3/4}}"/>
<view class="buttonView">
<button bindtap="minus">-10</button>
<button bindtap="plus">+10</button>
</view>
我這裏設置了組件寬度爲500rpx,進度框弧度大小爲3/4個圓的大小,其他屬性都用默認的並加了兩個按鈕加進度和減進度。
頁面的js代碼:
index.js
var value=0
Page({
data: {
},
/**
* 加
*/
plus:function(){
if(value<100){
value+=10
//this.progressView.showCanvasRing(value, 100); //繪製環形進度
this.progressView2.drawProgressBar(value, 100); //繪製環形進度
}
},
/**
* 減
*/
minus: function() {
if (value >0) {
value-=10
//this.progressView.showCanvasRing(value, 100); //繪製環形進度
this.progressView2.drawProgressBar(value, 100); //繪製環形進度
}
},
onLoad: function() {
//this.progressView = this.selectComponent("#progressView");
this.progressView2 = this.selectComponent("#progressView2");
//this.progressView.showCanvasRing(value, 100); //繪製環形進度
this.progressView2.drawProgressBar(value, 100); //繪製環形進度
},
})
寫了加進度和減進度的方法,我們看看效果:
效果還行,可用。
但這裏還有個小問題,當用戶設置屬性radian爲1,也就是進度條是整個圓時,那起始點的方向只能從六點鐘開始,如圖:
這樣的話還不太友好,我們再加個屬性,用來控制當前進度值的起始點位置。
優化
修改後的js代碼
ProgressView.js
var app = getApp();
var windWidth = wx.getSystemInfoSync().windowWidth;//屏幕寬度
//由於canvas的單位的px,爲了適配所有屏幕,這裏之前所有像素單位賦值之前都要換成以rpx爲單位的大小乘以xs
const xs = windWidth / 750;
Component({
/**
* 組件的屬性列表
*/
properties: {
//畫布的寬度 單位rpx
canvasWidth: {
type: Number,
value: 400
},
//進度條的弧度大小,最小值爲0,最大值爲1,默認爲1代表繪製一個圓的弧度也就是360°
radian: {
type: Number,
value: 1 / 2
},
//線條寬度 默認16,單位rpx
lineWidth: {
type: Number,
value: 16
},
//線條顏色 默認"#E3AF6A"
lineColor: {
type: String,
value: "#E3AF6A"
},
//進度條底色
bottomColor: {
type: String,
value: "#FFF9F1"
},
//當前的值
value: {
type: Number,
value: 36
},
//最大值 默認100
maxValue: {
type: Number,
value: 100
},
//當前進度值的方向,只在radian爲1時起作用,可填left(九點鐘方向開始),top(12點鐘方向開始),bottom(6點鐘方向開始),right(3點鐘方向開)
direction: {
type: String,
value: "top"
},
//是否顯示中間進度值文字
showText: {
type: Boolean,
value: true
},
//中間字體大小,單位rpx
textSize: {
type: Number,
value: 60
},
//中間字體顏色
textColor: {
type: String,
value: "#E3AF6A"
}
},
/**
* 組件的初始數據
*/
data: {
ctx: null,
width: 400,
height: 400,
percentage: 0 //中間百分比的值
},
/**
* 組件的方法列表
*/
methods: {
/**
* 繪製環形進度條
* @param value 當前進度值
* @param maxValue 進度最大值
*/
drawProgressBar(value, maxValue) {
var radian = this.data.radian//弧度大小
radian=radian>1?1:radian //控制最大值爲1
radian=radian<0?0:radian //控制最小值爲0
//作畫
var bottomColor = this.data.bottomColor //進度條底色
var lineColor = this.data.lineColor; //線條顏色
var lineWidth = this.data.lineWidth * xs; //線條寬度
var canvasWidth = this.data.canvasWidth//畫布的寬
var canvasHeight = (Math.cos((1 - radian) * Math.PI) + 1) * canvasWidth / 2 + lineWidth //計算畫布的高度
//設置畫布寬高,高最低是進度框的半徑
this.setData({
width: canvasWidth,
height: radian < 0.5 ? canvasWidth / 2 : canvasHeight
})
//計算弧形的起點,這是爲了保證起點和終點在同一水平線上
var startPath = Math.PI * (3 / 2 - radian)
var endPath = startPath + 2 * Math.PI * radian
//獲取canvas 的繪圖上下文
var ctx = this.data.ctx
if (!ctx) {
ctx = wx.createCanvasContext("circleBar", this); //創建 canvas 的繪圖上下文 CanvasContext 對象
this.setData({
ctx: ctx
})
}
var circle_r = canvasWidth * xs / 2; //畫布的一半,用來找中心點和半徑
ctx.translate(circle_r, circle_r);//改變座標原點的位置,將原點當做圓心作畫
//繪製底色圓弧
ctx.beginPath();//開始創建一個路徑
ctx.setStrokeStyle(bottomColor);//設置描邊顏色
ctx.setLineWidth(lineWidth);//設置線條的寬度
ctx.arc(0, 0, circle_r - lineWidth / 2, startPath, endPath, false);//創建一條弧線
ctx.setLineCap('round') //末端圓弧
ctx.stroke();//畫出當前路徑的邊框
ctx.closePath();//關閉一個路徑
//計算中間進度文字的值
var maxValue = maxValue != null ? maxValue : this.data.maxValue; //最大值
var value = value != null ? value : this.data.value; //當前的值
//更新進度值
this.setData({
percentage: parseInt(value / maxValue * 100)
})
//計算當前進度弧形大小,環形總長度爲Math.PI*2
var percent = 2 * Math.PI * radian * (value / maxValue)
var currStartPath = startPath //當前進度值的起點位置
//如果radian爲1則使用自定義的起始位置
if (radian == 1) {
var direction = this.data.direction//當前進度值
switch (direction) {
case 'left':
currStartPath =Math.PI //九點鐘方向
break
case 'top':
currStartPath = 3 / 2 * Math.PI //十二點鐘方向
break
case 'right':
currStartPath = 0 //三點鐘方向
break
case 'left':
currStartPath = 1 / 2 * Math.PI //六點鐘方向
break
}
}
//當前進度的圓弧
ctx.beginPath();//開始創建一個路徑
ctx.setStrokeStyle(lineColor);//設置描邊顏色
ctx.setLineWidth(lineWidth);//設置線條的寬度
ctx.arc(0, 0, circle_r - lineWidth / 2, currStartPath, percent + currStartPath, false);//創建一條弧線
ctx.setLineCap('round') //末端圓弧
ctx.stroke();//畫出當前路徑的邊框
ctx.closePath();//關閉一個路徑
ctx.draw(); //清空上次內容繪製本次內容
}
}
})
這裏增加了direction屬性控制起點方向,並控制該屬性只在radian爲1時起作用,比如我想要將進度條設置成圓環,並且起點方向從九點鐘方向開始,則在組件設置radian爲1,direction爲 left 就好了:
<progressView id="progressView2" canvasWidth="500" radian="1" direction="left"/>
效果圖:
好了,自定義一個基本的環形進度條大致就是這樣了,應該能滿足一些需求了,有些人可能還需要更加個性化一點,由於不同的場景有不同的需求,我這裏沒法一一實現,大家可以下載源碼在上面進行調整。
源碼地址: