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

news2025/7/11 6:43:45

这次和大家分享一个沙漏风格的加载动画
效果如下:
在这里插入图片描述

这是本系列的第六季了, 本次内容的关键在于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));
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1477520.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java实现Excel模板下载以及遇到的问题

Java实现Excel模板下载以及遇到的问题 前言&#xff1a; 项目在开发过程中&#xff0c;会用到Excel的导入&#xff0c;导出&#xff0c;复杂一点的Excel可以写好放在项目指定位置&#xff0c;下载时候直接从指定位置获取即可。 代码实现 excel存放的位置&#xff1a; cont…

C#,数值计算,求解微分方程的吉尔(Gear)四阶方法与源代码

1 微分方程 微分方程&#xff0c;是指含有未知函数及其导数的关系式。解微分方程就是找出未知函数。 微分方程是伴随着微积分学一起发展起来的。微积分学的奠基人Newton和Leibniz的著作中都处理过与微分方程有关的问题。微分方程的应用十分广泛&#xff0c;可以解决许多与导数…

Linux系统Docker部署StackEdit Markdown并实现公网访问本地编辑器

文章目录 前言1. ubuntu安装VNC2. 设置vnc开机启动3. windows 安装VNC viewer连接工具4. 内网穿透4.1 安装cpolar【支持使用一键脚本命令安装】4.2 创建隧道映射4.3 测试公网远程访问 5. 配置固定TCP地址5.1 保留一个固定的公网TCP端口地址5.2 配置固定公网TCP端口地址5.3 测试…

【mysql版本修改】

1、使用telnet确认当前mysql版本号 telnet <MySQL服务器IP地址> <MySQL端口号> telnet 192.168.38.20 33062、使用strings查看/usr/sbin/mysqld中包含版本号的字符串 # 查看/usr/sbin/mysqld文件中是否包含对应的版本号 strings /usr/sbin/mysqld | grep 5.7.30 …

vue-router4 (六) 路由嵌套

应用场景&#xff1a; ①比如京东页面的首页、购物车、我的按钮&#xff0c;可以点击切换到对应的页面&#xff1b; ② 比如 Ant Design左侧这些按钮点击就会切到对应的页面&#xff0c;此时可以把左侧按钮放在父路由中&#xff0c;右侧的子路由 1.路由配置&#xff0c;子路由…

408 数据结构笔记

408 数据结构 常用名词存取 第一章-绪论数据结构的基本概念数据的存储结构顺序存储链式存储索引存储散列存储 数据类型和抽象数据类型知识总览学习方式顺序 算法的基本概念时间复杂度空间复杂度 第二章-线性表总览顺序表顺序表的定义知识结构静态分配动态分配顺序表的特点顺序表…

AI一键生成3D模型!

一、Genie Genie 是 Luma AI 推出的一个文本到 3D 的生成模型&#xff0c;可以在 10 秒生成 4 款 3D 模型&#xff0c;自动精修后质感非常逼真&#xff0c;目前支持免费使用。 此次的 1.0 版本更新后将生成功能由 Discord 转到了单独的网页&#xff0c;使用起来更方便&#x…

探究前端路由hash和history的实现原理(包教包会)

今天我们来讲一讲前端中很重要的一个部分路由&#xff08;router&#xff09;&#xff0c;想必前端小伙伴对‘路由’一词都不会感到陌生。但是如果哪天面试官问你&#xff0c;能大概说一说前端路由的实现原理吗&#xff1f; 你又会如何应对呢&#xff1f; 今天勇宝就带着大家一…

温暖呵护,宝宝吐奶小贴士

引言 新生儿是我们生命中的宝贝&#xff0c;但在成长过程中&#xff0c;他们可能会经历各种各样的健康问题。其中&#xff0c;吐奶是较为常见的一种情况&#xff0c;让父母们倍感担忧。在这篇文章中&#xff0c;我们将深入探讨新生儿吐奶的注意事项&#xff0c;并分享一些温暖…

Mask Guided Matting via Progressive Refinement network

1.introduction 输入可以是trimap&#xff0c;粗略二进制分割图&#xff0c;低质量alpha&#xff0c; 2.MG Matting 2.1 Progressive refinement network 网络以图像和粗略mask作为输入&#xff0c;并输出抠图 &#xff0c;在解码过程中&#xff0c;RPN在每个特征级别产生一个…

怎么制作文件类型二维码?文件二维码如何加密?

现在将文件转二维码图片后&#xff0c;分享生成二维码来扫码查看或者下载文件的方式&#xff0c;在很多的场景中都有应用。这个方法的优势在于&#xff0c;成本低而且安全性高&#xff0c;有利于用户快速获取内容的速度&#xff0c;有效提高用户体验&#xff0c;而且日常使用的…

《Decoupling Representation and Classifier for Long-Tailed Recognition》阅读笔记

论文标题 《Decoupling Representation and Classifier for Long-Tailed Recognition》 用于长尾识别的解耦表示和分类器 作者 Bingyi Kang、Saining Xie、Marcus Rohrbach、Zhicheng Yan、 Albert Gordo、Jiashi Feng 和 Yannis Kalantidis 来自 Facebook AI 和 新加坡国…

JVM 有哪些垃圾回收器

引言&#xff1a; 如果说垃圾收集算法是内存回收的方法论&#xff0c;那么垃圾收集器就是内存回收的具体实现。下图展示了 7种作用于不同分代的收集器&#xff0c;其中用于回收新生代的收集器包括Serial、PraNew、Parallel Scavenge&#xff0c;回收老年代的收集器包括Serial …

1906_ AMBA_高级MCU总线架构

1906_ AMBA_高级MCU总线架构 全部学习汇总&#xff1a; g_arm_cores: ARM内核的学习笔记 (gitee.com) 在看内核相关的文件的时候看到了AMBA这个缩写&#xff0c;查了一下具体的概念。这个其实是一个总线架构&#xff0c;应该是ARM设计的。我找到了相关的介绍网页&#xff1a; A…

了解GPT:ChatGPT的终极指南

在人工智能&#xff08;AI&#xff09;的世界里&#xff0c;有一颗冉冉升起的新星正在革命性地改变我们与机器的交互方式&#xff1a;ChatGPT。在本文中&#xff0c;我们将深入研究什么是ChatGPT&#xff0c;为什么底层技术GPT如此强大&#xff0c;以及它是如何实现其卓越功能的…

激光打标机在塑料行业的高精度标记

随着科技的不断发展&#xff0c;激光打标机在塑料行业中的应用越来越广泛。这种高精度的标记技术为塑料产品提供了持久、清晰、可追溯的标识&#xff0c;满足了生产过程中的各种需求。 首先&#xff0c;激光打标机具有高精度的标记能力。这种技术利用激光束在塑料表面进行刻划&…

掌握这个防水浸技巧,工作中优势真的大很多!

随着科技的不断发展&#xff0c;水浸监控系统在各个领域中变得越来越重要。无论是工业、商业还是居住环境&#xff0c;及时发现并有效处理水浸问题对于维护设备、减少损失、确保安全至关重要。 水浸监控系统通过及时检测水浸情况&#xff0c;提供实时报警和监控&#xff0c;为用…

Python + Selenium —— 键盘操作!

Keys 类对键盘按键进行了定义&#xff0c;结合 send_keys() 方法可以向页面元素发送各种键盘按键。 比如在京东首页的搜索框&#xff0c;输入查询的内容后&#xff0c;再按一次回车。这是我们大部分人操作搜索的过程。 from selenium.webdriver.common.keys import Keys # 引…

【web APIs】6、(学习笔记)有案例!

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、正则表达式正则基本使用元字符边界符量词范围字符类 二、替换和修饰符三、正则插件change 事件判断是否有类 四、案例举例学生就业信息表用户注册界面用户登…

感受指针--初使用

程序首先定义了一个整数指针p和一个整数j&#xff0c;并给j赋值为10。 然后&#xff0c;程序将j的地址赋给p&#xff0c;使p指向j。 程序接着打印p&#xff08;即j的地址&#xff09;的十六进制表示。 接着&#xff0c;程序打印p本身的地址的十六进制表示。 最后&#xff0…