微信小程序自定义环形进度条

       做微信小程序的朋友大都接触过或自己动手写过自定义组件,最近我一直都在忙微信小程序的项目,这里我分享一个项目中自己写的一个环形进度条。

      废话不多说,先上效果图:

     

     是不是你想要的效果呢?下面说一下这么个东东是咋整出来的吧!

     先看下项目结构

      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 'bottom':
            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

  • 9
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值