C#實現(xiàn)炫酷啟動圖-動態(tài)進(jìn)度條效果
一、簡述
最近接到一個新需求,讓做一個動效進(jìn)度條。
由于我們的產(chǎn)品比較大,在軟件啟動的時候會消耗比較長的時間,原生的進(jìn)度條已經(jīng)不能滿足我們的需求,這里我們就需要一個會動的進(jìn)度條,效果如下圖所示。
光效進(jìn)度條主要是做了一個進(jìn)度動畫,在已完成的部分上進(jìn)行快速的迭代渲染,給用戶一種直觀感受,我們的軟件一直努力加載,它還活著。
有了這個進(jìn)度條之后,當(dāng)我們的進(jìn)度從40%到50%這個持續(xù)的過程中,界面再也不會出現(xiàn)假死的情況,是不是很完美呢。。。
下面我就來分析下這個動效進(jìn)度條是怎么做的
二、動效進(jìn)度條
如效果圖所示,光效進(jìn)度條不同于一般的進(jìn)度條,他在基礎(chǔ)的任務(wù)進(jìn)度之上還添加了一層光效,主要是想告訴用戶我們的軟件一直在努力運行,請在耐心的等待一下下。
我自己在做功能的時候,往往喜歡先做一個測試demo,然后在把做好的功能集成在正式環(huán)境,這個功能也不列外,如第一節(jié)中展示的效果圖,就是測試demo的樣子,雖然很丑,但是基礎(chǔ)功能是有的。
現(xiàn)在的很多軟件,在進(jìn)度展示上都有了比較絢麗的效果,比如壓縮軟件,解壓文件的時候都會有動效進(jìn)度條,用過的同學(xué)應(yīng)該都知道長啥樣,而我們的光效進(jìn)度條跟這個效果差不多,除此之外我們還提供了另一種動效,延遲動效,他們兩個在一定程度上都展示了更友好的進(jìn)度效果。
在開始分析功能前,首先我們先來考慮下我們的需求:動效進(jìn)度條,也就是說在原來的進(jìn)度條基礎(chǔ)上需要添加實時動畫,讓進(jìn)度條看起來更炫酷一些,除了光效進(jìn)度條以外,還有一種延遲到達(dá)進(jìn)度條,也屬于動態(tài)進(jìn)度條。
延遲動效、說直白一點兒就是延遲到達(dá),當(dāng)我們設(shè)置了進(jìn)度從10%到20%時,程序模擬了一個漸進(jìn)的過程,使用一個時間段走完了這10%的進(jìn)度。
下面我們分別來介紹這兩種進(jìn)度條的實現(xiàn)
實現(xiàn)炫酷的進(jìn)度條我們可以從QWidget自定義開始寫,也就是說從頭開始寫,但通常我們不這樣干,因為這樣可能會寫出無窮無盡的bug,而且現(xiàn)有的輪子已經(jīng)很穩(wěn)定了,為什么還要造呢。
1、光效進(jìn)度條
光效進(jìn)度條我們使用了一個小技巧,采用一個簡單的辦法實現(xiàn),我們的光效進(jìn)度條控件繼承自Qt原生進(jìn)度條類QProgressBar,在新類中我們只需要在Qt繪制完原生進(jìn)度條之后,補(bǔ)畫動效即可。
a、paintEvent函數(shù)
paintEvent函數(shù)是Qt的繪制函數(shù),當(dāng)界面刷新的時候,這個接口函數(shù)就會被調(diào)用,因此我們需要重寫這個接口,首先調(diào)用父窗口的繪制方法,然后我們在繪制我們自己的動效,代碼如下所示
QProgressBar::paintEvent(event); drawCache繪制動效
b、drawCache繪制動效
繪制動效的時候,我們需要知道動效的繪制區(qū)域,這個地方我們需要主動去解析qss的一些參數(shù),Qt的style()->subElementRect這個接口剛好可以拿到我們需要的信息
下面簡單描述下我們的實現(xiàn)流程
•首先我們獲取進(jìn)度條的幾何大小和中間進(jìn)度的幾何大小,這樣的話我們就可以計算出來各border的數(shù)值
•然后根據(jù)我們當(dāng)前的value值就可以計算出進(jìn)度條已經(jīng)走過(就是值小于我們設(shè)定的區(qū)域)的幾何大小
•我們的光效將是跑在第二步計算出來的區(qū)域上,一直循環(huán)迭代
•內(nèi)存里我們維護(hù)一個cacheValue,這個值在每次界面刷新的時候遞增,但是不能大于第二步的value值,cacheValue將是我們動效繪制的一個關(guān)鍵參數(shù),他表示了動效繪制的長度
•構(gòu)造一個漸變刷子,設(shè)置給QPainter
•繪制動效
上下大致描述了下繪制動效的一個流程,下面送上具體代碼,由于篇幅原因,代碼我進(jìn)行了部分偽代碼處理。
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(構(gòu)造一個QMargins);
QRectF rect(動效繪制區(qū)域);
if (m_iCacheValue != 0) { QPainter painter(this); QLinearGradient gradient(構(gòu)造繪圖刷子); painter.setBrush(gradient); painter.drawRoundedRect(rect, 2, 2); } }
c、定時刷新
由于我們的動效是需要主動去刷新的,因此我們需要聲明一個定時器,然后定時去刷新,實現(xiàn)代碼可能像下面這樣
connect(m_pCacheTimer, &QTimer::timeout, this, [this]{ if (TM_CACHE == m_mode) { ++m_iCacheValue; repaint(); }else { m_pCacheTimer->stop(); } });
定時器只需要在我們第一次設(shè)置進(jìn)度條值的時候啟動,或者當(dāng)我們設(shè)置一個新的值,而定時器沒有啟動,我們就需要去激活定時器。
TM_CACHE模式即是我們的動效模式,TM_SMOOTH模式則是我們的延遲到達(dá)模式
connect(this, &QProgressBar::valueChanged, [this](int value){//TM_CACHE模式下 啟動動畫時機(jī) if (!m_pCacheTimer->isActive() && value != 0 && TM_CACHE == m_mode) { m_pCacheTimer->start(m_iRefreshleveling); } });
動效進(jìn)度條效果如下圖所示
2、延遲到達(dá)進(jìn)度條
動效進(jìn)度條可能更適用于啟動界面,但是也有一些時候,我們可能需要更平緩的一個加載曲線,例如安裝軟件、卸載軟件的時候。
a、setValue
延遲到達(dá)進(jìn)度條和動效進(jìn)度條的實現(xiàn)方式就有所差別了,對于實現(xiàn)延遲到達(dá)進(jìn)度條,我們這里重寫了setValue函數(shù),當(dāng)外部調(diào)用該接口設(shè)置value值時,我們并沒有立即去設(shè)置當(dāng)前值,而是使用了一個時間段去完成這個值得刷新。
•外部調(diào)用setValue時,我們首先計算出我們應(yīng)該繪制的最大寬度PixelMax、當(dāng)前已經(jīng)繪制到的最大寬度cacheValue和我們的步長
•設(shè)置定時器刷新頻率,并重啟定時器
•定時器刷新時,cacheValue自增我們的步長
•調(diào)用父類的QProgressBar::setValue接口設(shè)置值
b、定時器
延遲達(dá)到功能的的定時器和之前我們什么的動效定時器可以混用一個,我們定時器刷新的時候,針對不同的動畫模式,我們執(zhí)行不同的的代碼即可,實現(xiàn)代碼可能像下面這樣
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(); } });
延遲到達(dá)進(jìn)度條效果如下圖所示
3、接口說明
光效進(jìn)度條類對外只暴露了3個接口,分別是設(shè)置動畫模式、動畫時長和刷新頻率
特別需要注意的是,我們這里重寫了父類的setValue接口,這意味著我們不能使用多態(tài)來操作這個接口
void setTransitionMode(TransitionMode mode);//設(shè)置動畫模式 void setSmoothDuration(int duration);//設(shè)置刷新總時長 模式為TM_SMOOTH時有效 void setRefreshleveling(int rate);//設(shè)置刷新頻率 每次更改TransitionMode之后會變?yōu)槟J(rèn)值
a、修改動畫模式
修改動畫模式的時候,我們需要清空內(nèi)存中的所有數(shù)據(jù),并把value值設(shè)為0。
void GMPProgressBar::setTransitionMode(TransitionMode mode) { if (m_mode == mode) { return; } m_mode = mode; clearData(); QProgressBar::setValue(0); }
b、其他接口
設(shè)置刷新時長和頻率接口都比較簡單,不做特別說明
特別注意:這個3個接口最好是在動畫啟動前設(shè)置,動畫開始后盡量不要去調(diào)用
三、啟動圖
第二節(jié)我們主要是講述了怎么做一個動效進(jìn)度條,這一節(jié)我們來做一個啟動圖頁面,把這個動效使用進(jìn)去。
啟動圖不是我們主要分析的內(nèi)容,這個我就簡單說下這個類的實現(xiàn)方式和一些借口說明
1、實現(xiàn)思路
Qt已經(jīng)給我們提供了一個QSplashScreen,但是使用起來還是特別有限,因此這里我把Qt的源碼直接進(jìn)行了二次開發(fā)
•首先Qt的原生實現(xiàn)方式基本都被移植了出來
•啟動圖使用了簡單的上下布局,上面是一張我自繪制的圖片,放在了一個QLabel上,下面是動效進(jìn)度條
•自繪制的圖片上包括了,產(chǎn)品名稱、logo、背景圖等等
2、背景圖切換
當(dāng)我們調(diào)用setPixmap設(shè)置背景圖時,如果我們指定了多張圖,我將會啟動一個定時器,在指定時長后重新構(gòu)造一張大的背景圖,并添加到啟動窗口上
這里主要說明下背景圖是怎么構(gòu)造出來的,代碼如下所示
a、根據(jù)背景圖構(gòu)造啟動圖大小,并移動到屏幕中間
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());
b、繪制程序logo
QPainter painter(&m_currentPixmap); painter.drawPixmap(m_startPos, m_logo);
c、繪制標(biāo)題欄
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();
d、設(shè)置給QLabel背景圖
m_pWindowBackground->setPixmap(m_currentPixmap);
啟動圖的效果這里就不在貼圖了,第三節(jié)上的兩個gif圖都是最終的啟動圖效果
四、測試
最后就是測試代碼了,主要是模擬了程序的一個加載過程
1、構(gòu)造啟動圖
首先我們構(gòu)造一個啟動圖對象,并設(shè)置程序logo和動畫模式
GMPSplashScreen * screen = new GMPSplashScreen(QPixmap(":/splashScreen/start.png")); screen->show(); screen->setLogo(QIcon("logo.ico").pixmap(48, 48)); screen->setTransitionMode(GMPProgressBar::TM_CACHE);
2、背景圖
設(shè)置背景圖,并設(shè)置背景圖更換時間間隔
QList<QPixmap> lstPixmap; lstPixmap.append(QPixmap(":/splashScreen/start.png")); lstPixmap.append(QPixmap(":/splashScreen/start.jpg")); screen->setPixmap(lstPixmap, 2000);
3、其他信息
設(shè)置程序的提示信息和標(biāo)題欄
screen->showMessage("Established connections", 0); screen->setTitle(QStringLiteral("廣聯(lián)達(dá)BIM土建計量GTJ2018"));
4、事件循環(huán)
這里寫了一個死循環(huán),主要是為了模擬程序的一個加載過程,每隔10ms處理下界面刷新事件
a.processEvents(); while (1) { QTest::qSleep(10); a.processEvents(); } splashScreen w; w.show(); screen->finish(&w);
總結(jié)
以上所述是小編給大家介紹的C#實現(xiàn)炫酷啟動圖-動態(tài)進(jìn)度條效果,希望對大家有所幫助,如果大家有任何疑問歡迎給我留言,小編會及時回復(fù)大家的!
上一篇:C#使用Json.Net進(jìn)行序列化和反序列化及定制化
欄 目:C#教程
本文標(biāo)題:C#實現(xiàn)炫酷啟動圖-動態(tài)進(jìn)度條效果
本文地址:http://mengdiqiu.com.cn/a1/C_jiaocheng/4755.html
您可能感興趣的文章
- 01-10C#實現(xiàn)txt定位指定行完整實例
- 01-10WinForm實現(xiàn)仿視頻播放器左下角滾動新聞效果的方法
- 01-10C#實現(xiàn)清空回收站的方法
- 01-10C#實現(xiàn)讀取注冊表監(jiān)控當(dāng)前操作系統(tǒng)已安裝軟件變化的方法
- 01-10C#實現(xiàn)多線程下載文件的方法
- 01-10C#實現(xiàn)Winform中打開網(wǎng)頁頁面的方法
- 01-10C#實現(xiàn)遠(yuǎn)程關(guān)閉計算機(jī)或重啟計算機(jī)的方法
- 01-10C#自定義簽名章實現(xiàn)方法
- 01-10C#文件斷點續(xù)傳實現(xiàn)方法
- 01-10winform實現(xiàn)創(chuàng)建最前端窗體的方法


閱讀排行
本欄相關(guān)
- 01-10C#通過反射獲取當(dāng)前工程中所有窗體并
- 01-10關(guān)于ASP網(wǎng)頁無法打開的解決方案
- 01-10WinForm限制窗體不能移到屏幕外的方法
- 01-10WinForm繪制圓角的方法
- 01-10C#實現(xiàn)txt定位指定行完整實例
- 01-10WinForm實現(xiàn)仿視頻播放器左下角滾動新
- 01-10C#停止線程的方法
- 01-10C#實現(xiàn)清空回收站的方法
- 01-10C#通過重寫Panel改變邊框顏色與寬度的
- 01-10C#實現(xiàn)讀取注冊表監(jiān)控當(dāng)前操作系統(tǒng)已
隨機(jī)閱讀
- 01-10SublimeText編譯C開發(fā)環(huán)境設(shè)置
- 04-02jquery與jsp,用jquery
- 01-10C#中split用法實例總結(jié)
- 01-11ajax實現(xiàn)頁面的局部加載
- 08-05織夢dedecms什么時候用欄目交叉功能?
- 01-10delphi制作wav文件的方法
- 08-05DEDE織夢data目錄下的sessions文件夾有什
- 01-11Mac OSX 打開原生自帶讀寫NTFS功能(圖文
- 08-05dedecms(織夢)副欄目數(shù)量限制代碼修改
- 01-10使用C語言求解撲克牌的順子及n個骰子