(转)UI的那点事
说到编程,对于我来说,不得不说一下最初的经历。最早一次接触计算机是在92年,高一。学校里给开了一门课叫劳动技术课也叫上机课。其实就是学习basic编程。那时候用的APPLE II机器。最经典的那种,屏幕是黑白的,也不是纯黑白,而且有些绿色。一开机就直接进入ROM的basic解释环境。光标一闪一闪等待输入basic语句。没有硬盘,有5.25的软驱,但是没见过软盘只听说过。那时候能通过一些基本的语句打印出一些由星号(*)组成的图形就是最大的乐趣。那时候对于计算机的认识也是极其有限,自然是没听过GUI的概念更别说见过了,虽然那时候离世界上第一个商用GUI系统的发布已经是10年之久。
第一次写GUI是在上大学以后,96年。那时候windows95已经非常流行了。486也已经很多了。但是那时候的游戏却大都是DOS系统的。大街上到处都是游戏机房,学校周围更是数不胜数。那些游戏机房里的机器大都是对等网。甚至很多还是无盘工作站。玩游戏的大都是学生,那时候的学生,包括计算机系的学生也不见得都很懂DOS的操作。机房的老板连电脑线都大都不会连,更别说会那些DOS命令了。因此我就写了一个DOS下仿windows界面的一个游戏入口界面。采用TC+ASM实现,说起来其实也很简单。把屏幕初始化为图形方式,在屏幕上画上满满当当的类似windows里的按钮,每个按钮上面写上游戏的名称。把鼠标驱动起来,用户可以通过键盘箭头键或鼠标选择某一个按钮。回车或点击即可进入游戏。界面可以翻页但是没有滚动条。游戏名称和对应的执行文件写在配置文件里。那时候自己觉得比较满意的就是按钮的选择框采用了一个定时器,把颜色从0-255之间不断变化,看起来五彩斑斓的,很是醒目。花力气最多的是显示中文,那时候没有现在这么舒服,需要自己从点阵字库里读取字模然后一个点一个点的打上去。最自豪的是吧这个GUI小程序给了一个机房的老板用后换回了可以随时免费使用电脑的权利。
在开始学习windows程序之后,一开始是基于SDK写的,最受不了的是那个长长消息处理函数。而且总是越来越长。最麻烦的是调整一点点UI上面的东西都要重新编译。而且每个控件都要算坐标。在大概98年底的时候接触MFC,对于UI开发确实是爽了不少。渐渐的能感觉到UI开发是一件非常枯燥而乏味的事情。而且每个控件都是一个德性没什么美观可言。
接触的第一个自绘GUI是在99年下半年,刚毕业,那时候看到公司里的产品界面的很漂亮,除了open/savefile dialog几乎所有的窗口都是自绘的。那套引擎不知道是哪位前辈写的。虽然产品UI很漂亮,但是那套引擎用起来却不是那么舒服。还不是库,产品需要基于源码开发,需要配置一大堆通过代码的配置,业务逻辑总是和UI逻辑掺杂在一起。修改UI更是痛苦。好在已经不需要为控件算坐标了,因为每个窗口每个控件都是通过图来实现的。控件的位置是通过单独的一张叫mask的灰度图来定位的。大概1年多以后,公司也觉得这个引擎确实不太好用。所以决定再开发一套更加方面的引擎。目标就是使得UI开发更加方面同时提供运行时动态布局控件。所以把他称为DynamicGUI。这个引擎比起第一个确实要好多了。首先,他是一个独立的dll。不再需要源码级的重用了。这也使得UI逻辑和业务逻辑能够分离得相对好一些。其次,我们把给每个控件的每个状态一张图,同时提供一张作用同上的mask图。mask除了能识别控件位置外而且还可以识别出控件的类型。通过灰度图的渐变还可以实现平滑过渡的效果。所以也可以叫做mask驱动的UI引擎。控件的消息处理类似MFC采用了反射机制,每个控件都可以自己处理自己的消息。而最最重要的进步我认为是提供了一个相当重要的工具,这个工具可以把UED设计的图片进行自动切割和打包。并且自动生成c++代码。程序员只要在哪些空着的函数里写商业逻辑代码即可。但是这个引擎和前一个有着一个共同的缺点就是图像资源很重。每个产品的UI资源都很大。好在那时候做的是桌面产品,用户对这个似乎不太关心。当然对于程序性能也是有影响的。DynamicGUI又用了大概两年吧。做了两个版本,一个是控件是非窗口的,一个是所有空间都是窗口。两个版本各有各得好处。基于窗口做的实现起来要简单一些,消息处理也比较简单。非窗口在做效果上比较有优势。但是实现要复杂一些。之后我们又做了另外一个引擎,本质差不多。只是图片不再是jpg,而是改为PSD,采用图层的方式,把图都合并了。之后是在嵌入式平台做了另外一个UI引擎。因为是在嵌入式设备里用,所以有几个方面必须考虑:1. 内存和CPU是相当稀奇的资源,所以皮肤不但要能支持图片还要支持纯色渲染。2. 运行时的窗口必须能序列化到存储设备(通常是flash),必要时再重新载入。3. 鉴于嵌入式设备硬件以及操作系统的多样性,必须考虑能很好的跨平台。4. 不再采用mask驱动,而是采用配置文件驱动。那时候xml还不流行,所以采用了自己定义的格式。整个引擎在实现时还是花了一些时间的。为了实现跨平台,封装了硬件设备的主要接口(比如3D硬件加速,硬件解码,乘法运算等)和操作系统的主要接口(比如文件系统,线程相关,定时器等)。整个引擎的最下层除了硬件接口和操作系统接口以外是图形引擎。比如linux下的framebuffer,windows下的GDI或一些3D引擎。在这上面一层是自己的窗口管理系统和消息管理系统。最上面才是普通意义的UI引擎。管理布局,皮肤,渲染等。
之后主要工作又回到了windows上来,依然缺不了UI引擎。但是实现东西基本都没有跳出上面的框框。无论是mask驱动还是xml驱动其实没有太大的本质区别。引擎的最大作用只是实现了通过mask或xml创建界面。把程序员从布局的调整/贴图中解脱出来了。可以更加关注的去实现软件的其他功能。但是程序员依然需要关心界面上有多少个控件,每个控件是干什么用的。还必须清楚的指导xml中每个控件的名称(ID)是什么。别看这点很小,但是其实很烦的。如果命名不好就可能要老是翻看配置文件。很无趣的。如果用工具自动命名那就更难看了。为了更进一步的让程序员从这些重复的工作中解脱出来之后还有一个版本的引擎。他是在上面版本的基础上开发的。最大的区别是程序员不再需要关心界面上还有哪些控件。也不关心哪个控件干什么用。这些都是由xml自动驱动的。从前面得几个版本来说xml或其他形式的配置文件都是被动的提高信息而已,也就是当程序要需要信息的时候采取问配置文件要,然后解析。那么如果反过来的话,让xml主动的去决定UI上需要哪些元素,以及都实现哪些功能的话。那么UI引擎才真正实现了xml驱动。说白了其实也很简单。一般情况下,xml配置文件里的每个控件都通过提供名字来供程序使用,需要实现xml驱动,那么xml配置文件最重要的不是提供名字而是实现的功能,甚至有时候不需要提供名字了。这个功能描述通常会对应到程序的某个函数去。这样也就实现了由xml驱动程序。而不是有程序来简单的调用xml配置文件。这个版本里由于没有采用脚本语言,因此不是所有的UI行为都可以描述的。所以说这个版本只是部分实现了xml的驱动。
现阶段比较完善的一种UI引擎是脚本驱动的方式。前几天还和智勇在讨论脚本驱动到底有什么好处。对于程序自不待言:很多UI逻辑都由脚本驱动。这样对于程序的灵活性和可扩展性都是大大的提高。脚本的能力自然不是xml可以比拟的。其实除了程序的角度,从软件开发的角度来说也是大有脾益的。现在已经很少有哪个成熟的商业产品说是有一个人实现了。开篇我们就说过,一个产品的UI好坏直接影响着产品。而UI得设计绝不是“程序员”的强项。一般来说,产品的界面都由专职的UI设计师或UED来完成。这里就有一个问题,UI设计师设计出来的通常是一些图片,然后将图片交给开发人员实现到软件中去。UI设计师在设计的时候无法实时的看到自己设计的UI运行起来的样子,而软件开发人员也许无法通过图片完全理解UI设计师,这样就有可能UI设计师设计出来的UI要到开发完成后才能看到真正的效果。在一些过程的表现上,UI设计师也无法仅仅使用图片来表达。这样必然导致UI设计师和开发人员的沟通成本和效率。另外开发人员从UI设计师手里获得的哪些图片也许还无法马上就可以使用,因为也许需要把图切割成符合UI引擎的要求。还要命名,然后把这些名字写入配置文件。。。其实这中间还有着很多的工作量。如果UI引擎能够把UI逻辑和业务逻辑完全分离,让UI设计师的图片加上脚本就可以直接看到程序运行的界面,那么这样必然大大提高UI设计师和软件开发人员的效率。如果再加入一种职位:脚本工程师。那么软件开发工程师将能够比较彻底的从UI开发当中解脱出来。专心的去实现业务上的内容。而把UI部分交给UI设计师和脚本工程师来完成。我想这是一种新的软件开发分工模式。可谓术业有专攻,这样UI设计师,脚本工程师和核心业务开发工程师都可以做自己擅长的事情。效率自然会高很多。脚本驱动从原理上说其实也很简单,也就是应用程序会开放一些对象给脚本,供脚本访问或者控制。通常称为“受控对象”。然后通过脚本引擎装载运行脚本,而脚本会在某些时候去操作应用程序开发给脚本的对象。当然反过来也是可以的,即应用程序在运行的过程中当某些事件发生时去执行脚本中对应的事件处理程序。在windows平台采用COM机制可以很容易就实现VBS或JS脚本的支持。当然也可以自己实现通过Lua等脚本的支持。实现的代价并不会很大。
前面我们谈了很多UI引擎,那么可能有人要问到底什么样的UI引擎才能算是好的UI引擎呢?我觉得这个问题还满不容易回答的。因为每个UI引擎都是针对特定的应用场合开发的。没有哪一个UI引擎说可以适用所有的应用场景。如果从windows平台桌面程序开发的角度来说,我认为一个好的UI引擎从产品需求来说最好有以下几个特征:
1. 使用简单:提供外部使用的接口要尽可能的清晰简单。使用者不需要经过专门的培训或学习就可以使用;
2. 高效:效率一定要高。无论是初始化窗口、渲染还是消息的分发的速度都要够快。占用内存还一定要低;
3. 提供自动生成排布配置文件的工具。不再需要手工写配置文件。这一点尤其重要;
4. 提供图形切割工具。供切割图形之用。切割之后的图形供排布工具使用;有没有完善的工具很大程度上是衡量一个UI引擎好坏的重要指标;
5. 能够使用各种复杂的UI排布。而且运行时排布能够自动化;
6. 方便本地化
7. 支持多层:每个窗口或空间支持多层渲染;
8. 单窗口:为了实现绚丽的效果,窗口内控件采用非窗口实现。这种方式的安全性也要高一些,恶意用户要模拟窗口控件行为或根据子窗口进行分析相对要难一些;
9. 渲染引擎最好采用3D渲染引擎,比如Direct3D或OpenGL,加上第五点,做效果就相对容易很多了。性能也会好很多;
10. 支持图形渲染也支持颜色渲染;
11. 脚本驱动;
从设计实现的角度来说也需要具备一些特征:
1. 一个UI系统最核心的我想应该是控件或窗口的属性以及消息/事件了,因此对于属性的处理以及消息的处理是非常关键的。如果这两个内容处理不好,效率一定不会好;
2. 要足够的灵活,提供足够用的控件。同时又能非常方便的添加控件,甚至是在运行时添加;
3. 提供统一的资源(图片,文字,字体等)管理,最好有scheme的概念。节省由于资源开销的内存;
4. 使用统一的渲染接口,这样便于切换使用不同的渲染引擎;
5. 对于跨平台,本人不是十分热衷于跨平台的东西,因为跨平台性、性能、灵活性等总是需要一个折中的。我们不能希望实现一个系统所有的方面都是最好的。
6. 对于灵活性,我也不提倡越灵活越好的做法。理由同5. 我们做一个东西总是需要有些限制条件的,比如使用环境等。只要够用就好。但是可扩展性一定要好。
对比分析一下我们目前使用的UI引擎:
1. 在易用性方面还是不错的,接口还比较清晰,用法也比较符合windows的习惯;
2. 性能方面要稍微弱一些,根据性能测试,主要集中在对属性和消息的处理。原来消息是使用字符串,后来优化使用数字了。其实使用字符串有字符串的好处。其实使用字符串不是性能下降的根本原因。打个比方:我们有一百种不同的消息。需要对发来的消息进行识别。如果你是通过发来的字符串和100个消息字符串比较,那么性能自然是不会好了。最简单的优化,比如把100个消息字符串按照长度分成二十组,首先计算发来消息的长度。然后只要在相同长度的那一组里进行比较就OK了。这样简单的优化就可以将极限比较100个字符串降低到只要比较5个字符串。而代价只是计算一个字符串的长度而已;
3. 没有提供统一的资源和渲染接口。更重要的是没有提供合适的工具帮忙开发人员和UI设计人员减轻工作;
4. 如果能支持3D渲染引擎以及单窗口那就好了。性能和UI效果都会上一个台阶;