在编写VC界面时,编写动画比较困难,代码重用性不高。编写一个临时动画需要创建定时器或者线程来驱动改变渲染状态,来达到画面实时改变的目的。但是定时器和线程都是比较难以维护的,处理不好很容易造成资源浪费甚至程序崩溃。
Skilla在上一周整理好了skillcore库,这一次又给它增添了通用动画框架。这个动画框架本身没有渲染功能,主要是提供动画的驱动事件,使用时需要自己去处理动画事件去完成动画渲染。该框架比较简单,动画由线程来驱动,下面展示一下具体的构成。
根据动画的特点,就像播放动画片一样,Skilla把动画抽象成了一个动画基类BaseAnimation,里面包括动画的运行时间,运行状态,循环状态,正反序等等。使用时可以调用PlayAnimation,PauseAnimation,ResumeAnimation,StopAnimation等方法来控制动画的运行状态。动画本身还可以绑定动画监听器AnimationListener来触发动画状态改变时的事件,以方便处理。BaseAnimation本身是个抽象类,使用时需实现virtual bool FirstRun() = 0; virtual void Run() = 0; virtual bool LastRun() = 0;这三个接口,firstRun触发时,渲染动画的初始状态,lastRun触发时渲染动画的结束状态,Run触发时则根据runningTime这个时间轴属性来渲染动画每一帧的状态。为了方便使用Skilla实现了三个常用动画子类,PosChangeAnimation AlphaChangeAnimation和SeqFrameAnimation,它们分别是位置改变动画,渐隐渐显(透明度改变)动画以及序列帧动画。关于使用方法在下面介绍。
由于动画的生命周期难以管理,因为有的动画为临时动画,播放一次就再也不用了;而有的动画则需要重复使用,甚至绑定到控件上,和执行动画的控件同生共死。之所以出现这个问题就是因为把动画抽象成了类。这时候用指针来管理动画的生命周期明显是个坏主意,为了保证其通用性,我们采用动画工厂来管理动画的生命周期,创建动画时采用AnimationFactory动画工厂,操作现有动画时,使用AnimationFactory的FindAniamtion方法通过aniamtionName取到动画对象指针,删除动画同样要使用AnimationFactory通过animationName来删除。在应用程序退出时,需要AnimationFactory清理掉所有的剩余动画对象。
下面以duilib界面库为例,看看具体如何使用的
class CloseAnimationListener : public AnimationListener
{
public:
LRESULT OnStop(std::int64_t runningTime,std::int64_t totalTime,bool bReverse,bool bLoop)
{
AnimationFactory::GetInstance()->DeleteAnimation(L"PosChange.CloseAni");
DuiApplicationBase::GetInstance()->RequestQuit();
return AnimationListener::OnStop(runningTime,totalTime,bReverse,bLoop);
}
};
class AlphaChangeListener : public AnimationListener
{
public:
LRESULT OnPlayEnd(std::int64_t runningTime,std::int64_t totalTime,bool bReverse,bool bLoop)
{
BaseAnimation* p = AnimationFactory::GetInstance()->FindAnimation(L"AlphaChange.InitAni");
p->SetReverse(!p->GetReverse());
return 0;
}
};
class CMainFrame
: public WindowImplBase ,public IPosChangeAnimation,public ISeqFrameAnimation,public IAlphaChangeAnimation
{
public:
CMainFrame(void);
~CMainFrame(void);
//duilib相关
static CMainFrame* Instance();
virtual CDuiString GetSkinFolder();
virtual CDuiString GetSkinFile();
virtual LPCTSTR GetWindowClassName(void) const;
CControlUI* CreateControl(LPCTSTR pstrClass);
virtual void SetAnimationPos(const RECT& rect) //实现位置改变动画接口
{
CDuiRect r(rect);
::MoveWindow(*this,r.left,r.top,r.right-r.left,r.bottom-r.top,false);
}
virtual void SetAnimationAlpha(const int Alpha)
{
CControlUI* pContrl = m_PaintManager.FindControl(L"alphaChange");
String str;
str.Format(L"file='alpha.png' fade='%d'",Alpha);
pContrl->SetBkImage(str);
}
virtual void SetAnimationSeqFrame(const int framePos) //实现序列帧动画接口
{
CControlUI* pControl = m_PaintManager.FindControl(L"bg");
if (framePos==0)
{
pControl->SetBkImage(L"11.jpg");
}else if (framePos==1)
{
pControl->SetBkImage(L"12.jpg");
}else if (framePos==2)
{
pControl->SetBkImage(L"13.jpg");
}else if (framePos==3)
{
pControl->SetBkImage(L"7.jpg");
}
}
//窗口相关
void InitWindow();
LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
LRESULT OnClose(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam);
void Notify(TNotifyUI& msg);
ScopedPtr<Thread> initThread;
ScopedPtr<Thread> destroyThread;
ScopedPtr<CloseAnimationListener> closeListener;
ScopedPtr<AlphaChangeListener> alphaListener;
};
首先,将被动画操作的控件事件对应的动画接口,这里以CMainFrame为动画控件,为了方便同时展现三种动画,直接给它把三个动画接口全部实现了,实现具体的动画渲染操作,另外还定义了两个动画监听器的子类CloseAnimationListener,AlphaChangeListener,用来监听动画的运行状态。
下面是实现部分
void CMainFrame::Notify(TNotifyUI& msg)
{
if (_tcsicmp(msg.sType,_T("windowinit"))==0)
{
closeListener = new CloseAnimationListener;
alphaListener = new AlphaChangeListener;
//创建一个序列帧动画
SeqFrameAnimation* animation = ( SeqFrameAnimation*)AnimationFactory::GetInstance()->CreateAnimation(L"SeqFrame.InitAni",SEQ_FRAME_ANIMATION);
animation->SetIntervalTime(600); //设置时间间隔
animation->SetFrameSize(4); //设置帧数
animation->BindObject(this); //绑定对象
animation->SetLoop(true); //设置循环
animation->PlayAnimation(); //开始播放
//创建一个渐隐渐显动画
AlphaChangeAnimation* animation2 = ( AlphaChangeAnimation*)AnimationFactory::GetInstance()->CreateAnimation(L"AlphaChange.InitAni",ALPHA_CHANGE_ANIMATION);
animation2->RegistListener(alphaListener);
animation2->SetTotalTime(3000);
animation2->SetKeyFrameRect(0,255);
animation2->BindObject(this); //绑定对象
animation2->SetLoop(true); //设置循环
animation2->PlayAnimation(); //开始播放
}else if (_tcsicmp(msg.sType,_T("click")) == 0)
{
if (_tcsicmp(msg.pSender->GetName(),_T("btn_close")) == 0)
{
PosChangeAnimation* animation = (PosChangeAnimation*)AnimationFactory::GetInstance()->CreateAnimation(L"PosChange.CloseAni",POS_CHANGE_ANIMATION);
animation->SetTotalTime(600); //设置时间
RECT rect;
GetWindowRect(*this,&rect);
RECT rectTo = {rect.left,(rect.top+rect.bottom)/2,rect.right,(rect.top+rect.bottom)/2};
animation->SetKeyFrameRect(rect,rectTo); //设置位移
animation->BindObject(this); //绑定动画对象
animation->SetReverse(false); //设置正反向播放,如果不明白改变一下属性试试有什么效果
animation->SetLoop(false); //设置是否循环播放
animation->RegistListener(closeListener); //绑定监听器
animation->PlayAnimation(); //开始播放
}
}
}
创建动画对象后,设置时间、位移、正反序等属性,绑定驱动对象以及添加监听器,完成这一系列初始化操作后就可以播放了,在播放过程中还可以任意修改动画属性。
下面是skillcore和Demo的下载链接:
如有问题,或者建议请联系作者:Skilla(QQ:848861075)