解析BLOT例子——HelloBolt5

来源:互联网 发布:怎么改善太阳凹陷知乎 编辑:程序博客网 时间:2024/06/09 21:08

HelloBolt5:使用动画

作者:Tsukasa

     在HellohBolt5中,我们将在HelloBolt4的基础上加入两个很Cool的动画效果,让界面动起来:第一,在关机图标上,每隔5s将其变为另一个图标;第二,当点击关闭按钮时,星星图标在原位置上逐渐变大并消失,正中的HelloBolt文本向右“飞出”界面,之后程序退出。

   
   
  前面的教程中已经说过,Bolt在一个内置时钟的驱动下,每隔若干时间对对象树进行一次渲染,更新对象树上被改变部分的输出,并将渲染结果在容器窗口中显示,称为一帧。比如在前一帧渲染之后,将某个对象移动位置,那么在下一帧渲染时,Bolt会重新渲染对象树上该对象之前的位置部分和之后的位置部分,这样展示出来的界面就发生了变化。如果我们将改变某对象位置这个操作的时间延长,比如延长到100帧,指定对象的初始位置和结束位置,并按照一定的规则对中间每一帧的对象位置进行插值,那么在每一帧中,对象只会被移动一小段距离,渲染出来的结果也会发生相应微小的改变,这样在这段时间内的,视觉上,界面就会展示出对象从初始位置连续移动到结束位置的效果,这就是Bolt中关键帧动画的原理。我们称动画执行的这段时间为持续时间,动画中每一帧的时刻为运行时刻,依照动画改变属性的对象为动画的执行对象。
首先,让我们来看看退出程序时的动画是如何实现的。转到MainWnd.xml.lua中在close_btn_OnLButtonDown函数定义代码如下:
local aniFactory = XLGetObject("Xunlei.UIEngine.AnimationFactory")
     local alphaAni = aniFactory:CreateAnimation("AlphaChangeAnimation")
以上两行中,"Xunlei.UIEngine.AnimationFactory"标识的全局对象是Bolt用于创建动画对象的动画工厂对象, 在动画工厂对象上调用CreateAnimation接口创建指定类型的动画对象,可以创建的动画类型包括Bolt的内建动画(包括改变对象透明度的AlphaChangeAnimation,改变对象位置的PosChangeAnimation等)及在布局xml中自定义的动画类型。这里创建的是一个类型为AlphaChangeAnimation动画对象。

alphaAni:SetTotalTime(700)

alphaAni:SetKeyFrameAlpha(255,0)
以上两行,设置刚创建出来的动画对象的几个关键属性。SetTotalTime接口以ms为单位,指定动画的持续时间,对于改变对象透明度(alpha)的动画来说,要指定执行对象的初始和结束的透明度,通过调用SetKeyFrameAlpha接口来设置。这样这个动画就可以在700ms内将执行对象的透明度从全不透明(255)改变到全透明(0).

local owner = self:GetOwner()

         local icon = owner:GetUIObject("icon")
以上两行获取指向星星图标对象的引用。因为close_btn_OnLButtonDown的self参数指向的是关闭按钮逻对象,而星星图标对象与之在同一对象树之上,可以通过对象树的对象索引接口得到该对象,调用UIObject的GetOwner接口返回该对象所在的对象树对象,调用对象树的GetUIObject接口获取对象树上指定id的UIObject对象,这样我们就得到了需要的对象;

alphaAni:BindRenderObj(icon)

          owner:AddAnimation(alphaAni)
         alphaAni:Resume()

以上几行,用动画的BindRenderObj接口指定执行对象,调用AddAnimation接口将动画对象加入到对象树的正在运行动画列表中,只有加入到该列表中的动画对象才能在时钟的驱动下,更新动画的运行时刻,当到达持续时间时,该动画结束,同时被从列表中移除。最后调用Resume接口,指定动画开始执行。
        类似的创建并执行另外几个动画,注意这里有两个不同动画对象指定了同一个执行对象,在Bolt里这是允许的,前提是两个动画对象是作用于对象的不同属性之上的,比如这里一个动画对象改变透明度,另一个改变位置,如果两个动画对象都改变透明度,Bolt会按照一定的规则来解决两个动画对象的冲突。
     动画的引入让我们的交互逻辑产生了变化:不能在close_btn_OnLButtonDown事件中立刻退出应用程序了,要让HelloBolt在动画结束之后再退出。为了达到这个目的,我们需要为动画对象添加状态改变的响应函数, 动画状态改变响应函数的原型为function onStateChange(self,oldState,newState), self是指向状态改变的动画对象的引用,oldState 和 newState表示改变时的前状态和后状态, 动画的状态 是从0 到 4的整形, 当为4时标明动画已经执行完成,退出程序,代码如下所示:

if newState == 4 then

              os.exit()
     end

     之后调用动画对象上的AttachListener接口指定上面定义的函数作为动画对象的状态响应函数,代码如下:

posAni2:AttachListener(true,onAniFinish)

这样当posAni2动画对象执行完成时,程序退出。
 
     上面使用了Bolt的内建动画,已经可以满足大部分常用动画需求(内建动画手册),除此之外,Bolt还提供了自定义动画机制(自定义动画指南)。转到MainWnd.xml中,定义一个自定义动画,xml节点如下所示:
<animation_def class="HelloBolt.ani">
定义自定义动画要在布局xml中加入<animation_def>节点,其中class属性指定动画的类型名;在<method_def>子节点中加入<Action>节点定义动画的行为, Action函数的原型为function Action(self), 当对象树在每帧中渲染的时候,都会调用该函数。在method_def节点中定义函数, 类似于在eventlist中定义event。比如<Action file=xxx func=yyy/>,其中节点名指定函数名, 该函数名可以作为对象的方法直接调用, 比如HelloBolt.ani类型的对象a可以在lua中用a:Action()的方式调用Action。我们在这里使用Bolt中另一种定义方法或事件的方式: 直接在布局xml中定义方法, 格式如<Action>函数体</Action> 和 <event name=OnLButtonDown>函数体</event>,这种写法比较适合教学演示实例代码,不推荐在实际的工程开发中使用。Action函数的定义如下:

local attr = self:GetAttribute()

     local obj = attr.obj  
local runningTime = self:GetRuningTime()
以上三行中,调用GetRunningTime() 和 GetTotalTime()接口分别获取动画对象执行时刻和通过SetTotalTime()设置的持续时间, 通过这两个值可以自行计算自定义动画的进度。在自定义对象(自定义动画对象和自定义控件对象)上调用GetAttribute()方法获取绑定到该对象上的一个lua table实例,称为属性表。可以在属性表上之上加入任何lua 对象, 在自定义对象的生命周期里可以访问属性表。 虽然没有为自定义动画定义类似BindObject的方法, 但是自定义动画仍然可以在执行Action时通过属性表获取执行对象。在这个Action的定义中,每隔5000ms将执行对象展示的位图改变。

        定义好了自定义动画之后,要通过跟使用内置动画类似的流程来使用之,代码如下所示:

local aniFactory = XLGetObject("Xunlei.UIEngine.AnimationFactory")

     myAni = aniFactory:CreateAnimation("HelloBolt.ani")
     myAni:SetTotalTime(9999999)
     local aniAttr = myAni:GetAttribute()
     aniAttr.obj = newIcon
     owner:AddAnimation(myAni)
     myAni:Resume()

与创建Bolt内置动画基本相同,只是在调用CreateAnimation接口时传入自定义动画的类型名。对照自定义动画Action的定义,通过调用GetAttribute()接口获取属性表并在其中加入元素,将执行对象传入到Action定义中使用。


MainWnd.xml.lua中
--lua文件必须是UTF-8编码的(最好无BOM头)function close_btn_OnLButtonDown(self)---创建内置动画的实例local aniFactory = XLGetObject("Xunlei.UIEngine.AnimationFactory") --Xunlei.UIEngine.AnimationFactory标识的全局对象是Bolt用于创建动画对象的动画工厂对象local alphaAni = aniFactory:CreateAnimation("AlphaChangeAnimation") --在动画工厂对象上调用CreateAnimation接口创建指定类型的动画对象alphaAni:SetTotalTime(700)  --一直运行的动画就是一个TotalTime很长的动画alphaAni:SetKeyFrameAlpha(255,0) --对于改变对象透明度(alpha)的动画来说,要指定执行对象的初始和结束的透明度,                                    --通过调用SetKeyFrameAlpha接口来设置。这样这个动画就可以在700ms内将执行对象的透明度从全不透明(255)改变到全透明(0). local owner = self:GetOwner()local icon = owner:GetUIObject("icon") --以上两行获取指向星星图标对象的引用alphaAni:BindRenderObj(icon)  --用动画的BindRenderObj接口指定执行对象owner:AddAnimation(alphaAni) --调用AddAnimation接口将动画对象加入到对象树的正在运行动画列表中alphaAni:Resume() --最后调用Resume接口,指定动画开始执行。local key = owner:GetUIObject("key")  --先获取对象local posAni = aniFactory:CreateAnimation("PosChangeAnimation")posAni:SetTotalTime(700)posAni:SetKeyFrameRect(45,100,45+60,100+60,45+30,100+30,45+60-30-10,100+60-30-10)      --SetKeyFrameRect方法设置对象开始位置和结束位置的left,top,right,bottom信息,对象的尺寸是随着参数变化。posAni:BindLayoutObj(icon) owner:AddAnimation(posAni)posAni:Resume()    --自定义动画local posAni3 = aniFactory:CreateAnimation("PosChangeAnimation")posAni3:SetTotalTime(1000)posAni3:SetKeyFramePos(150,80,600,80)      --SetKeyFrameRect方法设置对象开始位置和结束位置的left,top,right,bottom信息,对象的尺寸是随着参数变化。posAni3:BindLayoutObj(key) owner:AddAnimation(posAni3)posAni3:Resume()local alphaAni2 = aniFactory:CreateAnimation("AlphaChangeAnimation")alphaAni2:SetTotalTime(700)alphaAni2:SetKeyFrameAlpha(255,0)  --对于改变对象透明度(alpha)的动画来说,要指定执行对象的初始和结束的透明度,                                    --通过调用SetKeyFrameAlpha接口来设置。这样这个动画就可以在700ms内将执行对象的透明度从全不透明(255)改变到全透明(0). local msg = owner:GetUIObject("msg")alphaAni2:BindRenderObj(msg)owner:AddAnimation(alphaAni2)alphaAni2:Resume()---定义动画结束的回调函数local function onAniFinish(self,oldState,newState)if newState == 4 then----os.exit 效果等同于windows的exit函数,不推荐实际应用中直接使用os.exit()endendlocal posAni2 = aniFactory:CreateAnimation("PosChangeAnimation")posAni2:SetTotalTime(800)posAni2:BindLayoutObj(msg)posAni2:SetKeyFramePos(135,100,135,500)--当动画结束后,应用程序才退出posAni2:AttachListener(true,onAniFinish) --AttachListener接口指定上面定义的函数作为动画对象的状态响应函数owner:AddAnimation(posAni2)posAni2:Resume()endfunction OnInitControl(self)local owner = self:GetOwner()--动态创建一个ImageObject,这个Object在XML里没定义local objFactory = XLGetObject("Xunlei.UIEngine.ObjectFactory")local newIcon = objFactory:CreateUIObject("icon2","ImageObject")local xarManager = XLGetObject("Xunlei.UIEngine.XARManager")newIcon:SetResProvider(xarManager)newIcon:SetObjPos(45,165,45+70,165+70)newIcon:SetResID("app.icon2")local function onClickIcon()XLMessageBox("Don't touch me!")end--绑定鼠标事件的响应函数到对象newIcon:AttachListener("OnLButtonDown",true,onClickIcon)self:AddChild(newIcon)--创建一个自定义动画,作用在刚刚动态创建的ImageObject上local aniFactory = XLGetObject("Xunlei.UIEngine.AnimationFactory") --Xunlei.UIEngine.AnimationFactory标识的全局对象是Bolt用于创建动画对象的动画工厂对象myAni = aniFactory:CreateAnimation("HelloBolt.ani") --在动画工厂对象上调用CreateAnimation接口创建指定类型的动画对象--一直运行的动画就是一个TotalTime很长的动画myAni:SetTotalTime(9999999) --SetTotalTime接口以ms为单位,指定动画的持续时间local aniAttr = myAni:GetAttribute()aniAttr.obj = newIconowner:AddAnimation(myAni)myAni:Resume()endfunction MSG_OnMouseMove(self)self:SetTextFontResID ("msg.font.bold")self:SetCursorID ("IDC_HAND")endfunction MSG_OnMouseLeave(self)self:SetTextFontResID ("msg.font")self:SetCursorID ("IDC_ARROW")end

MainWnd.xml中:
<!--XML最好存储为UTF-8编码--><xlue><animation_def class="HelloBolt.ani"><method_def><Action>--自定义动画,短函数可以直接在xml里写代码(正规开发不推荐)local arg={...}local self = arg[1]local attr = self:GetAttribute()local obj = attr.objlocal runningTime = self:GetRuningTime() --调用GetRunningTime() 获取动画对象执行时刻 GetTotalTime() 通过SetTotalTime()设置的持续时间if runningTime%5000 > 2500 thenobj:SetResID("app.icon2")elseobj:SetResID("app.icon")endreturn 1</Action></method_def></animation_def><objtreetemplate id="HelloBolt.Tree" class="ObjectTreeTemplate"><attr><left>-200</left><top>-200</top><width>2000</width><height>2000</height></attr><obj id="app.bkg" class="ImageObject"><attr><left>0</left><top>0</top><width>429</width><height>267</height><!--资源相关的属性使用资源定义xml中设置的资源名--><image>app.bkg</image><alpha>255</alpha></attr><children><obj id="msg" class="TextObject"><attr><left>135</left><top>100</top><width>250</width><height>50</height><text>Hello,Bolt!</text><textcolor>system.orange</textcolor><font>msg.font</font></attr><eventlist><event name="OnMouseMove" file="MainWnd.xml.lua" func="MSG_OnMouseMove" /><event name="OnMouseLeave" file="MainWnd.xml.lua" func="MSG_OnMouseLeave" /></eventlist></obj><obj id="key" class="ImageObject">  <!--自绘图像-->    <attr>    <left>150</left><top>80</top><width>128</width><height>128</height><image>key</image><alpha>200</alpha></attr></obj><!--标题栏,可以模拟App的Titlebar--><obj id="title" class="CaptionObject"><attr><left>0</left><top>0</top><height>32</height><width>father.width</width><zorder>100</zorder></attr><children><obj id="title.text" class="TextObject"><attr><!-- 使用表达式局中--><left>father.width/2-86/2</left> <top>8</top><width>86</width><height>24</height><text>Hello,Bolt!</text><textcolor>system.white</textcolor><font>default.font</font></attr></obj></children></obj><obj id="icon" class="ImageObject"><attr><left>45</left><top>100</top><width>60</width><height>60</height><image>app.icon</image><!--设置成拉伸模式,ImageObject默认是不会拉伸其对应的位图的--><drawmode>1</drawmode></attr></obj><obj id="close.btn" class="LayoutObject"><attr><left>178</left><top>214</top><width>80</width><height>30</height></attr><children><!--对象树布局中直接定义的对象id即使在不同层次上也不能同名--><obj id="close.btn.bkg" class="TextureObject"><attr><left>0</left><top>0</top><width>father.width</width><height>father.height</height><texture>button.normal</texture></attr></obj><obj id="close.btn.msg" class="TextObject"><attr><left>0</left><top>0</top><width>father.width</width><height>father.height</height><font>default.font</font><halign>center</halign><valign>center</valign><text>关闭</text></attr></obj></children><eventlist><event name="OnLButtonDown" file="MainWnd.xml.lua" func="close_btn_OnLButtonDown" /></eventlist></obj></children><eventlist><!--定义事件响应时,如果方法被定义在同名的.xml.lua中同时方法名为事件名时,可以省略file 及 func属性,会损失加载效率, 不推荐--><event name="OnInitControl"/></eventlist></obj></objtreetemplate><hostwndtemplate id="HelloBolt.Wnd" class="FrameHostWnd"><attr> <mainwnd>1</mainwnd><title>Bolt</title><layered>1</layered><left>200</left> <top>100</top><!--注意这是容器窗口的大小,设置的要合理--><width>429</width><height>327</height><cacheleft>0</cacheleft> <cachetop>0</cachetop><cachewidth>1000</cachewidth><cacheheight>720</cacheheight><center>1</center><topmost>0</topmost><visible>1</visible><enable>1</enable><active>1</active><maxbox>0</maxbox><minbox>0</minbox><minwidth>100</minwidth><minheight>72</minheight><maxwidth>1000</maxwidth><maxheight>720</maxheight><appwindow>1</appwindow><fps>30</fps></attr></hostwndtemplate></xlue>