Qt QWidget 简约美观的加载动画 第四季

😊 第四季来啦 😊

效果如下:

只有三个文件,可以直接编译运行的

cpp 复制代码
//main.cpp
#include "LoadingAnimWidget.h"
#include <QApplication>
#include <QVBoxLayout>
#include <QGridLayout>
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    QWidget w;
    w.setWindowTitle("加载动画 第四季");
    QGridLayout * mainLayout = new QGridLayout;
    auto* anim1= new JellyInBox;
    mainLayout->addWidget(anim1,0,0);

    auto* anim2 = new HorizontalDNA;
    mainLayout->addWidget(anim2,0,1);

    auto * anim3 = new  LoopedRingWithText;
    mainLayout->addWidget(anim3,1,0);

    auto* anim4 = new Radar;
    mainLayout->addWidget(anim4,1,1);

    w.setLayout(mainLayout);
    w.show();
    anim1->start();
    anim2->start();
    anim3->start();
    anim4->start();
    return a.exec();
}
cpp 复制代码
//LoadingAnimWidget.h
#ifndef LOADINGANIMWIDGET_H
#define LOADINGANIMWIDGET_H
#include <QPropertyAnimation>
#include <QWidget>
class LoadingAnimBase:public QWidget
{
    Q_OBJECT
    Q_PROPERTY(qreal angle READ angle WRITE setAngle)
public:
    LoadingAnimBase(QWidget* parent=nullptr);
    virtual ~LoadingAnimBase();

    qreal angle()const;
    void setAngle(qreal an);
public slots:
    virtual void exec();
    virtual void start();
    virtual void stop();
protected:
    QPropertyAnimation mAnim;
    qreal mAngle;
};

class JellyInBox:public LoadingAnimBase{//一个会伸缩的果冻在一个小盒子里面,一开始拉伸,然后移动,然后收缩
public:
    explicit JellyInBox(QWidget* parent = nullptr);
protected:
    void paintEvent(QPaintEvent *event);
};
class HorizontalDNA:public LoadingAnimBase{//两根水平方向的小球链,螺旋滚动,像DNA双分子链
public:
    explicit HorizontalDNA(QWidget* parent = nullptr);
protected:
    void paintEvent(QPaintEvent*);
private:
    void getPosYAlphaScale(const int amplitude,const qreal & offset,qreal & y,qreal & alpha,qreal & scale);
    //根据原始偏移获取当前时间点下的y轴坐标,透明度,和缩放比例
};
class LoopedRingWithText:public LoadingAnimBase{//外面有三圈圆环转动,中间有一个高亮的灯光从左向右移动照亮字符串的一部分
public:
    explicit LoopedRingWithText(QWidget* parent = nullptr);
protected:
    void paintEvent(QPaintEvent*);
};
class Radar:public LoadingAnimBase{//像一个雷达一样扫描,等待耗时操作的结束
public:
    explicit Radar(QWidget* parent = nullptr);
protected:
    void paintEvent(QPaintEvent*);
};

#endif // LOADINGANIMWIDGET_H
cpp 复制代码
//LoadingAnimWidget.cpp
#include "LoadingAnimWidget.h"
#include <QDebug>
#include <QPaintEvent>
#include <QPainter>
#include <QtMath>
LoadingAnimBase::LoadingAnimBase(QWidget* parent):QWidget(parent){
    mAnim.setPropertyName("angle");
    mAnim.setTargetObject(this);
    mAnim.setDuration(2000);
    mAnim.setLoopCount(-1);//run forever
    mAnim.setEasingCurve(QEasingCurve::Linear);
    setFixedSize(200,200);
    mAngle = 0;
}
LoadingAnimBase::~LoadingAnimBase(){}
void LoadingAnimBase::exec(){
    if(mAnim.state() == QAbstractAnimation::Stopped){
        start();
    }
    else{
        stop();
    }
}
void LoadingAnimBase::start(){
    mAnim.setStartValue(0);
    mAnim.setEndValue(360);
    mAnim.start();
}
void LoadingAnimBase::stop(){
    mAnim.stop();
}
qreal LoadingAnimBase::angle()const{ return mAngle;}
void LoadingAnimBase::setAngle(qreal an){
    mAngle = an;
    update();
}
JellyInBox::JellyInBox(QWidget* parent):LoadingAnimBase (parent){}
void JellyInBox::paintEvent(QPaintEvent *event){
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing);
    painter.setBrush(Qt::NoBrush);

    int x = this->width();
    const int y = this->height();

    //先画一个盒子
    QPen pen("black");
    pen.setWidth(3);
    painter.setPen(pen);
    painter.drawRoundedRect(QRectF(1,0.4375*y,x-2,y/8),y/16.0,y/16.0);

    //画果冻
    pen.setCapStyle(Qt::RoundCap);
    painter.setBrush(Qt::NoBrush);

    const int gap = 4;//果冻和盒子之间的间距
    const int boxGap = 1;//盒子的外间距
    const qreal jh = y/8 - gap*2;//果冻高度
    pen.setWidthF(jh);
    painter.setPen(pen);

    painter.translate(boxGap + gap + jh/2,y/2);
    x -= (boxGap + gap) * 2 + jh;//可以移动的x范围是总宽度 - 两边的两个间距 - 笔头厚度
    const qreal maxw = 0.4*x;//最大的果冻宽度
    int jx = 0;
    qreal jw = 0;

    if(mAngle < 90){
        jw = maxw * mAngle / 90;
    }
    else if(mAngle < 270){
        jw = maxw;
        jx = (x - maxw) * (mAngle - 90) / 180;
    }
    else{
        jx = x - maxw + (mAngle - 270) / 90 * maxw;
        jw = x - jx;
    }
    painter.drawLine(jx,0,jx+jw,0);
}
HorizontalDNA::HorizontalDNA(QWidget* parent):LoadingAnimBase (parent){
    mAnim.setDuration(5000);
}
void HorizontalDNA::getPosYAlphaScale(const int amplitude,const qreal & offset,qreal & y,qreal & alpha,qreal & scale){
    static const qreal a = 2*3.1415926 / 360;
    //qreal x = a*mAngle + offset;//随着时间的流逝,x变大,y踩着着右边的值,看上去就是右边的小球带动左侧的小球移动
    //如果要实现左边的小球带动右侧的小球,要这样
    auto x = -a * mAngle + offset;
    y = qSin(x);
    auto tmp = qCos(x); //随着时间的流逝,x变小,当小球向上移动的时候,透明度变小
    tmp += 1;
    tmp /= 2;
    alpha = 1- tmp;
    scale = alpha / 2 + 0.5;//不要太小了,最小是0.5
    y *= amplitude;//amplitude就是振幅 sin(x)的振幅是1
}
void HorizontalDNA::paintEvent(QPaintEvent*){
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing);
    painter.setPen(Qt::NoPen);

    const int x = width();
    const qreal radius = x / 20.0;//小球的半径
    const qreal y = height();
    const qreal unitx = x / 8.0;//小球的间距
    painter.translate(unitx,y/2);

    static const qreal gap = M_PI / 6;
    static const qreal offsetList[7] = {gap*0,gap*1,gap*2,gap*3,gap*4,gap*5,gap*6};
    QColor ballColor("black");
    for(int i = 0;i < 7;++i){
        qreal bally ,alpha,scale;
        getPosYAlphaScale(y/6,offsetList[i],bally,alpha,scale);
        ballColor.setAlphaF(alpha);
        painter.setBrush(QBrush(ballColor));
        painter.drawEllipse(QPointF(unitx*i,bally),radius*scale,radius*scale);
    }
    static const qreal offsetList2[7] = {gap*6,gap*7,gap*8,gap*9,gap*10,gap*11,0};
    for(int i = 0;i < 7;++i){
        qreal bally ,alpha,scale;
        getPosYAlphaScale(y/6,offsetList2[i],bally,alpha,scale);
        ballColor.setAlphaF(alpha);
        painter.setBrush(QBrush(ballColor));
        painter.drawEllipse(QPointF(unitx*i,bally),radius*scale,radius*scale);
    }
}
LoopedRingWithText:: LoopedRingWithText(QWidget* parent):LoadingAnimBase(parent){
    mAnim.setDuration(6000);
}
void  LoopedRingWithText::paintEvent(QPaintEvent*){
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing);
    const int x = width();
    const int y = height();
    //step0:定义一些颜色变量,也可以改成成员变量
    static const QColor bright("turquoise");//最亮的圆环颜色
    static const QColor medium = QColor(bright.red(),bright.green(),bright.blue(),200);
    static const QColor dim = QColor(bright.red(),bright.green(),bright.blue(),160);

    //step1:画一个深色的背景
    painter.setPen(Qt::NoPen);
    painter.setBrush(QBrush(QColor("dimgray")));
    painter.drawRoundedRect(this->rect(),x/12,x/12);
    //step2:画最外面的两个圆环
    painter.translate(x/2,y/2);
    QPen pen(dim);
    pen.setWidth(4);
    pen.setCapStyle(Qt::RoundCap);
    painter.setPen(pen);
    const auto rect1 = QRectF(-0.45*x,-0.45*y,0.9*x,0.9*y);

    static const qreal start = 30;
    static const qreal span = 120;
    auto ang = mAngle;
    painter.rotate(ang);
    painter.drawArc(rect1,start * 16,span * 16);
    painter.drawArc(rect1,(start+180) *16,span * 16);
    painter.resetTransform();

    //step3:画中间的两个圆环
    pen.setColor(medium);
    painter.setPen(pen);
    painter.translate(x/2,y/2);
    const auto rect2 = QRectF(-0.375*x,-0.375*y,0.75*x,0.75*y);
    if(ang > 180) ang -= 180;//周期改成180,比外面的转的快一倍
    painter.rotate(-2*ang);
    painter.drawArc(rect2,start * 16,span * 16);
    painter.drawArc(rect2,(start + 180)*16,span*16);
    painter.resetTransform();

    //step4:画内部的4个圆环
    pen.setColor(bright);
    painter.setPen(pen);
    painter.translate(x/2,y/2);
    const auto rect3 = QRectF(-0.3*x,-0.3*y,0.6*x,0.6*y);
    ang = mAngle;
    static const qreal t3 = 120;//周期改成120,比中间的更快
    while(ang > t3) ang -= t3;
    painter.rotate( ang * 90 / t3);//四个圆环转过90度的时候也就是长短交替一次,要把这个转过的角度乘以调整系数:90/120
    qreal start0 = 15 + (90-15) * (ang / t3);//开始的时候上方圆环起点在15度,变化到90度时弧长为0
    qreal span0 = 180 - 2*start0;
    if(span0 > 0){
        //第一组
        painter.drawArc(rect3,start0*16 , span0*16);
        painter.drawArc(rect3,(start0+180)*16,span0*16);
    }
    start0 = -75 * (ang / t3);//开始的时候,右侧圆环起点是0,弧长为0,它的最大弧长时刻,起点在-75度
    span0 = -start0 * 2;
    if(span0 > 0){
        //第二组
        painter.drawArc(rect3,start0*16,span0*16);
        painter.drawArc(rect3,(start0+180)*16,span0*16);
    }
    painter.resetTransform();

    QFont font("Microsoft YaHei",12,4);
    painter.setFont(font);
    QFontMetrics fm(font);
    static const QString text("Loading...");
    qreal textw = fm.horizontalAdvance(text);
    qreal texth = fm.height();
    QPainterPath pp;
    pp.addText(QPointF( (x - textw)/2,0.5*y + texth/4),font,text);
    painter.setClipPath(pp);
    painter.setPen(Qt::NoPen);
    painter.setBrush(QBrush(dim));
    //step5: 先画一个淡色的文本
    painter.drawRect(rect());
    //step6: 最后一步啦,画一个圆灯光
    painter.translate(0,0.5*y);
    const qreal unit = x / 360.0;
    QRadialGradient grad(QPointF(mAngle*unit,0),texth);
    grad.setColorAt(0,bright);
    grad.setColorAt(1,dim);
    painter.setBrush(grad);
    painter.drawEllipse(QPointF(mAngle * unit,0),texth,texth);
}
Radar::Radar(QWidget* parent):LoadingAnimBase(parent){}
void Radar::paintEvent(QPaintEvent*){
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing);
    //step1: 画一个淡色背景,很多简约的界面都需要透明背景,这一步可以删掉
    painter.setPen(Qt::NoPen);
    painter.setBrush(QBrush("deepskyblue"));
    const int x = width();
    const int y = height();
    painter.drawRoundedRect(rect(),x/12,x/12);
    //step2: 画一个雷达
    painter.translate(x/2,y/2);
    QConicalGradient gra(QPointF(0,0),45);
    static const QColor color0("white");
    static const QColor color1(color0.red(),color0.green(),color0.blue(),100);
    gra.setColorAt(0,color0);
    gra.setColorAt(0.2,color1);
    gra.setColorAt(1,"transparent");
    painter.setBrush(gra);
    painter.rotate(mAngle);
    const auto rect = QRectF(-x/4,-y/4,x/2,y/2);
    painter.drawPie(rect,45*16,90*16);

}
相关推荐
zwjapple15 分钟前
typescript里面正则的使用
开发语言·javascript·正则表达式
小五Five16 分钟前
TypeScript项目中Axios的封装
开发语言·前端·javascript
前端每日三省18 分钟前
面试题-TS(八):什么是装饰器(decorators)?如何在 TypeScript 中使用它们?
开发语言·前端·javascript
凡人的AI工具箱31 分钟前
15分钟学 Go 第 60 天 :综合项目展示 - 构建微服务电商平台(完整示例25000字)
开发语言·后端·微服务·架构·golang
chnming19871 小时前
STL关联式容器之map
开发语言·c++
进击的六角龙1 小时前
深入浅出:使用Python调用API实现智能天气预报
开发语言·python
檀越剑指大厂1 小时前
【Python系列】浅析 Python 中的字典更新与应用场景
开发语言·python
湫ccc1 小时前
Python简介以及解释器安装(保姆级教学)
开发语言·python
程序伍六七1 小时前
day16
开发语言·c++
wkj0011 小时前
php操作redis
开发语言·redis·php