|
上篇文章中提到用戶體驗(yàn)(UE),并且說(shuō)到國(guó)內(nèi)有專門去做UE的團(tuán)隊(duì)也很少。據(jù)我了解Microsoft、Nokia、Google等,還有國(guó)內(nèi)的Baidu是有比較專業(yè)的UE團(tuán)隊(duì)。對(duì)于我們這樣的普通團(tuán)隊(duì)、普通開(kāi)發(fā)者來(lái)說(shuō),這樣的經(jīng)驗(yàn)實(shí)在太少了。而且普遍更認(rèn)為UE是UI Designer的事情,與我們這樣的Developer沒(méi)有太多關(guān)系。
當(dāng)然不是,UE遠(yuǎn)超過(guò)UI。很多因素造成了UE差,比如一份不正確的數(shù)據(jù)表明17%的用戶認(rèn)為手機(jī)運(yùn)行速度慢,Windows Mobile手機(jī)開(kāi)機(jī)漫長(zhǎng)的等待就十分的讓我受不了。我們開(kāi)發(fā)的應(yīng)用是否有過(guò)優(yōu)化?運(yùn)行效率是否已經(jīng)很讓用戶滿意了?等等這些問(wèn)題留在開(kāi)發(fā)中思考吧。
在使用優(yōu)秀的產(chǎn)品時(shí)用心體會(huì)、用心觀察、用心思考,在此基礎(chǔ)上創(chuàng)新。逐漸提高UE設(shè)計(jì)能力。(等Windows 7正式發(fā)布了,我們可以討論討論其UE^^)
這篇文章僅僅討論有關(guān)界面開(kāi)發(fā)上Developer涉及到的技術(shù)問(wèn)題,在學(xué)習(xí)過(guò)程中,隨著越深越廣越覺(jué)得自身水平的不足,所以只敢拋磚引玉,更多希望能夠引起大家對(duì)界面開(kāi)發(fā)技術(shù)、對(duì)UE的討論。
上篇文章已經(jīng)列出目錄:
1.相關(guān)商用產(chǎn)品一覽
2.Windows系統(tǒng)下圖形編程的相關(guān)基礎(chǔ)知識(shí)
3.DirectDraw簡(jiǎn)介
4.DirectDraw驅(qū)動(dòng)開(kāi)發(fā)
5.DirectDraw應(yīng)用開(kāi)發(fā)
6.一個(gè)推薦的入門Sample
第1部分已經(jīng)在上篇文章講過(guò),鑒于篇幅的原因2、3、4、5、6部分將在下篇文章介紹。這篇文章先介紹下如何使用Win32下的GDI等接口實(shí)現(xiàn)絢麗、高效率的界面。這樣我們就能發(fā)現(xiàn)GDI等接口的不足,進(jìn)而引申到DirectDraw上面。(這篇文章默認(rèn)你有一定的Windows編程基礎(chǔ),熟悉GDI等概念。)
補(bǔ)充內(nèi)容●如何使用Win32下的GDI等接口實(shí)現(xiàn)絢麗、高效的界面
1.如何讓界面絢麗?
怎么樣的算絢麗?有很漂亮的圖片?有Alpha透明?有Animation?
每個(gè)人的審美觀點(diǎn)都不同,所以如果你的界面很多人認(rèn)為絢麗那就可以了。設(shè)計(jì)界面主要是Designer的工作,包括UI邏輯的設(shè)計(jì),色彩搭配設(shè)計(jì)等,我認(rèn)為這也可以進(jìn)一步分工:熟悉用戶習(xí)慣的Designer、美學(xué)Designer等。但是一般情況下這些讓程序員給代勞了。
下面介紹Windows提供給開(kāi)發(fā)人員的相關(guān)接口,利用這些接口設(shè)計(jì)你認(rèn)為絢麗的界面。
2.如何透明?如何半透明?如何顏色漸變?
以下是我使用Imaging COM組件封裝的一個(gè)函數(shù),可以使用其繪制PNG圖片,當(dāng)然也可以繪制其它圖片。繪制帶Alpha通道的PNG圖片即實(shí)現(xiàn)了透明。
#include #include #include #pragma comment(lib, "Imaging.lib")BOOL DrawPNG(HDC hDC, TCHAR *szPicString, RECT &rcDraw){ BOOL br = FALSE; IImagingFactory *pImgFactory = NULL; IImage *pImage = NULL; ImageInfo sImgInfo; CoInitializeEx(NULL, COINIT_MULTITHREADED); // Create the imaging factory. if (SUCCEEDED(CoCreateInstance(CLSID_ImagingFactory, NULL, CLSCTX_INPROC_SERVER, IID_IImagingFactory, (void **)&pImgFactory))) { // Load the image from the JPG file. if (SUCCEEDED(pImgFactory->CreateImageFromFile( szPicString, &pImage))) { // Draw the image. pImage->Draw(hDC, &rcDraw, NULL); pImage->Release(); pImage = NULL; br = TRUE; } pImgFactory->Release(); } CoUninitialize(); return br;}
------------------------------------------------------------------------------------------------------
而封裝的這個(gè)函數(shù)實(shí)現(xiàn)了將一個(gè)DC根據(jù)Alpha值半透明繪制到另一個(gè)DC上,使用GDI函數(shù)AlphaBlend實(shí)現(xiàn)。
BOOL AlphaBlt(HDC hdcDest, int nXOriginDest, int nYOriginDest, int nWidthDest, int nHeightDest, HDC hdcSrc, int nXOriginSrc, int nYoriginSrc, int nWidthSrc, int nHeightSrc, BYTE alpha) { BLENDFUNCTION bf; bf.BlendOp = AC_SRC_OVER; bf.BlendFlags = 0; bf.SourceConstantAlpha = alpha; bf.AlphaFormat = 0; return AlphaBlend(hdcDest, nXOriginDest, nYOriginDest, nWidthDest, nHeightDest, hdcSrc, nXOriginSrc, nYoriginSrc, nWidthSrc, nHeightSrc, bf);}
如果你的設(shè)備支持AlphaBlend硬件加速那將是非常棒的事情,否則軟件方式會(huì)有點(diǎn)影響性能。
------------------------------------------------------------------------------------------------------
顏色漸變也是直接有API可以支持:
BOOL GradientFill( HDC hdc, PTRIVERTEX pVertex, ULONG nVertex, PVOID pMesh, ULONG nCount, ULONG ulMode);
hdc
[in] Handle to the destination device context.
pVertex
[in] Pointer to an array of TRIVERTEX structures, each of which defines a triangle vertex.
nVertex
[in] The number of vertices in pVertex.
pMesh
[in] Array of GRADIENT_RECT structures in rectangle mode.
nCount
[in] The number of rectangles in pMesh.
ulMode
[in] Specifies gradient fill mode. The following table shows the possible values for ulMode.
This function fills rectangular regions with a background color that is interpolated from color values specified at the vertices.
不管你使用.NET CF平臺(tái)調(diào)用這些API,還是Win32/MFC/ATL/WTL直接調(diào)用這些API,你都是可以實(shí)現(xiàn)這些效果的。更多內(nèi)容請(qǐng)查詢開(kāi)發(fā)文檔,畢竟那才是最好的參考資料。
3.如何實(shí)現(xiàn)動(dòng)畫?
動(dòng)畫的原理就是一幀一幀的畫面按照時(shí)間軸向后移動(dòng),在騙過(guò)眼睛之后就成了動(dòng)畫,所以你得到動(dòng)畫最簡(jiǎn)單的方法就是按照一定間隔將不同圖片一張一張繪制到屏幕上,雖然很簡(jiǎn)單,但是在編程中經(jīng)常使用這種方法。有時(shí)簡(jiǎn)單的往往是最好的。
這里還有個(gè)技巧,比如將每張圖片使用Photoshop中的運(yùn)動(dòng)濾鏡模糊下,這樣使用上面方法得到的動(dòng)畫會(huì)有種非常快速的感覺(jué)。也可以用類似的方法來(lái)用2D表現(xiàn)三維的事物,得到3D動(dòng)畫的效果。
還可以使用GIF動(dòng)畫的方式,比如在開(kāi)機(jī)和關(guān)機(jī)時(shí)。以下封裝的函數(shù)僅供參考,我沒(méi)用心整理。
BOOL DisplayGIF(TCHAR *szPicString){HANDLE hFile = CreateFile(strFileName, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);if (hFile == INVALID_HANDLE_VALUE){return FALSE;}DWORD dwFileSize = GetFileSize(hFile, NULL);if ( (DWORD)-1 == dwFileSize ){CloseHandle(hFile);return FALSE;}HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLE, dwFileSize);if (hGlobal == NULL){CloseHandle(hFile);return FALSE;}LPVOID pvData = GlobalLock(hGlobal);if (pvData == NULL){GlobalUnlock(hGlobal);CloseHandle(hFile);return FALSE;}DWORD dwBytesRead = 0;BOOL bRead = ReadFile(hFile, pvData, dwFileSize, &dwBytesRead, NULL);GlobalUnlock(hGlobal);CloseHandle(hFile);if (!bRead){return FALSE;}IStream* pStream = NULL;if ( FAILED(CreateStreamOnHGlobal(hGlobal, TRUE, &pStream)) ){return FALSE;}if( NULL == pStream ){return FALSE;} IImage *pImage = NULL;RECT rc;IImagingFactory *pImgFactory = NULL;CoInitializeEx(NULL, COINIT_MULTITHREADED);if ( !SUCCEEDED(CoCreateInstance(CLSID_ImagingFactory, NULL, CLSCTX_INPROC_SERVER, IID_IImagingFactory, (void **)&pImgFactory)) ){return FALSE;}IImageDecoder* pDecoder = NULL;UINT nCount = 0;if ( !SUCCEEDED(pImgFactory->CreateImageDecoder(pStream, DecoderInitFlagNone, &pDecoder)) ){return FALSE;}pDecoder->GetFrameDimensionsCount(&nCount);GUID *pDimensionIDs = (GUID*)new GUID[nCount];pDecoder->GetFrameDimensionsList(pDimensionIDs,nCount);TCHAR strGuid[39];StringFromGUID2(pDimensionIDs[0], strGuid, 39);UINT frameCount = 0;pDecoder->GetFrameCount(&pDimensionIDs[0],&frameCount);UINT iSize = 0;pDecoder->GetPropertyItemSize(PropertyTagFrameDelay,&iSize);BYTE* pBuff = new BYTE[iSize];PropertyItem* pItem = (PropertyItem*)pBuff;pDecoder->GetPropertyItem(PropertyTagFrameDelay,iSize,pItem);int fCount = 0;ImageInfo Info;pImgFactory->CreateImageFromStream(pStream,&pImage);pImage->GetImageInfo(&Info);rc.left = rc.top = 0;rc.right = Info.Width;rc.bottom = Info.Height;HDC tempDC;HBITMAP hbmNew = NULL;void * pv;BITMAPINFO bmi = { 0 };HBITMAP hbmOld = NULL;tempDC = CreateCompatibleDC(NULL);bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);bmi.bmiHeader.biWidth = Info.Width;bmi.bmiHeader.biHeight = Info.Height;bmi.bmiHeader.biPlanes = 1;bmi.bmiHeader.biBitCount = (SHORT) max(16, GetDeviceCaps(tempDC, BITSPIXEL));bmi.bmiHeader.biCompression = BI_RGB;hbmNew = CreateDIBSection(tempDC, &bmi, DIB_RGB_COLORS, &pv, NULL, 0);hbmOld = (HBITMAP)SelectObject(tempDC, hbmNew);pImage->Draw(tempDC, &rc, NULL);pDecoder->SelectActiveFrame(&pDimensionIDs[0], ++fCount);BitBlt(g_hdc, 0, 0, rc.right - rc.left, rc.bottom - rc.top, tempDC, 0, 0, SRCCOPY);delete []pBuff;delete []pDimensionIDs;pDecoder->Release();pImage->Release();pImgFactory->Release();CoUninitialize();return TRUE;}
4.如何有較高的運(yùn)行效率?
后面的內(nèi)容會(huì)介紹到使用GDI這些“較高層次”的接口是很難有較高的運(yùn)行效率。
但是可以使用一些技巧,比如“空間換取時(shí)間”。相信"Lazy Computation”你有聽(tīng)過(guò),延遲處理這項(xiàng)任務(wù)直到真正需要的時(shí)候(在編程中我們也會(huì)經(jīng)常用到,需要有這個(gè)意識(shí)。)這里使用的技巧有點(diǎn)恰恰相反的味道,把用戶將來(lái)很可能用到的地方先處理好,然后儲(chǔ)存起來(lái),而并不是等到用戶真正需要的時(shí)候才去處理。
比如使用Imaging COM組件繪制PNG圖片時(shí),每次都需要加載組件的庫(kù)文件,然后卸載,界面可能要反復(fù)刷新,然后反復(fù)繪制PNG圖片。這時(shí)可以考慮在程序啟動(dòng)的時(shí)候使用非界面主線程將繪制好的PNG圖片保存起來(lái)(比如以Device Context的形式),界面刷新的時(shí)候僅僅是BitBlt到目標(biāo)設(shè)備。BitBlt的效率是比較高的,如果仍然不能滿足你的效率要求,可以考慮下面介紹的DirectDraw等技術(shù)。
上面的方法對(duì)于具有豐富開(kāi)發(fā)經(jīng)驗(yàn)的應(yīng)該比較清楚,但是新手往往會(huì)忽略。在開(kāi)發(fā)界面時(shí)我們要保證一個(gè)基本原則:想盡一切辦法在現(xiàn)有的條件下提高界面響應(yīng)用戶的速度,界面要以用戶為中心。所以開(kāi)發(fā)時(shí)需要保持這個(gè)意識(shí)。
5.如何提高程序啟動(dòng)速度?
第4部分說(shuō)過(guò),為了提高運(yùn)行效率,可以將常用的界面在程序啟動(dòng)時(shí)一起緩存到內(nèi)存中,那么程序的啟動(dòng)時(shí)間會(huì)大大增加,如何解決這個(gè)問(wèn)題?我的建議是UI主線程僅僅加載少量的用戶啟動(dòng)后直接就能看到的界面,而另起一個(gè)子線程(叫A)用于加載其它界面,其它界面加載完之后這個(gè)子線程退出,當(dāng)用戶點(diǎn)擊其它界面時(shí),主線程如果發(fā)現(xiàn)子線程A并沒(méi)有退出,說(shuō)明其它界面還沒(méi)有加載完,讓用戶等待。
這么設(shè)計(jì)的好處是,將最耗時(shí)的任務(wù)分?jǐn)偝鋈ィ茨鼙WC了用戶快速看到界面,又能在之后的運(yùn)行中有較高的效率。
6.如何在絢麗和效率之間平衡?
最好的方法是得到界面運(yùn)行時(shí)具體的時(shí)間消耗數(shù)據(jù),如果必要可以精確到每個(gè)函數(shù)。得到一份系統(tǒng)正常情況下的數(shù)據(jù),得到幾份環(huán)境惡劣情況下的數(shù)據(jù)(比如系統(tǒng)非常繁忙、設(shè)備電量很少、要處理的數(shù)據(jù)非常多等)。定量的去分析解決這些問(wèn)題。如果在惡劣的環(huán)境下你的絢麗界面表現(xiàn)的仍然不錯(cuò),恭喜你,你太棒了!
Windows CE/Windows Mobile也提供了些基本的Performance API(像DirectDraw等技術(shù)還有自己的Performance接口和工具):
BOOL QueryPerformanceCounter(LARGE_INTEGER* lpPerformanceCount);
- lpPerformanceCount
[in] Pointer to a variable that the function sets, in counts, to the current performance-counter value. If the installed hardware does not support a high-resolution performance counter, this parameter can be set to zero.
This function retrieves the current value of the high-resolution performance counter if one is provided by the OEM.
BOOL QueryPerformanceFrequency(LARGE_INTEGER* lpFrequency);
- lpFrequency
[out] Pointer to a variable that the function sets, in counts per second, to the current performance-counter frequency. If the installed hardware does not support a high-resolution performance counter, the value passed back through this pointer can be zero.
This function retrieves the frequency of the high-resolution performance counter if one is provided by the OEM.
上面兩個(gè)API需要OEM在OAL層提供實(shí)現(xiàn),精度可以低于1ms,否則可以使用下面的API。
DWORD GetTickCount(void);
For Release configurations, this function returns the number of milliseconds since the device booted, excluding any time that the system was suspended. GetTickCount starts at zero on boot and then counts up from there.
For debug configurations, 180 seconds is subtracted from the the number of milliseconds since the device booted. This enables code that uses GetTickCount to be easily tested for correct overflow handling.
另外優(yōu)化PNG、Bitmap、GIF等圖片,讓圖片清晰度和大小剛好滿足要求。
7.控件為什么如此降低運(yùn)行效率?怎樣減少控件的使用?
手機(jī)軟件不同于桌面系統(tǒng)軟件,一方面手機(jī)的處理速度更低、電池容量更小,另一方面用戶會(huì)使用手機(jī)處理更緊急的事情。所以這也是我認(rèn)為 不應(yīng)該完全把桌面系統(tǒng)軟件開(kāi)發(fā)經(jīng)驗(yàn)借鑒到手機(jī)軟件開(kāi)發(fā)上的原因。一個(gè)240x320分辨率大小的手機(jī)界面,你給放上5、6個(gè)控件,甚至更多,這個(gè)界面注定不會(huì)太高效率,這樣的界面也不適合作為用戶最常用的界面,比如今日界面。另一方面,Windows的標(biāo)準(zhǔn)、通用控件不會(huì)有太絢麗的外觀,即使自定義的。但是這些控件能夠帶來(lái)很明顯的開(kāi)發(fā)速度。所以我們要協(xié)調(diào)好。不能為了窗口而窗口,更不能一切皆窗口。
那么你會(huì)問(wèn)如何協(xié)調(diào)。我的建議是能不用控件的地方就不要用,大多地方可以直接使用圖片,比如實(shí)現(xiàn)多狀態(tài)按鈕你可以這樣做:
WM_LBUTTONDOWN消息處理里面先判斷Point是否在按鈕的Rect中,如果是將按下?tīng)顟B(tài)的圖片DC BitBlt到屏幕對(duì)應(yīng)位置,WM_LBUTTONUP消息處理里面再BitBlt回來(lái)。
8.基于Win32的界面運(yùn)行效率比基于.NET CF高,但是開(kāi)發(fā)效率低,怎么辦?
Win32編程已經(jīng)很古老、很“落后”了。但是在處理速度還不及奔三的Windows嵌入式設(shè)備上有時(shí)你不得不選擇。把界面常用的功能代碼封裝成庫(kù)(類庫(kù)也可以),積累這樣的資源可以提高團(tuán)隊(duì)的開(kāi)發(fā)效率。C++泛型編程就是以犧牲編譯時(shí)效率換取代碼重用,但是不影響運(yùn)行時(shí)效率,值得去深入學(xué)習(xí)下,而且有現(xiàn)成的庫(kù)可用,比如STL。
還有其它的技術(shù)可供選擇:DirectDraw(后面介紹的)、Direct3DM、OpenGL ES等。但是開(kāi)發(fā)難度較高。
9.如何使用GDI+(Native/Managed)?
GDI+是GDI的下一個(gè)版本,它進(jìn)行了很好的改進(jìn),并且易用性更好。GDI的一個(gè)好處就是你不必知道任何關(guān)于數(shù)據(jù)怎樣在設(shè)備上渲染的細(xì)節(jié),GDI+更好的實(shí)現(xiàn)了這個(gè)優(yōu)點(diǎn),也就是說(shuō),GDI是一個(gè)中低層API,你還可能要知道設(shè)備,而GDI+是一個(gè)高層的API,你不必知道設(shè)備。以下引用自MSDN文檔:
"2-D vector graphics involves drawing primitives (such as lines, curves, and figures) that are specified by sets of points on a coordinate system.
For example, the Rect class stores the location and size of a rectangle; the Pen class stores information about line color, line width, and line style; and the Graphics class has methods for drawing lines, rectangles, paths, and other figures. There are also several Brush classes that store information about how closed figures and paths are to be filled with colors or patterns.
Certain kinds of pictures are difficult or impossible to display with the techniques of vector graphics. Imaging part will resolve this problem. An example of such a class is CachedBitmap, which is used to store a bitmap in memory for fast access and display.
Typography is concerned with the display of text in a variety of fonts, sizes, and styles. One of the new features in GDI+ is subpixel antialiasing. “
Windows CE/Windows Mobile下的GDI+僅僅是Windows桌面系統(tǒng)的一個(gè)很小的子集。OpenNETCF中封裝了GDI+,可以為基于.NET CF的開(kāi)發(fā)者提供便利,微軟提供的Native Code版本就是前面有提到的Imaging COM組件,你也可以直接調(diào)用gdiplus.dll里面的類和方法。網(wǎng)上已經(jīng)有人將Windows CE版本GDI+不支持的部分桌面系統(tǒng)版本GDI+的功能整理進(jìn)來(lái),你可以使用其提供的Lib庫(kù)和頭文件進(jìn)行開(kāi)發(fā)。但可能不是很穩(wěn)定。
Windows Mobile 6中的gdiplus.dll文件:
將上面的dll文件導(dǎo)出得到的函數(shù):
10.如何實(shí)現(xiàn)透明控件等其它問(wèn)題?
因?yàn)閃indows系統(tǒng)目前不支持窗口Alpha透明,所以無(wú)法直接使控件背景透明,我們常用的方法是將控件后面的窗口中對(duì)應(yīng)的背景作為控件的背景。
原理說(shuō)的有點(diǎn)繞,你可以去研究下代碼:
http://www.codeproject.com/KB/mobile/transparent_controls.ASPx(C++)
http://www.codeproject.com/KB/dotNET/TransparentControl.ASPx(C#)
其它參考內(nèi)容:
怎樣在Windows Mobile上設(shè)計(jì)一個(gè)美觀的用戶界面程序(Win32)
Windows Mobile 6.0下實(shí)現(xiàn)自繪多種狀態(tài)按鈕(Win32)
Windows Mobile 6.0下實(shí)現(xiàn)自繪多種狀態(tài)按鈕(Win32) 續(xù)
NET技術(shù):如何開(kāi)發(fā)絢麗、高效率的界面(Windows嵌入式系統(tǒng)),轉(zhuǎn)載需保留來(lái)源!
鄭重聲明:本文版權(quán)歸原作者所有,轉(zhuǎn)載文章僅為傳播更多信息之目的,如作者信息標(biāo)記有誤,請(qǐng)第一時(shí)間聯(lián)系我們修改或刪除,多謝。