解析BLOT例子——HelloBolt5

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()
		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>


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值