主页 > 人工智能  > 

Qt简约美观的加载动画小沙漏风格第六季


这次和大家分享一个沙漏风格的加载动画 效果如下:

这是本系列的第六季了, 本次内容的关键在于cubicTo函数的使用, 在这里分享一个非常好用的网站https://www.desmos.com/calculator/cahqdxeshd 在这上面可以手动拖动贝塞尔曲线的控制点, 并且显示了起终点和两个控制点的精确坐标, 这样来使用qt的cubicTo函数就非常方便了.

一共三个文件,可以直接编译运行

//main.cpp #include "LoadingAnimWidget.h" #include <QApplication> #include <QGridLayout> int main(int argc, char *argv[]) { QApplication a(argc, argv); QWidget w; w.setWindowTitle("加载动画 第6季"); QGridLayout * mainLayout = new QGridLayout; auto* anim1= new FillGlassBead; mainLayout->addWidget(anim1,0,0); auto* anim2 = new FillGlassBead; anim2->setWaveType(FillGlassBead::WaveType::SwayingWater); mainLayout->addWidget(anim2,0,1); auto* anim3 = new Hourglass; mainLayout->addWidget(anim3,1,0); auto* anim4 = new Hourglass; anim4->setColumnVisibility(true); anim4->setSandColor("seagreen"); mainLayout->addWidget(anim4,1,1); w.setLayout(mainLayout); w.show(); anim1->start(); anim2->start(); anim3->start(); anim4->start(); return a.exec(); } //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 FillGlassBead:public LoadingAnimBase{ public: FillGlassBead(QWidget* parent = nullptr);//一颗玻璃珠,内部逐渐充满液体 enum class WaveType{ PeacefulWater /*平静的水面*/ , SwayingWater /*左右晃动的水面*/ }; void setWaveType(WaveType t); protected: void paintEvent(QPaintEvent*); private: WaveType mWaveType; }; class Hourglass:public LoadingAnimBase{ public: Hourglass(QWidget* parent = nullptr);//沙漏 void setSandColor(const QColor& color);//设置沙子颜色 void setColumnVisibility(bool vis);//设置柱子可见性 protected: void paintEvent(QPaintEvent*); private: QColor mSandColor; bool mColumnVisible; }; #endif // LOADINGANIMWIDGET_H //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(); } FillGlassBead::FillGlassBead(QWidget* parent):LoadingAnimBase (parent){ mAnim.setDuration(3600); mWaveType = WaveType::PeacefulWater; } void FillGlassBead::setWaveType(WaveType t){ if(mWaveType != t){ mWaveType = t; update(); } } void FillGlassBead::paintEvent(QPaintEvent* e){ QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing); const int x = width(); const int y = height(); static const QColor color("lightseagreen"); if(mAngle < 90){ //只要画一个坠落的小球 painter.translate(x/2,0); painter.setPen(Qt::NoPen); painter.setBrush(QBrush(color)); qreal posY = y/4.0 * mAngle / 90;//坠落的小球最低点是高度的四分之一 painter.drawEllipse(QPointF(0,posY),5,5); } else{ painter.translate(x/2,0.625*y); QPen pen(color); pen.setWidth(4); painter.setPen(pen); painter.setBrush(Qt::NoBrush); const qreal r = 0.375*y; if(mAngle < 225){ //画一个包裹玻璃珠的圆环 //最高点起点角度是90,弧长是0,最低点起点角度是-90,弧长是360 qreal proportion = (mAngle - 90) / 135.0; painter.drawArc(QRectF(-r,-r,2*r,2*r), 16*(90-180*proportion),16*360*proportion); } else{ //画一个退去的包裹圆环 //最开始起点角度是-90,最后起点角度是90 qreal proportion = (mAngle - 225) / 135.0; painter.drawArc(QRectF(-r,-r,2*r,2*r),16*(-90+180*proportion),16*(360 - proportion*360)); //再画一个上涨的水波 QPainterPath pp; const qreal startx = -x/2; const qreal starty = 0.375*y - 0.75*y*proportion; if(mWaveType == WaveType::PeacefulWater){ pp.addRect(QRectF(startx,starty,x,y)); } else{ QPointF start(startx,starty); QPointF end(start.x() + x,start.y()); const qreal h = qSin(4*M_PI * proportion) *x * 0.3; QPointF c1( start.x() + 0.25*x, start.y() + h); QPointF c2( start.x() + 0.75*x, start.y() - h); pp.moveTo(start); pp.cubicTo(c1,c2,end); pp.lineTo(end.x(),999);//999没有具体含义,大一点就行了 pp.lineTo(startx,999); pp.closeSubpath(); } painter.setClipPath(pp); painter.setPen(Qt::NoPen); painter.setBrush(QBrush(color)); const qreal r2 = r - 4; painter.drawEllipse(QRectF(-r2,-r2,2*r2,2*r2)); } } } Hourglass::Hourglass(QWidget* parent):LoadingAnimBase (parent),mSandColor("lime"),mColumnVisible(false){ } void Hourglass::setSandColor(const QColor& color){ if(mSandColor != color){ mSandColor = color; update(); } } void Hourglass::setColumnVisibility(bool vis){ if(vis != mColumnVisible){ mColumnVisible = vis; update(); } } void Hourglass::paintEvent(QPaintEvent*){ QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing); const qreal x = width(); const qreal y = height(); //step1: 先画上下两个瓶盖 qreal ang = (mAngle - 320)/40;//到320度的时候要旋转瓶子 if(ang > 1) ang = 1; if(ang < 0) ang = 0; ang *= 180; QPen pen("lightslategray");//岩灰色的瓶身,就像我冰冷的内心 pen.setWidth(16); pen.setCapStyle(Qt::RoundCap); painter.setBrush(Qt::NoBrush); painter.setPen(pen); painter.translate(x/2,y/2); painter.rotate(ang); painter.drawLine(-x/4,-0.375*y,x/4,-0.375*y);//上方瓶盖 painter.drawLine(-x/4,0.375*y,x/4,0.375*y);// 下方瓶盖 //step2: 画一个瓶身 QPainterPath pp; const int gap1 = 8;//瓶身距离四分之一水平位置的间距,这个值越大,沙漏越瘦 QPointF start(-x/4 + gap1 , -0.375*y); QPointF end(-x/32 , 0); QPointF c1(start.x(),end.y()); QPointF c2(end.x(),end.y() + 0.4 * (start.y() - end.y())); pp.moveTo(start); pp.cubicTo(c1,c2,end); const qreal penWidth = 6; pen.setWidthF(penWidth); painter.setPen(pen); painter.drawPath(pp);//瓶身轮廓左上部分 painter.rotate(180); painter.drawPath(pp);//右下 painter.rotate(-180); painter.scale(1,-1); painter.drawPath(pp);//右上 painter.rotate(180); painter.drawPath(pp);//左下 //step3: 画两根小柱子(可选) if(mColumnVisible){ pen.setWidthF(4); painter.setPen(pen); painter.drawLine(-x/4,start.y(),-x/4,-start.y()); painter.drawLine(x/4,start.y(),x/4,-start.y()); } painter.resetTransform(); painter.translate(x/2,y/2); painter.setPen(Qt::NoPen); painter.setBrush(QBrush(mSandColor)); if(mAngle < 320){ //step4: 画动态的沙子 //画上面的沙子 QPainterPath sand; start.setX(start.x() + penWidth/2);//沙子区域要瘦一点,免得盖住了瓶身 end.setX(end.x() + penWidth/2); //沙子区域要瘦一点,免得盖住了瓶身 c1.setX(c1.x() + penWidth/2); //沙子区域要瘦一点,免得盖住了瓶身 c2.setX(c2.x() + penWidth/2); //沙子区域要瘦一点,免得盖住了瓶身 sand.moveTo(start); sand.cubicTo(c1,c2,end); sand.lineTo(0,(0.33-0.2*mAngle/320)*y); sand.lineTo(QPointF(end.x()*-1,end.y())); sand.cubicTo(QPointF(-c2.x(),c2.y()),QPointF(-c1.x(),c1.y()),QPointF(-start.x(),start.y())); sand.lineTo(start); painter.setClipPath(sand); painter.drawRect(QRectF(-x/2,-y/4+mAngle/320 * y *0.38,x,x)); //画下面的沙子, 一个等腰三角形 QPointF a(start.x() * mAngle/320,0.33*y); QPointF top(0,(0.33 - mAngle/320 * 0.2) * y); QPointF b(-a.x(),a.y()); sand.moveTo(a); sand.lineTo(top); sand.lineTo(b); sand.closeSubpath(); painter.setClipPath(sand); painter.drawRect(-x/2,top.y(),x,x);//这个高度不能太随意,否则会把上面的沙子也画出来 } else{ //旋转沙子 QPainterPath pp; pp.moveTo(QPointF(start.x() + penWidth/2,0.33*y)); pp.lineTo(0,0.13*y); pp.lineTo(QPointF(-start.x()-penWidth/2,0.33*y)); pp.closeSubpath(); QTransform bottoleTrans; bottoleTrans.rotate(ang); painter.setClipPath(bottoleTrans.map(pp)); painter.drawRect(QRectF(-x/2,-x/2,x,x)); } }
标签:

Qt简约美观的加载动画小沙漏风格第六季由讯客互联人工智能栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“Qt简约美观的加载动画小沙漏风格第六季