Qt自绘实现圆角渐变弧形进度条
一、效果图
二、前因
由于项目上需要做一个圆弧形进度条(设计师出图是带有圆角渐变的圆弧形进度条),开始一看设计图,就估计这个图可能做不了,后面项目上其他人都比较忙,而且都不太会Qt,我自己在网上找了一圈,有很多自绘的弧形进度条控件,比如仪表盘控件,但是这些控件代码都不能满足我的项目需要(没有圆角),所以被逼着没办法,自能自己下想办法实现。
三、效果图拆解
根据效果图上显示,圆弧控件有一圈外边框,边框内部颜色是渐变色(上图的颜色被修改过,所以看着有点不爽)。进度条是顺时针样式,起始位置和结束位置是圆角样式,进度条的起始位置颜色比较深,结束位置是一个图标(效果图上我是绘制的两个同心圆),颜色比较鲜。
四、解决过程
①开始时一直考虑Qt是否提供了圆弧形控件,或者有没有什么办法可以把线形进度条控件弄弯,结果以失败告终。
②在网上有一个QRoundProgressBar控件,是一位大神写的,并且公开源码可以下载使用。这个代码下载下来测试了一下,发现并没有圆角部分,中间也没有渐变色部分实现,没有想要的效果,最终放弃这个代码。
③考虑是否有什么神奇的QSS代码可以实现,结果以失败告终。
④听说用Qt中Echarts图标库实现这个非常方便,但由于没有做过Echarts,同时也不知道在Qt中如何加载控制Echarts控件,所以这种方式最终放弃。
⑤在网上看到可以用Qt的QML开速开发绘制这样的异形控件,最后同样由于没有做过QML二放弃这样的开发方式。
⑥最后根据网上各路大神的总结,Qt控件基本上都是可以用自绘的方式实现想要的效果,所以,最后尝试自己想办法绘制这个圆角渐变圆弧进度条。
五、绘制过程拆解
①首先圆弧边框,可以用Qt的QPainter绘制两个同心圆,可以通过设置QPainterPath,将绘制的两个同心圆变成两个同心扇形圆,可以设置绘制圆形的半径和圆弧线条的颜色。
②两个同心圆的接口处可以绘制两个半圆弧,绘制成封口的样式(模拟进度条的圆角效果)。
③Qt的QPainter可以通过设置QRadialGradient绘制渐变色圆,通过设置QPainterPath,可以将绘制的渐变圆改成绘制渐变扇形圆弧。
④最后绘制一个遮罩在圆内,圆的颜色和背景色相同(看上去效果就是中间什么都没有,实际中间是绘制了一个圆圈)。
六、详细绘制过程
在上面绘制过程拆解中提到绘制圆形线条,圆中间不进行填充,但是在实际绘制中,发现可以绘制不填充的圆形,也可以绘制填充的圆形,为了调用接口单一,下面的详细绘制中都是采用绘制填充圆的方式绘制的。
①首先初始化必要的变量,比如绘制过程拆解中提到的两个同心圆的半径,颜色等值,进度条的起始角度和结束角度值等,详细变量如下所示,后续绘制代码中,会直接使用这些变量:
//进度条的起始角度
int startAngle=-45;
//进度条的结束角度
int endAngle=225;
//进度条圆环宽度
int ringWidth=20;
//进度图外圆的半径
int maxRadius=110;
//进度条进度图标半径
int iconWidth=15;
//当前进度条所在的角度
int currAngle=270;
//进度条边框颜色
QRgb circularBorderColor=qRgb(120,120,120);
//进度条内圆遮罩颜色
QRgb insideMaskColor=qRgb(68,68,68);
//进度条渐变开始颜色
QRgb startGradientColor=qRgb(80,80,80);
//进度条渐变结束颜色
QRgb endGradientColor=qRgb(150,140,20);
②绘制之前先做一些准备工作,由于Qt的坐标系不是控件中间为原点,所以如果不调整QPainter的圆心的话,绘制的图形将会在控件的角落里,所以首先调整QPainter的原点位置,代码如下:
//重载Qt控件的paintEvent函数,进行绘图
//初始化变量
QPainter painter(this);
// 右移1位 相当于width()/2
painter.translate(width() >> 1, height() >> 1);
//启动反锯齿
painter.setRenderHint(QPainter::Antialiasing);
③绘制进度条的扇形外圆,代码如下:
//函数调用代码
GradientArc(painter,maxRadius,circularBorderColor);
//函数实现代码-这个函数是有几个地方调用,所以是单独提出来的
void QRoundProgressBar::GradientArc(QPainter &painter,int radius,QRgb color)
{
//渐变色
QRadialGradient gradient(0, 0, radius);
gradient.setColorAt(0, color);
gradient.setColorAt(1.0, color);
painter.setBrush(gradient);
QRectF rect(-radius, -radius, radius << 1, radius << 1);
QPainterPath path;
path.arcTo(rect, startAngle, endAngle-startAngle);
painter.setPen(Qt::NoPen);
painter.drawPath(path);
}
效果图如下:
④计算两个圆角(两个小圆)的圆形位置,绘制两个实心圆(最开始设计的时候是计划绘制两个实心半圆,但是由于大圆起始角度和结束角度计算两个小半圆的起始角度和结束角度很难,所以改成绘制两个实心圆,下一步中的渐变圆弧填充会将两个小圆的一半颜色覆盖掉,只要覆盖的颜色和被覆盖的颜色相同,效果看上去就像是绘制了两个半圆在那个接口上),代码如下所示:
//调用代码如下-这里0表示是绘制小圆外圆-1表示绘制小圆内圆(为了绘制圆角边框)
DrawTwoSmallCircle(painter,0);
//实现代码如下-这个代码也是共有代码,会有两个地方调用:
void QRoundProgressBar::DrawTwoSmallCircle(QPainter& painter,int type)
{
//计算小圆坐标
QPoint rightCircle(0,0);
QPoint leftCircle(0,0);
rightCircle.setY(-(qSin(startAngle*M_PI/180)*(maxRadius-ringWidth/2)));
rightCircle.setX(qCos(startAngle*M_PI/180)*(maxRadius-ringWidth/2)+1);
leftCircle.setX(-rightCircle.rx());
leftCircle.setY(rightCircle.ry());
if(type==0){
painter.drawEllipse(rightCircle,ringWidth/2,ringWidth/2);
painter.drawEllipse(leftCircle,ringWidth/2,ringWidth/2);
}
else{
painter.drawEllipse(rightCircle,ringWidth/2-1,ringWidth/2-1);
painter.drawEllipse(leftCircle,ringWidth/2-1,ringWidth/2-1);
}
}
效果图如下:
⑤将大圆半径减小1个像素,绘制一个非渐变色扇形圆弧,用于显示进度条的底色(进度条空白区域的颜色),代码如下所示:
//调用代码如下
GradientArc(painter,maxRadius-1,startGradientColor);
//实现代码如下-其实实现代码和上面的一样,只是传递的参数不同:
void QRoundProgressBar::GradientArc(QPainter &painter,int radius,QRgb color)
{
//渐变色
QRadialGradient gradient(0, 0, radius);
gradient.setColorAt(0, color);
gradient.setColorAt(1.0, color);
painter.setBrush(gradient);
QRectF rect(-radius, -radius, radius << 1, radius << 1);
QPainterPath path;
path.arcTo(rect, startAngle, endAngle-startAngle);
painter.setPen(Qt::NoPen);
painter.drawPath(path);
}
效果图如下:
⑥将小圆半径-1,在两个实现小圆的坐标基础上重新绘制两个实心小圆,代码如下所示:
//调用代码如下,1表示绘制小圆内圆
DrawTwoSmallCircle(painter,1);
//实现代码如下-这个代码和上面的绘制代码相同,只是参数不一样:
void QRoundProgressBar::DrawTwoSmallCircle(QPainter& painter,int type)
{
//计算小圆坐标
QPoint rightCircle(0,0);
QPoint leftCircle(0,0);
rightCircle.setY(-(qSin(startAngle*M_PI/180)*(maxRadius-ringWidth/2)));
rightCircle.setX(qCos(startAngle*M_PI/180)*(maxRadius-ringWidth/2)+1);
leftCircle.setX(-rightCircle.rx());
leftCircle.setY(rightCircle.ry());
if(type==0){
painter.drawEllipse(rightCircle,ringWidth/2,ringWidth/2);
painter.drawEllipse(leftCircle,ringWidth/2,ringWidth/2);
}
else{
painter.drawEllipse(rightCircle,ringWidth/2-1,ringWidth/2-1);
painter.drawEllipse(leftCircle,ringWidth/2-1,ringWidth/2-1);
}
}
效果如下所示:
⑦在底⑤基础上,绘制渐变圆,代码如下所示:
//调用代码如下所示:
DrawGradientCircle(painter,maxRadius-1);
//实现代码如下所示:
void QRoundProgressBar::DrawGradientCircle(QPainter& painter,int radius)
{
QConicalGradient conicalGradient(QPointF(0, 0), endAngle+1);
conicalGradient.setColorAt((360-qAbs(endAngle-startAngle)+currAngle)/360.0, endGradientColor);
conicalGradient.setColorAt(1, startGradientColor);
painter.setBrush(conicalGradient);
QPainterPath path;
QRectF rect(-radius, -radius, radius << 1, radius << 1);
path.arcTo(rect, startAngle+currAngle, endAngle-startAngle-currAngle);
painter.setPen(Qt::NoPen);
painter.drawPath(path);
}
效果图如下所示(需要点击一下“测试按钮”,后面给出测试按钮代码):
⑧将大圆半径减去中间圆弧宽度,绘制一个和大圆颜色相同的同心圆(目的是为了实现圆弧进度条内部线条,这里采用绘制填充圆的方式,可以直接绘制一个非填充圆),代码如下所示:
//调用代码如下:
GradientArc(painter,maxRadius-ringWidth+1,circularBorderColor);
//实现代码如下,实现代码和上面的代码一样
void QRoundProgressBar::GradientArc(QPainter &painter,int radius,QRgb color)
{
// 渐变色
QRadialGradient gradient(0, 0, radius);
gradient.setColorAt(0, color);
gradient.setColorAt(1.0, color);
painter.setBrush(gradient);
QRectF rect(-radius, -radius, radius << 1, radius << 1);
QPainterPath path;
path.arcTo(rect, startAngle, endAngle-startAngle);
painter.setPen(Qt::NoPen);
painter.drawPath(path);
}
效果图如下所示:
⑨此时,圆角渐变弧形进度条已经有一个大概样子了,现在只需要将中间灰色部分的颜色和背景色绘制成一样即可,即是绘制一个遮罩圆,代码如下所示:
//调用代码如下:
QPointF point(0,0);
GradientFullArc(painter,point,maxRadius-ringWidth,insideMaskColor);
//实现函数如下,这个函数也是共有函数:
void QRoundProgressBar::GradientFullArc(QPainter &painter,QPointF& point,int radius,QRgb color)
{
//渐变色
QRadialGradient gradient(0, 0, radius);
gradient.setColorAt(0, color);
gradient.setColorAt(1.0, color);
painter.setBrush(gradient);
painter.drawEllipse(point,radius,radius);
}
效果图如下所示:
⑩最后在进度条的头头上绘制一个当前进度标志的圆圈(为了凸显效果,可以绘制成两个同心圆),代码如下所示:
//调用代码如下所示:
QPointF point(0,0);
point.setX(point.x()+qCos((startAngle+currAngle)*M_PI/180)*(maxRadius-ringWidth/2));
point.setY(point.y()-qSin((startAngle+currAngle)*M_PI/180)*(maxRadius-ringWidth/2));
GradientFullArc(painter,point,iconWidth,endGradientColor);
GradientFullArc(painter,point,iconWidth*0.6,startGradientColor);
//实现代码如下,和上面的代码一样:
void QRoundProgressBar::GradientFullArc(QPainter &painter,QPointF& point,int radius,QRgb color)
{
//渐变色
QRadialGradient gradient(0, 0, radius);
gradient.setColorAt(0, color);
gradient.setColorAt(1.0, color);
painter.setBrush(gradient);
painter.drawEllipse(point,radius,radius);
}
效果图如下所示:
⑪“测试”按钮的代码
currAngle=qrand()%(qAbs(endAngle-startAngle))+startAngle;
update();
七、最后总结
①由于现在这个是的进度是计算角度实现进度的,可用稍微修改一下,变成设置进度条最小值、最大值和当前值实现进度条,对应的需要将值和角度做一个对应映射计算,这个太简单了,这里就不给出来了。
②上面描述的是绘制的整个过程,源码中,给出了设置开始角度、结束角度、边框颜色,渐变颜色等一系列参数设置函数,可以根据自身需要设置不同的值,实现不同的效果。
③整个工程的演示效果可以在我上传的资源中下载,注意,那是一个exe运行程序,不是源代码,地址如下:
https://download.csdn.net/download/youyicc/12740274
需要源代码的可以加群联系作者,群二维码如下:
上一篇: Android Jetpack-Navigation
下一篇: Python 深拷贝 浅拷贝
推荐阅读