HelloBolt5:使用动画
作者:Tsukasa
在HellohBolt5中,我们将在HelloBolt4的基础上加入两个很Cool的动画效果,让界面动起来:第一,在关机图标上,每隔5s将其变为另一个图标;第二,当点击关闭按钮时,星星图标在原位置上逐渐变大并消失,正中的HelloBolt文本向右“飞出”界面,之后程序退出。
![](http://xldoc.xl7.xunlei.com/upload/image/Tutorial5-1.bmp)
前面的教程中已经说过,Bolt在一个内置时钟的驱动下,每隔若干时间对对象树进行一次渲染,更新对象树上被改变部分的输出,并将渲染结果在容器窗口中显示,称为一帧。比如在前一帧渲染之后,将某个对象移动位置,那么在下一帧渲染时,Bolt会重新渲染对象树上该对象之前的位置部分和之后的位置部分,这样展示出来的界面就发生了变化。如果我们将改变某对象位置这个操作的时间延长,比如延长到100帧,指定对象的初始位置和结束位置,并按照一定的规则对中间每一帧的对象位置进行插值,那么在每一帧中,对象只会被移动一小段距离,渲染出来的结果也会发生相应微小的改变,这样在这段时间内的,视觉上,界面就会展示出对象从初始位置连续移动到结束位置的效果,这就是Bolt中关键帧动画的原理。我们称动画执行的这段时间为持续时间,动画中每一帧的时刻为运行时刻,依照动画改变属性的对象为动画的执行对象。
首先,让我们来看看退出程序时的动画是如何实现的。转到MainWnd.xml.lua中在close_btn_OnLButtonDown函数定义代码如下:
local aniFactory = XLGetObject("Xunlei.UIEngine.AnimationFactory")
local alphaAni = aniFactory:CreateAnimation("AlphaChangeAnimation")
|
alphaAni:SetTotalTime(700)
alphaAni:SetKeyFrameAlpha(255,0)
|
local owner = self:GetOwner()
local icon = owner:GetUIObject("icon")
|
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接口指定上面定义的函数作为动画对象的状态响应函数,代码如下:
以上三行中,调用GetRunningTime() 和 GetTotalTime()接口分别获取动画对象执行时刻和通过SetTotalTime()设置的持续时间, 通过这两个值可以自行计算自定义动画的进度。在自定义对象(自定义动画对象和自定义控件对象)上调用GetAttribute()方法获取绑定到该对象上的一个lua table实例,称为属性表。可以在属性表上之上加入任何lua 对象, 在自定义对象的生命周期里可以访问属性表。 虽然没有为自定义动画定义类似BindObject的方法, 但是自定义动画仍然可以在执行Action时通过属性表获取执行对象。在这个Action的定义中,每隔5000ms将执行对象展示的位图改变。
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()
|
定义好了自定义动画之后,要通过跟使用内置动画类似的流程来使用之,代码如下所示:
MainWnd.xml中:
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()
end
end
local 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()
end
function 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 = newIcon
owner:AddAnimation(myAni)
myAni:Resume()
end
function MSG_OnMouseMove(self)
self:SetTextFontResID ("msg.font.bold")
self:SetCursorID ("IDC_HAND")
end
function 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.obj
local runningTime = self:GetRuningTime() --调用GetRunningTime() 获取动画对象执行时刻 GetTotalTime() 通过SetTotalTime()设置的持续时间
if runningTime%5000 > 2500 then
obj:SetResID("app.icon2")
else
obj:SetResID("app.icon")
end
return 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>