微信小程序自定義環形進度條

       做微信小程序的朋友大都接觸過或自己動手寫過自定義組件,最近我一直都在忙微信小程序的項目,這裏我分享一個項目中自己寫的一個環形進度條。

      廢話不多說,先上效果圖:

     

 

     是不是你想要的效果呢?下面說一下這麼個東東是咋整出來的吧!

 

     先看下項目結構

      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"/>

效果圖:

       好了,自定義一個基本的環形進度條大致就是這樣了,應該能滿足一些需求了,有些人可能還需要更加個性化一點,由於不同的場景有不同的需求,我這裏沒法一一實現,大家可以下載源碼在上面進行調整。

 

源碼地址:

https://github.com/954469291/ProgressView.git

發佈了8 篇原創文章 · 獲贊 6 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章