当前位置: BOLT界面引擎 > 知识库文章 > 用Bolt引擎实现换肤指南

用Bolt引擎实现换肤指南

作者:韦秋实 2012-03-13

用Bolt引擎实现换肤指南

1.  引言

提到界面引擎,大家一定会想到“换肤”这个关键字。的确,使用界面引擎开发的产品,其换肤功能的实现会比使用系统API开发的简单很多。也有很多朋友经常问我们:“你们的界面引擎支持换肤么? ”其实在不同产品之间,换肤的需求千变万化,引擎的开发者也不可能开发出一个“万金油”型的换肤功能来,而固定的换肤功能又往往不能满足开发者的需要。

在这一点上,Bolt引擎认为:换肤功能应该被放在产品层面,而不是引擎层面;引擎则提供完成换肤功能所必要的自由度和基础功能。基于这些功能,产品可以定制自己的换肤需求,然后使用界面引擎将其方便快速地实现。

 

常见的换肤需求一般有以下几种:

1.  控件资源换肤(比如windows XP的主题改变),但控件的布局不变

2.  换肤同时改变整个界面布局(比如WindowsMeidaPlayer

3.  用户可以通过自定义的图片改变产品的界面(win7的主题)

在原有Windows API下这些需求的实现,使用简单的伪代码描述如下:

1.  假设按钮控件资源换肤

LRESULT MyButton::OnDrawItem()

{

    Const char* resid = ResManager.GetResidByCtrlId(m_id);

    //换肤后得到不同的hBitmap

HBITMAP hBitmap = SkinManager.GetBitmapByResid(resid);

    HDC dc = GetDC();

    DrawBitmap(dc,hBitmap) 

}

Void SkinManager::ChangeSkin(skinName)

{

  ResManager.LoadFrom(skinName.zip);

  UpdateAllWindowInProcess();

}

2.  改变界面布局

LRESULT MyWind::OnDrawItem()

{

    PosInfo nowpos = SkinManager.GetPosByCtrlId(m_id);

    MoveWindow(m_hWnd, nowpos.left, nowpos.top, nowpos.width, nowpos.height);

    UpdateWindow();

}

3.  自定义图片

LRESULT MyBkg::OnDrawItem()

{

    Const char* imagePath = GetBkgPath();

    Int nWidth = 0, nHeight = 0;

    HBITMAP hBitmap = SkinManager.LoadBitmapFromPath(imagePath, &nWidth, &nHeight);

    DrawState(dc, NULL, NULL, hBitmap, 0, 0, 0, 0, 0, DST_BITMAP|DSS_NORMAL);

    UpdateWindow();

}

以使用Bolt引擎开发的迅雷7为例,迅雷7的换肤功能相当强大,除了提供毛玻璃效果、部分透明度调整、任意图片做背景图片之外,还有界面元素调色方案、自动调色、字体效果更换等进阶的换肤功能,而这些功能在其他的产品中是难以见到的。总而言之,迅雷7中的换肤需求有以下几点:

1. 任意图片作为背景图片

2. 界面元素的透明度调整,可以整体调整也可以分块调整

3. 界面边缘阴影

4. 界面毛玻璃效果

5. 界面元素的统一调色

6. 字体的统一更换

引擎按照粒度的层级,将换肤的层次由低到高分为4个层次:对象层、资源层、XAR包层、逻辑层。层次越低,换肤涉及到的对象就越底层,自由度就相对越小。迅雷7的换肤功能主要集中在对象层和资源层,以不改变基本界面样式为前提。应用后两层的换肤方法,可以将换肤应用到产品的每一个角落,实现真正意义上的“随意换”。

在以下的章节中,我们会按照由低到高的层次顺序,辅以迅雷7的换肤需求,讲解不同层面上换肤效果的设计思路。

 

2.  初级换肤:第一层,对象层面的换肤处理

对象层面的换肤,是通过对对象树上单个元素的改变,达到换肤的效果。迅雷7换肤需求中的123三点,就属于对象层面。

产品中最大的界面元素,也是最容易被人忽略的元素,就是软件背景。不像传统基于Windows的开发,在引擎中窗口的背景也是界面对象树的一部分。由于引擎的分层设计特性,对象树必须需要一个足够大的根元素来承载上层的其他元素,此时这个“根元素”就可以作为产品的背景使用。再加上引擎可以实时更改元素的特性,这样最初级的换肤功能就实现了。

下面是一个简单实现了自定义图片背景,填充背景和毛玻璃效果的背景控件的结构,产品的布局可以建立在这个背景控件的基础之上。在这个结构中,几个Object的大小都是相同的,图上这种表示方式是为了清晰地显示出控件结构。其中,ImageObject用于放置自定义的背景图片,FillObject则是为了当背景图片不够大,或者没有背景图片时充当填充背景所用。

BkgCtrl的控件结构

通常情况下,充当背景元素的对象一般是ImageObjectTextureObjectFillObject三种

ImageObject是位图对象,可以显示一张位图,并且有平铺、拉伸、原图三种显示方式,水平和垂直对齐方式也是可调的。迅雷7的图片背景就是使用ImageObject实现的。从文件中读取图片和图片大小等信息,可以使用XLGraphic中的对应C接口来完成。TextureObject是纹理对象,可以按照资源文件中配置的方式,显示一张符合Bolt引擎定义的纹理。若是不想放置图片,也可以用填充对象(FillObject)来充当背景。FillObject可以用单色、线性、圆形这几种填充方法,将指定的颜色填充到其中去。迅雷7的默认背景就是用FillObject做出的。

关于界面透明的实现,引擎中所有元对象都可以通过SetAlpha函数单独地调整透明度。注意此处透明度的调整只限于该对象本身,对该对象的子对象没有影响。

另外要注意的是,界面透明,边缘阴影和Vista以后系统下毛玻璃效果的实现,都依赖于开启主窗口的layer属性。层窗口(Layered窗口)Windows XP以后支持的窗口特性。开启了layer属性的窗口渲染时可以进行alpha混合,而没开启layer属性的窗口在绘制时则忽略alpha属性。由于系统实现上的原因,XP系统下,非layer窗口的渲染效率要比layer窗口高;Vista及以后系统则相反,layer窗口的绘制效率更高。

那么,在bolt引擎中又该如何实现毛玻璃效果呢?毛玻璃效果是通过在界面上放置BlurObject来实现的,想要毛玻璃效果生效,还需配置主窗口的blur属性为1。另外,BlurObject也属于元对象,可以任意调整大小,移动位置。因此,在界面上实现部分毛玻璃效果也是可行的。

 

3.  中级换肤:第二层,现有资源层面的换肤处理

现有资源层面的换肤,指对现有资源包中的资源进行处理,比如调色、更换字体等。由于一个资源可以被多个对象使用,改变一个资源就可以改变多个对象的视觉效果。

Bolt引擎中,每一个能被加载的资源(包括位图、纹理、颜色、字体等),都有一个在本资源包中不重复的id,引擎可以根据id来对指定的资源进行处理。

能做到这一点,正是基于引擎的资源预处理(Pretreat)机制,用户可以对一类资源设置自己的预处理函数。预处理机制在渲染这类资源之前,使用者可以通过预处理函数来改变资源渲染的结果,包括调色、划线、增加文字等;具体可参见引擎参考手册

下面是一个有关设置Pretreat函数的小例子,用Lua代码写成:

local function ModifyWith(h, s, v, bepaint)

        local function Pretreat(resId, resObj)              

            resObj:ModifyColor(h, s, v, bepaint)

        end

        return Pretreat

    end

   

         local xarManager = XLGetObject("Xunlei.UIEngine.XARManager")

         xarManager:SetBitmapPretreater(“mainxar::default.bitmap”, ModifyWith(h,s,v,1))

    xarManager:CommitPretreater()

 

其中,SetBitmapPretreater函数是设置预处理函数的关键,函数原型为SetBitmapPretreater(resID, function),其中ResID为想要设置预处理函数的资源ID,例子中mainxar::default.bitmap代表了mainxar包中的default.bitmap资源;为处理大型产品有多个xar包的情况,SetBitmapPretreater函数是xar相关的,可以通过”xar包名::资源ID”的形式来指定某个xar包中的资源。如果不指定包名,则该函数会被设置到所有加载的xar包中。Function是一个自定义的lua函数,这个lua函数必须返回一个原型为preatret(resID, resObj)lua函数,当被设置的资源ID被渲染时,引擎会主动调用该preatret(resID, resObj)函数,用户可以在函数内对resObj进行操作,改变渲染结果。

要注意的是,对于不同种类的资源,设置预处理的函数也是不同的,目前已经支持的有:

l  SetBitmapPretreater

l  SetColorPretreater

l  SetTexturePretreater

l  SetImageSeqPretreater

该例中,设置的预处理函数只对资源做了ModifyColor操作,即对资源进行调色。ModifyColor使用HSV颜色空间进行调色,与一般的RGB颜色空间不同,可以使用XLGraphic提供的C函数来进行RGBHSV之间的转换。

最重要的一点:设置完成后不要忘了调用CommitPretreater来提交预处理函数的修改,否则设置的预处理函数会不起作用。

为了方便大家调色,XLGraphic提供了C函数XL_GetBitmapMainColor,用于获取位图的主色调,方便大家调色使用,达到界面一致的效果。

 

4.  高级换肤:第三层,xar层面的换肤处理

前两层的换肤处理,都是通过改变已有的对象来达到目的。引擎支持一个xar包中带有多个资源包,并且在运行过程中可以实时更换资源包来达到换肤的效果。

更换资源包可以调用xarManagerSelectResPackage方法,注意新包加载完成以后,之前资源包中所有的资源对象都会被释放。具体可参见开发文档。资源包可以在xarpackage.cfg文件中配置。

在资源包的resource.cfg文件中,可以通过添加loadscriptunloadscript节点,自定义资源包loadunload时的脚本,给予开发者更大的自由度。

这里要注意一下,资源替换和资源预处理的区别。资源预处理只能针对已经加载完成的资源包,即只能对现有资源的基础上进行一些小的改变,改变针对的是某个特定的资源;而资源替换则是将旧有的资源包整个替换成为新的资源包,这是针对所有资源的改变。

 

5.  终极换肤:第四层,逻辑层面的换肤处理

到了这一层,“换肤”已经不是一个具体的功能或者函数,而是一种思想。利用Bolt引擎运行中更改对象状态的特性,“换肤”可以不局限在调整资源和颜色的范畴中,对界面元素的位置,层次甚至个数的调整也是可能的。这一层次的定制灵活性最大,要根据需求的具体情况具体分析。

具体来说,就是通过XLLuaRuntime的相关功能,在换肤的时候执行一段自定义的脚本,通过脚本来实现对布局的修改。

6. 皮肤包和皮肤管理器设计指南

上面的4个换肤层次,主要解释了引擎在换肤方面提供的功能。要将这些功能整合起来,一般会需要开发者自己实现一个“皮肤管理器”,将以上的几部分功能统合起来,真正为己所用。接下来我们就简单看一下,皮肤包和皮肤管理器所应该具有的功能。

要实现第一层级的换肤,更换背景图片、调整透明度等,皮肤包中就要有一个存储了背景图片、透明度设定等的皮肤配置文件;皮肤管理器可以从配置文件中读取各种设定,来通知界面进行改变。管理器最好还能有事件注册机制,界面Lua可以向皮肤管理器中注册事件,当设定更改之后,管理器可以通知Lua,让Lua部分作更灵活的调整。

实现第二层级的换肤,皮肤包应该有一个调色配置文件,调色配置文件包含可调色的资源id,皮肤管理器通过读取配置文件,得到可调色的资源id。当背景图片或者填充对象发生变化时,可以通过这些资源id来调用相对应的SetPretreater函数,达到配色效果。

实现第三层次的换肤效果,可以将额外的资源包放在皮肤包中,管理器通过XLFSIO的映射函数将资源包映射到资源目录中去,就可以通过SelectResPackage函数来更换资源包了。

对于第四层次,逻辑层面的换肤,皮肤包也可以包括一些lua脚本文件作为换肤脚本,皮肤管理器在加载皮肤包的时候,通过XLLuaRuntime的相关接口来执行换肤脚本,达到完全自定义的效果。

 

迅雷公司 版权所有 Copyright 2003-2010 Thunder Inc.All Rights Reserved. 意见反馈:xl7doc@xunlei.com