我在Windows嵌入式系统上的一个绚丽用户界面开源项目(iToday)
2009-11-27 19:32 王克伟 阅读(7800) 评论(52) 编辑 收藏 举报前言
很少能看到国内手机开发商能够拿出一个优秀的手机用户界面,有也不是开源的。前一段时间朋友等待我的DirectDraw文章,我迟迟没有发布的原因其实是因为这个开源项目iToday。
iToday我会继续实现并改进,等到时机成熟我就把它正式发布为开源项目。
我的初衷是希望iToday的代码能够很好的运行在Windows Mobile和Windows Embedded CE平台上,编译过后只需简单的皮肤配置就能在这两个平台不同分辨率的设备上很好的运行起来。
iToday的设计会一直遵循简洁的原则,复杂的设计不光把问题本身搞复杂了,还把开发和维护成本搞上去了。合理的协调空间和时间,在一些地方用空间换取时间,在另一些地方用时间换取空间。一切以用户为中心。将丰富的功能通过减法的原则隐藏于简洁的用户界面后面。
如果你想参与进来,我会对你提供支持。我的QQ:3423 67 776
如何部署?
在Windows Mobile 6.5 Professional模拟器上安装iTodayCab,从手机的Program Files下的iTodayCab文件夹中运行iToday.exe即可。模拟器屏幕的分辨率没有要求,但是最好在480x800分辨率下运行。
功能需求
1.主要有如下Panel:Home、Contact Shortcut、Application Shortcut、Picture等。
2.能够记忆用户每个Panel的状态,以便从其它Panel切换过来之后用户能够很方便的继续上次的操作。
2.窗口之间切换需要Animation(匀滑滚动、百叶窗、逐渐透明),并且可以设置是否开启这个功能。
3.面板上被选中的一项设置为一定Alpha值的半透明。
4.一个Panel里面的内容可以被拖动,内容不足一个屏幕时可以自动回到原位,内容较多时可以很方便的浏览。
5.尽量使用颜色和图形进行反馈。
6.能够扩展面板(比如可以扩展widgets,实现Weather、RSS Reader等功能)。
7.可以更换按钮等的皮肤。
8.提供给用户设置的接口。
9.Home Panel提供便签、任务管理器功能。
一些技术要求
1.使用Win32实现成一个EXE Application,使用GDI等较高的API,而非较低的DirectDraw API。(也许你会问是否可以考虑.Net CF,答案很明显:在嵌入式上开发高效率、绚丽的界面不用考虑.Net CF。它不是用来干这事的。)
(2010.4.19纠正:托管代码表现较差的地方在于:1.Load的时候时间较长,2.内存的消耗较高,而托管代码运行时P/Invoke的执行效率与Native基本相当,可以忽略不计。我们对托管框架的优化往往也要把重点放在前面提到的2点上。)
2.使用Windows Mobile 6.5的Gesture API提供良好的对手势的支持,如果API不能完全满足要求,自己实现更多功能(并封装成类)。
3.在xml文件里面配置多国语言支持。
4.可以根据设置将Log保存为文件或者打印到控制台窗口。
5.将UI和Business Logic尽量分开。
6.尽量不要使用窗口、控件,而使用图片,使界面更美观并提高运行效率。
7.需要能够适应不同的Windows Mobile版本,并且将程序移植到Windows Embedded CE上时不需要太多的修改。
8.代码需要稳健,容易Exception的地方需要使用Win32结构化异常处理(try,catch)。
性能要求
1.为了使运行效率更快,可以适当增加内存的开销(在480x800分辨率屏幕下内存使用需要控制在15M以内,我们可以看到Moto Today和Spb Mobile Shell的内存使用都在10M以上)。
2.响应时间需要让用户足够的满意(合理使用多线程设计)。
3.用户体验需要足够的好。
4.节约电池电量。
目前实现了的部分
1.程序整体框架已经确定下来,包括消息分发,主要的类的框架。
2.每个Panel的一些简单的功能:
Home Panel中未接电话、未读短信等通知,简单的时间和日期。(需要增加漂亮的动画。)
Contact Panel中读取联系人数据库,并将数据缓存在dat文件中,添加联系人等等。(需要添加Gesture功能。)
Application Panel中简单的显示应用程序图标。(需要实现成从xml文件中读取,然后保存在一个封装了vector的模板类中。)
Picture Panel部分实现了一些手势功能。(这一部分需要实现的见下文。)
3.利用Windows Mobile 6.5 Gesture API实现的手势。
待实现的部分
1.Animation类
2.图片解码
3.程序的皮肤、Application Panel中程序快捷方式等数据存储在xml文件中,程序初始化时从xml文件中读取这些数据。
4.我的Picture Panel的实现思路:
Picture Panel实现以缩略图的方式显示,这些缩略图被储存在一个文件中(Windows桌面操作系统是储存在Thumb.db文件中),Picture Panel每次在内存中缓存2~3个实际屏幕大小的Memory DC,Memory DC里面画好从储存缩略图的文件中取得的各个缩略图。这样做的目的是为了让手势更流畅。Memory DC按2~3实际屏幕大小的粒度从文件中重新读取缩略图,也就是说用户做翻页操作时会有点停顿,因为现在要从文件中重新读取缩略图。
我们需要一个子线程去监测图片文件的变化,并将图片以缩略图的形式存储在一个文件中,这个线程的主要流程如下:
监测到文件变化->
读取图片文件到内存中/记录图片的文件名称、路径、格式等信息->
图片解码(根据前面提供的格式信息来调用不同的解码库,如何解码失败直接跳过这一张图片)->
得到按比例缩小后的Bitmap->
将Bitmap存储到文件中->
通知iToday->
iToday更新Picture Panel
有关Picture Panel更多的idea:
(1).要实现缩略图必须在一个文件里面(.dat,db,或者其它)缓存图片文件夹中的图片,否则效率太低。
(2).应该让用户选择使用缩略图显示还是列表或者其它方式显示,当采用缩略图方式显示时将生成缓存文件。
(3).Today第一次启动时应该另起一个子线程(叫做Picture Thread)去处理图片,把缩略图缓存在文件中,并告诉用户当前处理的进度(可以参考Windows桌面系统上的处理)。处理完之后,Picture Thread用于监测图片文件夹,文件夹中图片有变化时更新缩略图缓存文件,并通知UI主线程。Picture Thread的优先级应该比UI主线程低,以解决在一些情况下(比如新增了大量图片)界面响应用户较慢的问题。
(4).为了提高处理的效率,缩略图缓存文件需要以一定的格式,以便可以将管理缩略图的时间降到最低。为了降低磁盘的使用,需要及时的删除不需要的缩略图。
(5).当Today退出的时候如何知道文件中的图片发生了什么变化,以便更新缩略图缓存文件?如果仅仅是重新全部更新缩略图缓存文件,效率太差。
(6).当图片大小超过500张时,采用以上方法各自的效率如何?用户是否可以接受?
(7).当磁盘没有空间能够使用了,此时如何处理?
(8).是否可以提供对更多图片格式缩略图预览的扩展(图片解码扩展),是以代码的方式扩展,还是以其它方式扩展(比如Windows桌面系统是以COM的方式)?
(9).缩略图缓存文件以什么样的格式存储会更好?Windows桌面系统采用thumbs.db格式,是否可以采用?
(10).Windows Mobile自带的图片与视频程序是如何处理缩略图问题的?缩略图文件存储在哪里?是否可以对其进行扩展以便可以预览更多格式的图片(像Windows桌面系统那样扩展)?
(11).Windows Mobile以下API可以实现文件的监测:
BOOL WINAPI SHChangeNotifyRegister( HWND hwnd, SHCHANGENOTIFYENTRY * pshcne );
BOOL WINAPI SHChangeNotifyDeregister( HWND hwnd );
5.其它一些比较简单的部分:去掉右上角的X,修改开始菜单位置的显示文字。
实现的不好,需要重新实现的部分
1.一些Animation(比如Panel之间切换的动画)是使用如下的方法实现的,是较差的实现:
for( int i=1; i< 9; i++ ) { BitBlt( m_hRealDC, m_rcScreen.left, m_rcScreen.top , m_rcScreen.right - m_rcScreen.right *i/8, m_rcScreen.bottom - m_rcScreen.right/5, m_hPublicSwitchMemoryDC, m_rcScreen.right * i / 8 , m_rcScreen.top , SRCCOPY ); BitBlt( m_hRealDC, m_rcScreen.right - m_rcScreen.right * i/8 , m_rcScreen.top , m_rcScreen.right * i /8 , m_rcScreen.bottom - m_rcScreen.right/5 , m_hPublicMemoryDC, m_rcScreen.left, m_rcScreen.top , SRCCOPY ); Sleep( 20 ); }
需要更换为WM_TIMER方式来实现:
case WM_TIMER: switch (wParam) { case GESTURE_ANIMATION_TIMER_ID: g_pPanelManager->GetClickedPanelPoint()->ProcessAnimationTimer(); break; default: break; } break;
BOOL PicturePanel::ProcessAnimationTimer() { PHYSICSENGINESTATE state = {sizeof(state)}; if ( NULL == g_hPhysicsEngine) { SnapBackToFrame(); DebugPrintString(L"PicturePanel::ProcessAnimationTimer: Invalid state in timer callback\n"); MessageBox(m_hWnd, TEXT("Error"), TEXT("Invalid state in timer callback"), MB_OK); return FALSE; } TKQueryPhysicsEngine(g_hPhysicsEngine, &state); m_ptStartPointInMemDC.x = state.ptPosition.x; m_ptStartPointInMemDC.y = state.ptPosition.y; BitBlt( m_hRealDC, 0, 0, m_rcScreen.right, m_rcScreen.bottom - m_rcScreen.right/5, m_hMemoryDC, m_ptStartPointInMemDC.x, m_ptStartPointInMemDC.y, SRCCOPY ); FillEmptyPlaceWithBlack(); // If the animation is now complete we need to clean up all our resources. // Just to be safe this is done by calling Snap to ensure there is no residual offset if (state.fComplete) { // pPanState here should be valid still (even tho PanAxis can release it) because // the fAnimating flag is guaranteed to be set at this point SnapBackToFrame(); } return TRUE; }
2.当点击开始菜单,然后点击今日后会显示iToday,这是会发现显示的比较慢,这是因为对WM_PAINT消息处理的不好,每次都是重新GetDC,然后再重新BitBlt,需要修改,使用BeginPaint返回的DC。
构架设计问题
1.iToday如何如何运行在系统中?谁来创建窗口?谁来处理窗口消息?
Moto Today有一个插件窗口(窗口类名为Moto Today,大小为240x320),
另外有3个独立的窗口(窗口类名为类3E9F39D-454F-4361-BA31-14DA7F8BD5EB,大小为480x748),这个3个窗口是使用同一个进程中不同的线程创建的。
插件窗口并没有窗口消息,所以功能的主要实现应该不在插件DLL里面。
在Windows Mobile上有两种方案可以选择:
第一种方案:
Today插件创建窗口,然后将消息以消息队列的途径传递给另一个Application处理。
该方案的缺点:
(1).通信造成性能的损耗。
(2).设计复杂,造成编码复杂。
第二种方案:
仅仅在Application中创建窗口和处理窗口消息。
缺点:
因为Application的窗口不是Today的窗口,而仅仅是将Application的窗口置顶,所以会造成把关机提示等窗口也覆盖了,这些需要特殊处理一下。
2.另外为了提高程序的启动效率,UI主线程仅仅初始化用户立马就能看到的界面。而其它界面交给另一个线程处理,另外这个线程还被设计用于监测(不是轮询)图片文件夹,以便在用户不知不觉的情况下更新PicturePanel。
3. iToday使用PanelManager这个类来分发鼠标消息(WM_LBUTTONDOWN、WM_GESTURE等) 以及分发Animation的WM_TIMER消息给Switcher和各个Panel(HomePanel、PicturePanel、ContactPanel、ApplicationPanel)处理,而具体的处理是交给Switcher和各个Panel。分工遵循职责分配原则。
4. PanelManager是如何分发窗口消息的呢?
当用户接触到屏幕时,总会有WM_LBUTTONDOWN消息,在这个消息处理里面首先判断在那个Region(SwitcherRegion、PanelRegion),如果是PanelRegion,把消息交给对应的激活的Panel类处理(默认是HomePanel)。
Gesture消息也会如此分发(因为任何Gesture消息之前都会有WM_LBUTTONDOWN消息,所以你会知道应该把消息交给那个Panel处理)。
5. 所有Panel(Switcher即是屏幕下面的一部分,它也被作为Panel处理)继承PanelBase,PanelBase有以下几个虚方法用于处理手势(比如实现Animation):
////////////////////////////////////////////////////////////////////////// ///Gesture and Animation virtual void SnapBackToFrame(){} //Stop Animation, bring the content back into the visible area virtual HRESULT ProcessPanEnd( //Processes the end gesture and figures out what physics engine animation is needed int nTransitionSpeed, // @parm Start speed of animation (relevant for flick end gesture). Must be a posative value DWORD nTransitionAngle // @parm Angle for start speed (relevant for flick end gesture) ){ return S_OK; } virtual BOOL ProcessAnimationTimer(){ return TRUE; } virtual BOOL ProcessPan(POINTS& pt){ return TRUE; } virtual BOOL RecordLastPanPoint( POINTS& pt ){ return TRUE; } virtual BOOL CalculateStartPointInMemDC( POINTS& pt ){ return TRUE; } //////////////////////////////////////////////////////////////////////////
以上这么做的原因是想尝试让代码足够精简,思路清晰,并且能够重用和扩展。
6. 使用XML文件来实现换肤功能。分离代码和数据(用户对Today的配置信息),如何程序启动的时候找不到对应的配置XML文件,就采用默认的配置信息。
7. LogUtility.cpp负责Log相关
PerfomanceTest.cpp负责性能计数相关
FileUtility文件I/O相关
XMLUtility XML相关
8. 每个Panel有自己的Memory DC,用于记录各自UI的元素,这里可以改进一下,每个Panel保存各自的特有的UI元素为Bitmap。
约定
请参考林锐的《高质量C++编程指南》
优化建议
建议参考《Effective C++》
代码和安装包地址
开发工具:
Visual Studio 2005以上版本
Windows Mobile 6 Professional SDK Refresh
Windows Mobile 6.5 Professional Developer Tool Kit (CHS)
展望
后面会考虑DirectDraw、Direct3D Mobile、OpenGL ES等技术在支持硬件加速的平台上进行更绚丽和高效的界面开发尝试。欢迎一起交流。
我的Windows Embedded CE/Windows Mobile文章索引
也许你看过这些文章后会更好:
如何开发绚丽、高效率的界面(Windows嵌入式系统)(一)
出处: http://wangkewei.cnblogs.com/
版权声明: 本文的版权归作者与博客园共有。转载时须注明本文的详细链接,否则作者将保留追究其法律责任的权利。
您可以从这里更方便的找到我的文章。