😃 第二季来啦 😃
简约的加载动画,用于网络查询等耗时操作时给用户的提示.
这是最终效果:
一共只有三个文件,可以直接编译运行
//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 BouncingBalls;
mainLayout->addWidget(anim1,0,0);
auto* anim2 = new SlinkyCircle;
mainLayout->addWidget(anim2,0,1);
w.setLayout(mainLayout);
w.show();
anim1->start();
anim2->start();
return a.exec();
}
//LoadingAnimWidget.h
#ifndef LOADINGANIMWIDGET_H
#define LOADINGANIMWIDGET_H
#include <QPropertyAnimation>
#include <QWidget>
class LoadingAnimBase:public QWidget
{
Q_OBJECT
public:
LoadingAnimBase(QWidget* parent=nullptr);
virtual ~LoadingAnimBase();
public slots:
virtual void exec();
virtual void start();
virtual void stop();
protected:
QPropertyAnimation mAnim;
qreal mAngle;
};
class BouncingBalls:public LoadingAnimBase//一蹦一蹦的小球
{
Q_OBJECT
Q_PROPERTY(qreal angle READ angle WRITE setAngle)
public:
explicit BouncingBalls(QWidget* parent = nullptr);
qreal angle()const;
void setAngle(qreal an);
protected:
void paintEvent(QPaintEvent *event);
};
class SlinkyCircle:public LoadingAnimBase{
//可以伸缩的管子,绕着中心转动,就像弹簧圈,英语叫做slinky,查了好久才查到这个单词,有点强迫症😂
Q_OBJECT
Q_PROPERTY(qreal angle READ angle WRITE setAngle)
public:
explicit SlinkyCircle(QWidget* parent = nullptr);
qreal angle()const;
void setAngle(qreal an);
protected:
void paintEvent(QPaintEvent *event);
};
#endif // LOADINGANIMWIDGET_H
//LoadingAnimWidget.cpp
#include "LoadingAnimWidget.h"
#include <QDebug>
#include <QPaintEvent>
#include <QPainter>
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();
}
BouncingBalls::BouncingBalls(QWidget* parent):LoadingAnimBase (parent){
}
qreal BouncingBalls::angle()const{return mAngle;}
void BouncingBalls::setAngle(qreal an){
mAngle = an;
update();
}
void BouncingBalls::paintEvent(QPaintEvent *event){
//这个动画和角度无关,但还是保留了之前的360度,因为360这个数字很好,除以谁都除的清
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.setPen(Qt::NoPen);
static const QStringList colorList{"yellow","orangered","lime","coral","steelblue"};
const int x = width();
const int y = height();
const int d = 0.1*x; //小球的直径
static const int startAngle[5] = {0,45,90,135,180};//五个小球启动时间点
static const qreal maxHeight = 0.25*y;//小球蹦起来的最大高度
painter.translate(1.0/6 * x - d/2, 0.5 * y);//最左边的小球的静止坐标是 (1/6*x , 0.6*y),画圆的起点是左上角,所以x坐标还要减去半径
for(int i = 0;i < 5;++i){
const auto start = startAngle[i];
qreal delta = mAngle - start;
painter.setBrush(QBrush(QColor(colorList[i])));
if(delta > 0 && delta < 180){//每个小球蹦起来的持续时间是4个单位,每个单位是45
if(delta > 90) delta = 180 - delta;
painter.drawEllipse(QRectF(0,-maxHeight * delta / 90.0,d,d));
}
else{//原地不动
painter.drawEllipse(QRectF(0,0,d,d));
}
painter.translate(x/6.0,0);
}
}
SlinkyCircle::SlinkyCircle(QWidget* parent):LoadingAnimBase (parent){
mAnim.setEasingCurve(QEasingCurve::InOutCubic);
}
qreal SlinkyCircle::angle()const{ return mAngle;}
void SlinkyCircle::setAngle(qreal an){
mAngle = an;
update();
}
void SlinkyCircle::paintEvent(QPaintEvent *event){
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.setPen(Qt::NoPen);
//画一个番茄色的背景
painter.setBrush(QBrush(QColor("tomato")));
painter.drawRoundedRect(this->rect(),8,8);
//画圆弧
painter.setBrush(Qt::NoBrush);
const int x = this->width();
const int y = this->height();
QPen pen("white");
pen.setCapStyle(Qt::RoundCap);
pen.setWidth(x/20);
painter.setPen(pen);
painter.translate(x/2,y/2);
static const qreal spanAngle = 45;//mAngle<=45,要把弧线拉伸出来
static const qreal shrinkAngle = 360-spanAngle;//mAngle==315时,要把弧线收缩起来
auto arcRect = this->rect().adjusted(x/5,y/5,-x/5,-y/5);
arcRect.translate(-x/2,-y/2);
static const int direction = -1;//顺时针
if(mAngle < spanAngle){
painter.drawArc(arcRect,90 * 16,mAngle * 16 * direction);
}
else{//弧长是固定的
//40 - 320 --> 320 , 280 --> 320
if(mAngle > shrinkAngle){
painter.drawArc(arcRect,90 * 16,-(360-mAngle)*16 * direction);
}
else{
//我转动的角度是当前角度 - 拉伸门槛,因为有收尾的不动的时间段,占据了一段角度,所以要把转动的角度拉伸一些,
//这个比例就是 (360-spanAngle) / (shrinkAngle - spanAngle)
const auto delta = (mAngle - spanAngle) * (360-spanAngle) / (shrinkAngle - spanAngle);
painter.rotate(-delta * direction);
painter.drawArc(arcRect,90 * 16,spanAngle * 16 * direction);
}
}
}