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