程序动画部分来自龚建波大佬的博客:https://blog.csdn.net/gongjianbo1992/article/details/106885483
首先上实机效果图,相较于龚建波大佬的代码,打破了原有的界面布局,新的界面布局用于缩放比适配和阴影效果,相当于做了些界面美化的工作

实现思路:
动画部分使用QPropertyAnimation 属性动画配合动画组。然后根据设置来决定启用哪些动画、是否定时关闭
由于只有一个实例存在,所以可以看到如演示所示,第二次调用弹框显示的时候,会进行判断,判断实例上的动画组是否完结,如果没完结的话,会立即完结动画开始下一组动画
阴影效果部分使用QGraphicsDropShadowEffect,使用此效果需要界面UI留有显示阴影的空间
代码部分:
获取系统缩放比的代码
//************************************
// Method: getDpi
// Description:获取系统dpi(缩放比例)
// Returns: 缩放比例
//************************************
double getDpi()
{
double dDpi = 1;
// Get desktop dc
HDC desktopDc = GetDC(NULL);
// Get native resolution
float horizontalDPI = GetDeviceCaps(desktopDc, LOGPIXELSX);
float verticalDPI = GetDeviceCaps(desktopDc, LOGPIXELSY);
int dpi = (horizontalDPI + verticalDPI) / 2;
dDpi = 1 + ((dpi - 96) / 24)*0.25;
//为了保证页面显示正常,暂时不支持小于1和大于2的缩放系数
if (dDpi < 1)
{
dDpi = 1;
}
return dDpi;
}
TrayMessageDlg.h
#pragma once
#include <QWidget>
#include <QPropertyAnimation>
#include <QParallelAnimationGroup>
#include <QTimer>
#include "ui_TrayMessageDlg.h"
class TrayMessageDlg : public QWidget
{
Q_OBJECT
public:
//动画模式枚举
enum AnimationMode
{
//无动画
NoAnimation = 0x00,
//仅透明度动画
OpacityAnimation = 0x01,
//仅位置动画
PosAnimation = 0x02,
//全部动画
//OpacityAnimation|PosAnimation
AllAnimation = 0xFF
};
public:
explicit TrayMessageDlg();
~TrayMessageDlg();
//显示弹框-已显示动画重新开始,timeout<=0不会定时消失
static void showTip(const QString &title, const QString &texts, int timeout);
//显示弹框-已显示不重复动画
static void keepTip(const QString &texts);
//隐藏弹框
static void hideTip();
//设置动画模式
static TrayMessageDlg::AnimationMode getMode();
static void setMode(TrayMessageDlg::AnimationMode newMode);
protected:
void paintEvent(QPaintEvent *event);
private:
//初始化动画设置
void initAnimation();
//初始化定时器设置
void initTimer();
//准备定时器
void readyTimer(int timeout);
//启动显示动画-已显示动画重新开始
void showAnimation();
//启动显示动画-已显示不重复动画
void keepAnimation();
//启动隐藏动画
void hideAnimation();
private:
Ui::TrayMessageDlg *ui;
//唯一实例
static TrayMessageDlg *instance;
//动画设置
static AnimationMode mode;
//动画组
QParallelAnimationGroup *showGroup;
//保存动画结束状态
bool showAnimEnd = false;
//透明度属性动画
QPropertyAnimation *showOpacity = nullptr;
//位置属性动画
QPropertyAnimation *showPos = nullptr;
//定时关闭
QTimer *hideTimer = nullptr;
//定时计数
int hideCount = 0;
//缩放比例,适配高分屏
double m_dpi;
QGraphicsDropShadowEffect *m_pEffect;
};
TrayMessageDlg.cpp
无法编译的部分为日志输出
#include <stdafx.h>
#include "TrayMessageDlg.h"
#include "ui_TrayMessageDlg.h"
#include <QApplication>
#include <QScreen>
#include <QDebug>
#include "../Common/Utils.h"
#include "Log.h"
TrayMessageDlg* TrayMessageDlg::instance = nullptr;
TrayMessageDlg::AnimationMode TrayMessageDlg::mode = TrayMessageDlg::AllAnimation;
#define SHADOW_WIDTH 10 //边框阴影宽度
TrayMessageDlg::TrayMessageDlg()
: QWidget(nullptr),ui(new Ui::TrayMessageDlg),showGroup(new QParallelAnimationGroup(this))
{
try
{
ui->setupUi(this);
setWindowFlags(Qt::FramelessWindowHint | Qt::ToolTip);
setAttribute(Qt::WA_TranslucentBackground,true);//背景透明
setAttribute(Qt::WA_DeleteOnClose);
//setWindowModality(Qt::WindowModal);
ui->btnClose->setIcon(QIcon(":/YozoUCloud/Resources/Main/window_close_n.png"));
//缩放比适配
m_dpi = getDpi();
setFixedSize(300 * m_dpi, 160 * m_dpi);
QFont font;
font.setPixelSize(14*m_dpi); //字体基础是14
font.setFamily(QString::fromLocal8Bit("微软雅黑"));
ui->titleLabel->setFont(font);
ui->contentLabel->setFont(font);
ui->countLabel->setFont(font);
//添加阴影效果
m_pEffect = new QGraphicsDropShadowEffect(this);//该类提供了图形元素的阴影效果,用于增加立体感。
m_pEffect->setOffset(0, 0);//用于设定在哪个方向产生阴影效果,如果dx为负数,则阴影在图形元素的左边
m_pEffect->setColor(Qt::gray);//用于设定阴影的颜色
m_pEffect->setBlurRadius(20);//用于设定阴影的模糊度
ui->frame->setGraphicsEffect(m_pEffect);
ui->verticalLayout->setContentsMargins(10,10,10,0);//设置frame和主窗口的距离,也就是阴影的距离
//关闭按钮事件绑定
connect(ui->btnClose, &QPushButton::clicked, this, &TrayMessageDlg::hideTip);
//程序退出时释放
connect(qApp, &QApplication::aboutToQuit, this, &TrayMessageDlg::close);
//动画初始化设置
initAnimation();
//定时器设置
initTimer();
}
catch (...)
{
Log::WriteOutput(LogType::Error, L"TrayMessageDlg::TrayMessageDlg() Failed!");
}
}
TrayMessageDlg::~TrayMessageDlg()
{
try
{
if (ui)
{
delete ui;
ui = NULL;
}
if (showGroup)
{
delete showGroup;
showGroup = NULL;
}
if (hideTimer)
{
delete hideTimer;
hideTimer = NULL;
}
if (m_pEffect)
{
delete m_pEffect;
m_pEffect = NULL;
}
}
catch (...)
{
Log::WriteOutput(LogType::Error, L"TrayMessageDlg::~TrayMessageDlg() Failed!");
}
}
void TrayMessageDlg::showTip(const QString &title, const QString &texts, int timeout)
{
try
{
if (!instance){
//仅在ui线程
instance = new TrayMessageDlg();
}
instance->readyTimer(timeout);
//模态框
instance->setWindowModality(Qt::WindowModal);
instance->ui->contentLabel->setText(texts);
instance->ui->titleLabel->setText(title);
instance->showAnimation();
}
catch (...)
{
Log::WriteOutput(LogType::Error, L"TrayMessageDlg::showTip() Failed!");
return;
}
}
void TrayMessageDlg::keepTip(const QString &texts)
{
try
{
if (!instance){
//仅在ui线程
instance = new TrayMessageDlg;
}
instance->readyTimer(0);
//模态框
instance->setWindowModality(Qt::WindowModal);
instance->ui->contentLabel->setText(texts);
instance->keepAnimation();
}
catch (...)
{
Log::WriteOutput(LogType::Error, L"TrayMessageDlg::keepTip() Failed!");
return;
}
}
//关闭按钮点击事件
void TrayMessageDlg::hideTip()
{
try
{
if (!instance){
return;
}
instance->ui->countLabel->hide();
instance->hideAnimation();
}
catch (...)
{
Log::WriteOutput(LogType::Error, L"TrayMessageDlg::hideTip() Failed!");
return;
}
}
TrayMessageDlg::AnimationMode TrayMessageDlg::getMode()
{
return mode;
}
void TrayMessageDlg::setMode(TrayMessageDlg::AnimationMode newMode)
{
if (mode != newMode){
mode = newMode;
}
}
void TrayMessageDlg::initAnimation()
{
try
{
//透明度动画
showOpacity = new QPropertyAnimation(this, "windowOpacity");
//判断是否设置了此模式的动画
if (mode&AnimationMode::OpacityAnimation){
showOpacity->setDuration(1500);
showOpacity->setStartValue(0);
}
else{
showOpacity->setDuration(0);
showOpacity->setStartValue(1);
}
showOpacity->setEndValue(1);
showGroup->addAnimation(showOpacity);
//位置动画
showPos = new QPropertyAnimation(this, "pos");
QScreen * screen = QGuiApplication::primaryScreen();
if (screen) {
const QRect desk_rect = screen->availableGeometry();
const QPoint hide_pos{ desk_rect.width() - this->width(),
desk_rect.height() };
const QPoint show_pos{ desk_rect.width() - this->width(),
desk_rect.height() - this->height() };
//判断是否设置了此模式的动画
if (mode&AnimationMode::PosAnimation){
showPos->setDuration(1500);
showPos->setStartValue(hide_pos);
}
else{
showPos->setDuration(0);
showPos->setStartValue(show_pos);
}
showPos->setEndValue(show_pos);
}
showGroup->addAnimation(showPos);
//
connect(showGroup, &QParallelAnimationGroup::finished, [this]{
//back消失动画结束关闭窗口
if (showGroup->direction() == QAbstractAnimation::Backward){
//Qt::WA_DeleteOnClose后手动设置为null
instance = nullptr;
qApp->disconnect(this);
//关闭时设置为非模态,方式主窗口被遮挡,待测试
this->setWindowModality(Qt::NonModal);
this->close();
}
else{
//配合keepAnimation
showAnimEnd = true;
//配合定时关闭
if (hideCount>0)
hideTimer->start();
}
});
}
catch (...)
{
Log::WriteOutput(LogType::Error, L"TrayMessageDlg::initAnimation() Failed!");
return;
}
}
void TrayMessageDlg::initTimer()
{
try
{
hideTimer = new QTimer(this);
hideTimer->setInterval(1000); //1s间隔
connect(hideTimer, &QTimer::timeout, [this]{
if (hideCount>1){
hideCount--;
ui->countLabel->setText(QString::fromLocal8Bit("%1s后自动关闭").arg(hideCount));
}
else{
ui->countLabel->hide();
hideTimer->stop();
hideTip();
}
});
}
catch (...)
{
Log::WriteOutput(LogType::Error, L"TrayMessageDlg::initTimer() Failed!");
return;
}
}
void TrayMessageDlg::readyTimer(int timeout)
{
try
{
//先设置,在显示动画结束再start开始计时器
hideCount = timeout;
hideTimer->stop();
if (hideCount>0){
ui->countLabel->setText(QString::fromLocal8Bit("%1s后自动关闭").arg(hideCount));
}
else{
ui->countLabel->hide();
}
}
catch (...)
{
Log::WriteOutput(LogType::Error, L"TrayMessageDlg::readyTimer() Failed!");
return;
}
}
void TrayMessageDlg::showAnimation()
{
try
{
showGroup->setDirection(QAbstractAnimation::Forward);
if (showGroup->state() == QAbstractAnimation::Running)
{
showGroup->stop(); //停止正在进行的动画重新
}
showGroup->start();
show();
}
catch (...)
{
Log::WriteOutput(LogType::Error, L"TrayMessageDlg::showAnimation() Failed!");
return;
}
}
void TrayMessageDlg::keepAnimation()
{
try
{
//show没有完成,或者正在动画中才进入
if (!showAnimEnd || showGroup->state() != QAbstractAnimation::Stopped){
showGroup->setDirection(QAbstractAnimation::Forward);
showGroup->start();
show();
}
}
catch (...)
{
Log::WriteOutput(LogType::Error, L"TrayMessageDlg::keepAnimation() Failed!");
return;
}
}
void TrayMessageDlg::hideAnimation()
{
try
{
//Backward反向执行动画
showGroup->setDirection(QAbstractAnimation::Backward);
showGroup->start();
}
catch (...)
{
Log::WriteOutput(LogType::Error, L"TrayMessageDlg::hideAnimation() Failed!");
return;
}
}
void TrayMessageDlg::paintEvent(QPaintEvent *event)
{
try
{
//目前没有动作,可以删除
}
catch (...)
{
Log::WriteOutput(LogType::Error, L"TrayMessageDlg::paintEvent() Failed!");
return;
}
}
TrayMessageDlg.ui
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TrayMessageDlg</class>
<widget class="QWidget" name="TrayMessageDlg">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>300</width>
<height>160</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<property name="styleSheet">
<string notr="true"> #frame{
background-color: white;
border:none;
border-radius:6px;
}
#titleArea{
background-color: white;
border-bottom:1px solid rgb(227, 227, 227);
}
#titleLabel{
background-color:white;
border:none;
}
#contentLabel{
background-color:white;
padding: 6px 20px;
}
#countLabel{
background-color:white;
color:rgb(59, 119, 229);
}
#btnClose{
background-color:white;
border:none;
border-radius:5px
}
#btnClose:hover{
background-color:rgb(255,64,64);
color: rgb(0, 85, 127);
}
</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QFrame" name="frame">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2" stretch="2,5">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item>
<widget class="QWidget" name="titleArea" native="true">
<property name="minimumSize">
<size>
<width>0</width>
<height>50</height>
</size>
</property>
<layout class="QHBoxLayout" name="horizontalLayout" stretch="2,2,1">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>20</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>20</number>
</property>
<property name="bottomMargin">
<number>1</number>
</property>
<item>
<widget class="QLabel" name="titleLabel">
<property name="text">
<string>提示</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="countLabel">
<property name="text">
<string>3s后自动关闭</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btnClose">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>16</width>
<height>16</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>25</width>
<height>25</height>
</size>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QLabel" name="contentLabel">
<property name="baseSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="text">
<string/>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>