在编写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)