目錄git
最近接到一個新需求,讓作一個動效進度條。app
因爲咱們的產品比較大,在軟件啓動的時候會消耗比較長的時間,原生的進度條已經不能知足咱們的需求,這裏咱們就須要一個會動的進度條,效果以下圖所示。函數
光效進度條主要是作了一個進度動畫,在已完成的部分上進行快速的迭代渲染,給用戶一種直觀感覺,咱們的軟件一直努力加載,它還活着。佈局
有了這個進度條以後,當咱們的進度從40%到50%這個持續的過程當中,界面不再會出現假死的狀況,是否是很完美呢。。。測試
下面我就來分析下這個動效進度條是怎麼作的動畫
如效果圖所示,光效進度條不一樣於通常的進度條,他在基礎的任務進度之上還添加了一層光效,主要是想告訴用戶咱們的軟件一直在努力運行,請在耐心的等待一下下。this
我本身在作功能的時候,每每喜歡先作一個測試demo,而後在把作好的功能集成在正式環境,這個功能也不列外,如第一節中展現的效果圖,就是測試demo的樣子,雖然很醜,可是基礎功能是有的。spa
如今的不少軟件,在進度展現上都有了比較絢麗的效果,好比壓縮軟件,解壓文件的時候都會有動效進度條,用過的同窗應該都知道長啥樣,而咱們的光效進度條跟這個效果差很少,除此以外咱們還提供了另外一種動效,延遲動效,他們兩個在必定程度上都展現了更友好的進度效果。3d
在開始分析功能前,首先咱們先來考慮下咱們的需求:動效進度條,也就是說在原來的進度條基礎上須要添加實時動畫,讓進度條看起來更炫酷一些,除了光效進度條之外,還有一種延遲到達進度條,也屬於動態進度條。rest
延遲動效、說直白一點兒就是延遲到達,當咱們設置了進度從10%到20%時,程序模擬了一個漸進的過程,使用一個時間段走完了這10%的進度。
下面咱們分別來介紹這兩種進度條的實現
實現炫酷的進度條咱們能夠從QWidget自定義開始寫,也就是說從頭開始寫,但一般咱們不這樣幹,由於這樣可能會寫出無窮無盡的bug,並且現有的輪子已經很穩定了,爲何還要造呢。
光效進度條咱們使用了一個小技巧,採用一個簡單的辦法實現,咱們的光效進度條控件繼承自Qt原生進度條類QProgressBar,在新類中咱們只須要在Qt繪製完原生進度條以後,補畫動效便可。
paintEvent函數是Qt的繪製函數,當界面刷新的時候,這個接口函數就會被調用,所以咱們須要重寫這個接口,首先調用父窗口的繪製方法,而後咱們在繪製咱們本身的動效,代碼以下所示
QProgressBar::paintEvent(event); drawCache繪製動效
繪製動效的時候,咱們須要知道動效的繪製區域,這個地方咱們須要主動去解析qss的一些參數,Qt的style()->subElementRect這個接口恰好能夠拿到咱們須要的信息
下面簡單描述下咱們的實現流程
上下大體描述了下繪製動效的一個流程,下面送上具體代碼,因爲篇幅緣由,代碼我進行了部分僞代碼處理。
void GMPProgressBar::drawCache() { QStyleOptionProgressBarV2 opt; QRect outerRect = style()->subElementRect(QStyle::SE_ProgressBarGroove, &opt, this); QRect innerRect = style()->subElementRect(QStyle::SE_ProgressBarContents, &opt, this); QMargins borders(構造一個QMargins); QRectF rect(動效繪製區域); if (m_iCacheValue != 0) { QPainter painter(this); QLinearGradient gradient(構造繪圖刷子); painter.setBrush(gradient); painter.drawRoundedRect(rect, 2, 2); } }
因爲咱們的動效是須要主動去刷新的,所以咱們須要聲明一個定時器,而後定時去刷新,實現代碼可能像下面這樣
connect(m_pCacheTimer, &QTimer::timeout, this, [this]{ if (TM_CACHE == m_mode) { ++m_iCacheValue; repaint(); }else { m_pCacheTimer->stop(); } });
定時器只須要在咱們第一次設置進度條值的時候啓動,或者當咱們設置一個新的值,而定時器沒有啓動,咱們就須要去激活定時器。
TM_CACHE模式便是咱們的動效模式,TM_SMOOTH模式則是咱們的延遲到達模式
connect(this, &QProgressBar::valueChanged, [this](int value){//TM_CACHE模式下 啓動動畫時機 if (!m_pCacheTimer->isActive() && value != 0 && TM_CACHE == m_mode) { m_pCacheTimer->start(m_iRefreshleveling); } });
動效進度條效果以下圖所示
動效進度條可能更適用於啓動界面,可是也有一些時候,咱們可能須要更平緩的一個加載曲線,例如安裝軟件、卸載軟件的時候。
延遲到達進度條和動效進度條的實現方式就有所差異了,對於實現延遲到達進度條,咱們這裏重寫了setValue函數,當外部調用該接口設置value值時,咱們並無當即去設置當前值,而是使用了一個時間段去完成這個值得刷新。
延遲達到功能的的定時器和以前咱們什麼的動效定時器能夠混用一個,咱們定時器刷新的時候,針對不一樣的動畫模式,咱們執行不一樣的的代碼便可,實現代碼可能像下面這樣
connect(m_pCacheTimer, &QTimer::timeout, this, [this]{ if (TM_CACHE == m_mode) { ++m_iCacheValue; repaint(); } else if (TM_SMOOTH == m_mode) { changeSmooth(); } else { m_pCacheTimer->stop(); } });
延遲到達進度條效果以下圖所示
光效進度條類對外只暴露了3個接口,分別是設置動畫模式、動畫時長和刷新頻率
特別須要注意的是,咱們這裏重寫了父類的setValue接口,這意味着咱們不能使用多態來操做這個接口
void setTransitionMode(TransitionMode mode);//設置動畫模式 void setSmoothDuration(int duration);//設置刷新總時長 模式爲TM_SMOOTH時有效 void setRefreshleveling(int rate);//設置刷新頻率 每次更改TransitionMode以後會變爲默認值
修改動畫模式的時候,咱們須要清空內存中的全部數據,並把value值設爲0。
void GMPProgressBar::setTransitionMode(TransitionMode mode) { if (m_mode == mode) { return; } m_mode = mode; clearData(); QProgressBar::setValue(0); }
設置刷新時長和頻率接口都比較簡單,不作特別說明
特別注意:這個3個接口最好是在動畫啓動前設置,動畫開始後儘可能不要去調用
第二節咱們主要是講述了怎麼作一個動效進度條,這一節咱們來作一個啓動圖頁面,把這個動效使用進去。
啓動圖不是咱們主要分析的內容,這個我就簡單說下這個類的實現方式和一些藉口說明
Qt已經給咱們提供了一個QSplashScreen,可是使用起來仍是特別有限,所以這裏我把Qt的源碼直接進行了二次開發
當咱們調用setPixmap設置背景圖時,若是咱們指定了多張圖,我將會啓動一個定時器,在指定時長後從新構造一張大的背景圖,並添加到啓動窗口上
這裏主要說明下背景圖是怎麼構造出來的,代碼以下所示
m_currentPixmap = m_lstPixmaps.at(m_iCurrentIndex); QRect size(QPoint(), m_currentPixmap.size() / m_currentPixmap.devicePixelRatio()); size.setHeight(size.height() + StatusBarHeight); setFixedSize(size.size()); m_pProgressBar->setFixedWidth(size.width() / 8 * 3); move(QApplication::desktop()->screenGeometry().center() - size.center());
QPainter painter(&m_currentPixmap); painter.drawPixmap(m_startPos, m_logo);
painter.save(); painter.setFont(m_titleFont); QFontMetrics fontMetrics(m_titleFont); int textWidth = fontMetrics.width(m_strWindowTitle); int textHegith = m_logo.height(); QRect textTect = QRect(m_startPos + QPoint(13 + m_logo.width(), 0), QSize(textWidth, textHegith)); painter.drawText(textTect, m_strWindowTitle, QTextOption(Qt::AlignCenter)); painter.restore();
m_pWindowBackground->setPixmap(m_currentPixmap);
啓動圖的效果這裏就不在貼圖了,第三節上的兩個gif圖都是最終的啓動圖效果
最後就是測試代碼了,主要是模擬了程序的一個加載過程
首先咱們構造一個啓動圖對象,並設置程序logo和動畫模式
GMPSplashScreen * screen = new GMPSplashScreen(QPixmap(":/splashScreen/start.png")); screen->show(); screen->setLogo(QIcon("logo.ico").pixmap(48, 48)); screen->setTransitionMode(GMPProgressBar::TM_CACHE);
設置背景圖,並設置背景圖更換時間間隔
QList<QPixmap> lstPixmap; lstPixmap.append(QPixmap(":/splashScreen/start.png")); lstPixmap.append(QPixmap(":/splashScreen/start.jpg")); screen->setPixmap(lstPixmap, 2000);
設置程序的提示信息和標題欄
screen->showMessage("Established connections", 0); screen->setTitle(QStringLiteral("廣聯達BIM土建計量GTJ2018"));
這裏寫了一個死循環,主要是爲了模擬程序的一個加載過程,每隔10ms處理下界面刷新事件
a.processEvents(); while (1) { QTest::qSleep(10); a.processEvents(); } splashScreen w; w.show(); screen->finish(&w);
須要源碼的留郵箱,如今的csdn簡直太坑爹了。。。
![]() |
![]() |
很重要--轉載聲明