程序動畫部分來自龔建波大佬的博客: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>
