Qt自定义一个带动画的桌面弹框

实现效果

这次制作的桌面弹窗,主要功能有透明度和位置动画、定时关闭。

实现细节

首先,我们需要获取桌面大小,然后 move 到右下角去,这里借助的 QScreen:

QScreen * screen = QGuiApplication::primaryScreen();
const QRect desk_rect = screen->availableGeometry();

对于动画,我用的  QPropertyAnimation 属性动画配合动画组。然后根据设置来决定启用哪些动画、是否定时关闭等。

还有要注意的是模态框的问题,如果有一个 ApplicationModal 的窗口显示了,就没法点击其他窗口。而且关闭模态窗口时,可能会导致主窗口跑到其他窗口后面去了。这里我时先设置为 WindowModal ,关闭时再设置程 NoModal 非模态。

目前还没有做鼠标放在上面不自动关闭的功能;目前的实现只有单个弹窗实例,可自行修改为允许多个弹框同时存在。

实现代码

完整代码链接(CuteDesktopTip类):https://github.com/gongjianbo/QtWidgetsComponent

主要实现代码:

#pragma once
#include <QDialog>
#include <QPropertyAnimation>
#include <QParallelAnimationGroup>
#include <QTimer>
#include "CuteComponentExport.h"
#include "CuteDesktopTipForm.h"

/**
 * @brief 桌面弹框
 * @author 龚建波
 * @date 2020-6-28
 * @note
 * 可以增加 show 的 parent 参数,使模态有父子关系
 * 目前没有样式设置接口,可以增加一个 setForm 接口设置内部窗口
 * @details
 * show 时创建,close 时释放
 * 分为外层透明区域限定位置,和内层窗口样式
 */
class Cute_API CuteDesktopTip : public QDialog
{
    Q_OBJECT
public:
    // 动画模式枚举
    enum AnimationMode
    {
        // 无动画
        NoAnimation = 0x00,
        // 仅透明度动画
        OpacityAnimation = 0x01,
        // 仅位置动画
        PosAnimation = 0x02,
        // 全部动画
        AllAnimation = 0xFF
    };
    // 显示位置枚举
    enum DisplayArea
    {
        // 当前窗口所在屏幕居中
        CenterArea = 0x00,
        // 主屏幕右下角
        RightBottomArea = 0x01
    };
private:
    explicit CuteDesktopTip(QWidget *parent = nullptr);
public:
    ~CuteDesktopTip();

    // 设置动画模式
    static CuteDesktopTip::AnimationMode getMode();
    static void setMode(CuteDesktopTip::AnimationMode mode);

    // 设置显示位置
    static CuteDesktopTip::DisplayArea getArea();
    static void setArea(CuteDesktopTip::DisplayArea area);

    // 显示弹框 -已显示动画重新开始, timeout <= 0 不会定时消失
    static void showTip(const QString &title, const QString &text, int timeout = 0);
    // 显示弹框 -已显示不重复动画
    static void keepTip(const QString &title, const QString &text);
    // 隐藏弹框
    static void hideTip();

private:
    // 初始化动画设置
    void initAnimation();
    // 初始化定时器设置
    void initTimer();
    // 准备定时器
    void readyTimer(int timeout);
    // 启动显示动画-已显示动画重新开始
    void showAnimation();
    // 启动显示动画-已显示不重复动画
    void keepAnimation();
    // 启动隐藏动画
    void hideAnimation();
    // 显示的标题和文本
    void setText(const QString &title, const QString &text);

private:
    // form 为独立的 widget,封装 ui 相关接口
    CuteDesktopTipForm *form{ nullptr };

    // 单例
    static CuteDesktopTip *instance;
    // 动画设置
    static AnimationMode aniMode;
    // 显示位置
    static DisplayArea aniArea;

    // 动画组
    QParallelAnimationGroup *aniGroup{ nullptr };
    // 透明度属性动画
    QPropertyAnimation *aniOpacity{ nullptr };
    // 位置属性动画
    QPropertyAnimation *aniPos{ nullptr };
    // 动画结束标志,因为每次都会创建新的弹框,所以默认 false 表示正在动画
    bool showAniEnd{ false };

    // 定时关闭
    QTimer *hideTimer{ nullptr };
    // 定时计数
    int hideCount{ 0 };
};
#include "CuteDesktopTip.h"
#include <QApplication>
#include <QScreen>
//#include <QDebug>

CuteDesktopTip *CuteDesktopTip::instance = nullptr;
CuteDesktopTip::AnimationMode CuteDesktopTip::aniMode = CuteDesktopTip::AllAnimation;
CuteDesktopTip::DisplayArea CuteDesktopTip::aniArea = CuteDesktopTip::RightBottomArea;

CuteDesktopTip::CuteDesktopTip(QWidget *parent)
    : QDialog{parent}
    , form{new CuteDesktopTipForm(this)}
    , aniGroup{new QParallelAnimationGroup(this)}
{
    setWindowFlags(Qt::FramelessWindowHint | Qt::ToolTip);
    setAttribute(Qt::WA_TranslucentBackground);
    setAttribute(Qt::WA_DeleteOnClose);
    // setWindowModality(Qt::WindowModal);

    // resize 会被 label 撑开
    this->setFixedSize(310,210);
    form->setFixedSize(310,210);

    // 关闭
    QPushButton *btn_close = form->findChild<QPushButton *>("close");
    if (btn_close) {
        connect(btn_close, &QPushButton::clicked, this, &CuteDesktopTip::hideTip);
    }
    // 程序退出时释放
    connect(qApp, &QApplication::aboutToQuit, this, &CuteDesktopTip::close);
    // 动画设置
    initAnimation();
    // 定时器设置
    initTimer();
    // qDebug() << __FUNCTION__;
}

CuteDesktopTip::~CuteDesktopTip()
{
    // qDebug() << __FUNCTION__;
}

CuteDesktopTip::AnimationMode CuteDesktopTip::getMode()
{
    return aniMode;
}

void CuteDesktopTip::setMode(AnimationMode mode)
{
    if (aniMode != mode) {
        aniMode = mode;
    }
}

CuteDesktopTip::DisplayArea CuteDesktopTip::getArea()
{
    return aniArea;
}

void CuteDesktopTip::setArea(DisplayArea area)
{
    if (aniArea != area) {
        aniArea = area;
    }
}

void CuteDesktopTip::showTip(const QString &title, const QString &text, int timeout)
{
    if (!instance) {
        // 仅在ui线程
        instance = new CuteDesktopTip;
    }
    instance->readyTimer(timeout);
    // 模态框
    instance->setWindowModality(Qt::WindowModal);
    instance->setText(title, text);
    instance->showAnimation();
}

void CuteDesktopTip::keepTip(const QString &title, const QString &text)
{
    if (!instance) {
        // 仅在ui线程
        instance = new CuteDesktopTip;
    }
    instance->readyTimer(0);
    // 模态框
    instance->setWindowModality(Qt::WindowModal);
    instance->setText(title, text);
    instance->keepAnimation();
}

void CuteDesktopTip::hideTip()
{
    if (!instance) {
        return;
    }
    instance->hideAnimation();
}

void CuteDesktopTip::initAnimation()
{
    // 透明度动画,窗口才能设置透明度
    aniOpacity = new QPropertyAnimation(this, "windowOpacity");
    // 判断是否设置了此模式的动画
    if (aniMode & AnimationMode::OpacityAnimation) {
        aniOpacity->setDuration(1500);
        aniOpacity->setStartValue(0);
    } else {
        aniOpacity->setDuration(0);
        aniOpacity->setStartValue(1);
    }
    aniOpacity->setEndValue(1);
    aniGroup->addAnimation(aniOpacity);

    // 位置动画
    aniPos = new QPropertyAnimation(form, "pos");
    // 右下角放主屏,居中默认在当前窗口所在屏幕
    if (aniArea == DisplayArea::RightBottomArea) {
        QScreen *screen = QGuiApplication::primaryScreen();
        if (screen) {
            QRect rect = screen->availableGeometry();
            move(rect.right() - width(), rect.bottom() - height());
        }
    }
    const QPoint hide_pos{0, form->height()};
    const QPoint show_pos{0, 0};
    // 判断是否设置了此模式的动画
    if (aniMode & AnimationMode::PosAnimation){
        aniPos->setDuration(1500);
        aniPos->setStartValue(hide_pos);
    } else {
        aniPos->setDuration(0);
        aniPos->setStartValue(show_pos);
    }
    aniPos->setEndValue(show_pos);
    aniGroup->addAnimation(aniPos);
    //
    connect(aniGroup, &QParallelAnimationGroup::finished, [this]{
        // back 消失动画结束关闭窗口
        if (aniGroup->direction() == QAbstractAnimation::Backward) {
            // Qt::WA_DeleteOnClose 后手动设置为 nullptr
            instance = nullptr;
            qApp->disconnect(this);
            // 关闭时设置为非模态,方式主窗口被遮挡,待测试
            this->setWindowModality(Qt::NonModal);
            this->close();
        } else {
            // 配合 keepAnimation
            showAniEnd = true;
            //配合定时关闭
            if (hideCount > 0) {
                hideTimer->start();
            }
        }
    });
}

void CuteDesktopTip::initTimer()
{
    hideTimer = new QTimer(this);
    // 1s 间隔
    hideTimer->setInterval(1000);
    connect(hideTimer, &QTimer::timeout, [this]{
        QPushButton *btn_close = form->findChild<QPushButton *>("close");
        if (hideCount > 1) {
            hideCount--;
            if (btn_close) {
                btn_close->setText(QString("%1 S").arg(hideCount));
            }
        } else {
            if (btn_close) {
                btn_close->setText("Close");
            }
            hideTimer->stop();
            hideTip();
        }
    });
}

void CuteDesktopTip::readyTimer(int timeout)
{
    // 先设置,在显示动画结束再 start 开始计时器
    hideCount = timeout;
    hideTimer->stop();

    QPushButton *btn_close = form->findChild<QPushButton *>("close");
    if (hideCount > 0) {
        if (btn_close) {
            btn_close->setText(QString("%1 S").arg(hideCount));
        }
    } else {
        if (btn_close) {
            btn_close->setText("Close");
        }
    }
}

void CuteDesktopTip::showAnimation()
{
    aniGroup->setDirection(QAbstractAnimation::Forward);
    // 停止正在进行的动画重新
    if (aniGroup->state() == QAbstractAnimation::Running) {
        aniGroup->stop();
    }
    aniGroup->start();
    show();
}

void CuteDesktopTip::keepAnimation()
{
    // show 没有完成,或者正在动画中才进入
    if (!showAniEnd || aniGroup->state()!=QAbstractAnimation::Stopped) {
        aniGroup->setDirection(QAbstractAnimation::Forward);
        aniGroup->start();
        show();
    }
}

void CuteDesktopTip::hideAnimation()
{
    // Backward 反向执行动画
    aniGroup->setDirection(QAbstractAnimation::Backward);
    aniGroup->start();
}

void CuteDesktopTip::setText(const QString &title, const QString &text)
{
    QLabel *title_label = form->findChild<QLabel *>("title");
    if (title_label) {
        title_label->setText(title);
    }
    QLabel *content_label = form->findChild<QLabel *>("content");
    if (content_label) {
        QString tip_text("<p style='line-height:120%'>");
        QStringList tip_list = text.split("\n");
        for (const QString &line : tip_list)
        {
            if (line.isEmpty())
                continue;
            tip_text += line + "<br>";
        }
        tip_text += "</p>";
        content_label->setText(tip_text);
    }
}

使用方式:

void ToolTipDemo::initDesktopTip()
{
    // 选择启用的动画
    ui->boxDesktopTipAni->setView(new QListView(this));
    // 第一项用默认值
    ui->boxDesktopTipAni->addItems({"All", "No", "Opacity", "Pos"});
    connect(ui->boxDesktopTipAni, QOverload<int>::of(&QComboBox::currentIndexChanged),
            [](int index){
                switch (index) {
                case 0: CuteDesktopTip::setMode(CuteDesktopTip::AllAnimation); break;
                case 1: CuteDesktopTip::setMode(CuteDesktopTip::NoAnimation); break;
                case 2: CuteDesktopTip::setMode(CuteDesktopTip::OpacityAnimation); break;
                case 3: CuteDesktopTip::setMode(CuteDesktopTip::PosAnimation); break;
                default: break;
                }
            });
    // 选择弹出位置
    ui->boxDesktopTipArea->setView(new QListView(this));
    // 第一项用默认值
    ui->boxDesktopTipArea->addItems({"RightBottom", "Center"});
    connect(ui->boxDesktopTipArea, QOverload<int>::of(&QComboBox::currentIndexChanged),
            [](int index){
                switch (index) {
                case 0: CuteDesktopTip::setArea(CuteDesktopTip::RightBottomArea); break;
                case 1: CuteDesktopTip::setArea(CuteDesktopTip::CenterArea); break;
                default: break;
                }
            });

    connect(ui->btnDesktopTipShow, &QPushButton::clicked, [=]{
        //只显示 n 秒就消失
        CuteDesktopTip::showTip("测试 showTip"
                               , "这是 1 条信息\n这是 1 条信息\n这是 1 条信息\n这是 1 条信息"
                               , 5);
    });
    connect(ui->btnDesktopTipKeep, &QPushButton::clicked, [=]{
        // 一直显示,直到点关闭
        CuteDesktopTip::keepTip("测试 keepTip"
                                , "这是 1 条信息\n这是 1 条信息\n这是 1 条信息\n这是 1 条信息");
    });
    connect(ui->btnDesktopTipHide, &QPushButton::clicked, [=]{
        CuteDesktopTip::hideTip();
    });
}

  • 8
    点赞
  • 58
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论
Qt一个跨平台的C++应用程序开发框架,可以用于开发图形用户界面(GUI)应用程序。如果要自定义一个滑动的日历,可以利用Qt提供的QCalendarWidget类来实现。 QCalendarWidget类是Qt中的一个内置的日历控件,可以在窗口中添加并显示日历。它提供了一些常见的功能,如显示当前日期、选择日期、显示不同月份的日期等。 要实现滑动的功能,可以利用QCalendarWidget类的一些方法和信号来处理。首先,可以调用setGridVisible方法设置网格可见以显示日期。然后,可以使用setMinimumDate和setMaximumDate方法设置日历的最小日期和最大日期,以限定可选择的范围。 接下来,可以使用QPropertyAnimation类来创建一个动画效果,使日历在滑动过程中平滑过渡。可以通过设置动画的起始值和结束值,并指定动画持续时间和缓动效果来控制滑动的速度和效果。 然后,需要使用QEvent类来处理滑动事件。可以重写QWidget类中的事件处理函数,例如重写mousePressEvent、mouseMoveEvent和mouseReleaseEvent函数,并在函数中根据鼠标点击的位置和滑动的距离来控制日历的滑动。 最后,可以使用QVBoxLayout或QGridLayout来组织和布局日历控件,并将其添加到窗口中显示。可以根据需要设置控件的大小、颜色和样式等。 总之,通过使用Qt提供的QCalendarWidget类、QPropertyAnimation类和QEvent类,结合适当的布局和事件处理,可以自定义一个滑动的日历控件。以上是一个简单的实现思路,具体的实现细节还需要根据需求进行调整和完善。
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

龚建波

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值