创建动画
窗口
- 这个内容的设计是,作为一个组件存在(DLL),其他一个地方,会在比较早的时机统一调用这些插件的同名接口。
- 调用的时候会创建UI对象,并在这个UI对象的初始化里面,创建下面的窗口对象,并指定该对象位置,调用该对象初始化函数InitControls,设置该对象的可见与否。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
class AccelerateQualityWnd : public SXYWindow { friend class AccelerateQualityWndEvaluatePage; friend class AccelerateQualityWndQuestionPage; friend class AccelerateQualityWndSuggestPage; public: explicit AccelerateQualityWnd(); virtual ~AccelerateQualityWnd(); public: void InitControls(); void AniShow(); void AniHide(); SWindow* GetVisiblePage(); protected: const wchar_t* evaluate_pagename = L"evaluate"; const wchar_t* question_pagename = L"question"; const wchar_t* suggest_pagename = L"suggest"; void SetPage(LPCTSTR pszName); void SetScore(int nScore); void SetSelect(int nSelect); int GetScore() { return m_nScore; } int GetSelect() { return m_nSelect; } private: bool OnBkgAlphaAniEx(EventArgs* pEvt); bool OnMainLayPosAniEx(EventArgs* pEvt); SOUI_MSG_MAP_BEGIN() MSG_WM_CHILD_PAGE(m_page_evaluate) SOUI_MSG_MAP_END() private: AccelerateQualityWndEvaluatePage m_page_evaluate; AccelerateQualityWndQuestionPage m_page_question; AccelerateQualityWndSuggestPage m_page_suggest; int m_nScore; int m_nSelect; }; |
动画消息
- 在初始化InitControls这里,为目标控件添加相关动画消息:给背景图片添加了alpha相关的消息,给主窗口添加了pos相关的消息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
void AccelerateQualityWnd::InitControls() { auto bkg = FindChildByName2<SWindow>(L"bkg"); if (bkg) { bkg->GetEventSet()->addEvent(EVENTID(EventAlphaAnimateStartEx)); bkg->GetEventSet()->addEvent(EVENTID(EventAlphaAnimateStopEx)); bkg->GetEventSet()->subscribeEvent(EVT_ALPHA_ANI_START_EX, Subscriber(&AccelerateQualityWnd::OnBkgAlphaAniEx, this)); bkg->GetEventSet()->subscribeEvent(EVT_ALPHA_ANI_STOP_EX, Subscriber(&AccelerateQualityWnd::OnBkgAlphaAniEx, this)); } auto main_lay = FindChildByName2<SWindow>(L"main_lay"); if (main_lay) { main_lay->GetEventSet()->addEvent(EVENTID(EventPosAnimateStartEx)); main_lay->GetEventSet()->addEvent(EVENTID(EventPosAnimateStopEx)); main_lay->GetEventSet()->subscribeEvent(EVT_POS_ANI_START_EX, Subscriber(&AccelerateQualityWnd::OnMainLayPosAniEx, this)); main_lay->GetEventSet()->subscribeEvent(EVT_POS_ANI_STOP_EX, Subscriber(&AccelerateQualityWnd::OnMainLayPosAniEx, this)); } m_page_evaluate.InitControls(this); m_page_question.InitControls(this); m_page_suggest.InitControls(this); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
bool AccelerateQualityWnd::OnBkgAlphaAniEx(EventArgs* pEvt) { if (pEvt->GetID() == EventAlphaAnimateStartEx::EventID) { EventAlphaAnimateStartEx *e = (EventAlphaAnimateStartEx*)pEvt; if (atv_out == e->nValue) { SetVisible(TRUE, TRUE); FindChildByName2<STabCtrl>(L"quality_tab")->SetCurSel(evaluate_pagename, false); _SetUIObjectAlpha(sobj_cast<SWindow>(pEvt->sender), 0); } if (atv_back == e->nValue) { } } if (pEvt->GetID() == EventAlphaAnimateStopEx::EventID) { EventAlphaAnimateStopEx *e = (EventAlphaAnimateStopEx*)pEvt; if (atv_out == e->nValue) { auto rect = GetWindowRect(); auto alphaani = _PosAniHelper->Add(FindChildByName2<SWindow>(L"main_lay"), 300, CRect(rect.left, rect.top + rect.Height(), rect.right, rect.bottom + rect.Height()), rect, atv_out, CREATEINTERPOLATOR(SAccelerateInterpolator::GetClassName())); if (alphaani) alphaani->StartAni(); } if (atv_back == e->nValue) { SetVisible(FALSE, TRUE); } } return true; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
bool AccelerateQualityWnd::OnMainLayPosAniEx(EventArgs* pEvt) { if (pEvt->GetID() == EventPosAnimateStartEx::EventID) { EventPosAnimateStartEx *e = (EventPosAnimateStartEx*)pEvt; if (atv_out == e->nValue) { auto rect = GetWindowRect(); sobj_cast<SWindow>(pEvt->sender)->Move(CRect(rect.left, rect.top + rect.Height(), rect.right, rect.bottom + rect.Height())); sobj_cast<SWindow>(pEvt->sender)->SetVisible(true, true); } if (atv_back == e->nValue) { } } if (pEvt->GetID() == EventPosAnimateStopEx::EventID) { EventPosAnimateStopEx *e = (EventPosAnimateStopEx*)pEvt; if (atv_out == e->nValue) { m_page_evaluate.StartTimer(); } if (atv_back == e->nValue) { sobj_cast<SWindow>(pEvt->sender)->SetVisible(false, true); auto alphaani = _AlphaAniHelper->Add(FindChildByName2<SWindow>(L"bkg"), 500, 255, 0, atv_back, CREATEINTERPOLATOR(SDecelerateInterpolator::GetClassName())); if (alphaani) alphaani->StartAni(); } } return true; } |
通知
- 因为这个UI对象是的父类是实现了通知机制的,所以在收到自定义的通知的时候,就开始SHOW相关内容,触发动画。
- 同理,收到关于隐藏的自定义通知的时候,也是类似处理。
- 下面的AniShow就是启动动画。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
LRESULT AccelerateQualityEvaluateUIComponent::OnNotify(UINT uMsg, LPARAM lParam) { if (WM_XY_COM_ASYNC_SHOW == uMsg) { Show(lParam); return 0; } else if (WM_XY_ACCELERATE_EVALUATE_HIDE == uMsg) { Hide(true); } return 0; } |
1 2 3 4 5 6 7 8 9 10 11 12 |
HRESULT AccelerateQualityEvaluateUIComponent::Show(LPARAM lParam) { do { if (!m_pAccelerateQualityWnd) break; m_pAccelerateQualityWnd->AniShow(); } while (false); return S_OK; } |
触发展示
- 可以看到,把目标bkg对象,以及一些参数传了过去,创建了一个对象,把这个对象添加到了一个map里面,并返回了这个对象,通过这个SAlphaAni启动动画
1 2 3 4 5 6 |
void AccelerateQualityWnd::AniShow() { auto alphaani = _AlphaAniHelper->Add(FindChildByName2<SWindow>(L"bkg"), 500, 0, 255, atv_out, CREATEINTERPOLATOR(SDecelerateInterpolator::GetClassName())); if (alphaani) alphaani->StartAni(); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
SAlphaAni* SAlphaAniHelper::Add(SWindow* obj, int totaltime, int beginalpha, int endalpha, int value, IInterpolator* interpolator/* = CREATEINTERPOLATOR(SLinearInterpolator::GetClassName())*/) { SAlphaAni* posan = nullptr; if (nullptr == obj) return posan; int id = (++index) % 1000000; if (m_animap.end() == m_animap.find(id)) { posan = new SAlphaAni(obj, totaltime, beginalpha, endalpha, this, id, value, interpolator); m_animap[id] = posan; } return posan; } |
- 触发动画,可以看到使用上面创建的那个对象的一些成员变量,具象化了一个临时的EventAlphaAnimateStartEx,然后调用FireEvent对目标控件触发目标消息
1 2 3 4 5 6 7 8 |
void SAlphaAni::StartAni() { EventAlphaAnimateStartEx evt(m_obj); evt.aniid = m_id; evt.nValue = m_value; m_obj->FireEvent(evt); m_obj->GetContainer()->RegisterTimelineHandler(this); } |
触发消失
- 上面的窗口里面有3个page,这里是第一个page点击了关闭后,就掉触发隐藏动画
1 2 3 4 5 6 7 8 9 |
bool AccelerateQualityWndEvaluatePage::OnCloseBtnClick(SOUI::EventArgs* pEvtBase) { EndTimer(); ReportClose(); ClearCountTime(); m_parent->AniHide(); ClearCloseText(); return true; } |
- 创建了一个退场动画消息相关的对象,对象被添加到了map,然后返回了对象,然后通过StartAni调用FireEvent触发目标消息
1 2 3 4 5 6 7 8 |
void AccelerateQualityWnd::AniHide() { auto rect = GetWindowRect(); auto alphaani = _PosAniHelper->Add(FindChildByName2<SWindow>(L"main_lay"), 300, rect, CRect(rect.left, rect.top + rect.Height(), rect.right, rect.bottom + rect.Height()), atv_back, CREATEINTERPOLATOR(SAccelerateInterpolator::GetClassName())); if (alphaani) alphaani->StartAni(); } |
动画事件
- 上面分析了,调用AniShow后最后会通过FireEvent触发目标消息,这个目标消息就是一开始AccelerateQualityWnd::InitControls里面对目标添加的EVT_POS_ANI_START_EX,而这个事件和OnMainLayPosAniEx函数绑定到了一起,可以看到EventPosAnimateStopEx绑定了同一个函数
1 2 3 4 5 6 7 |
auto main_lay = FindChildByName2<SWindow>(L"main_lay"); if (main_lay) { main_lay->GetEventSet()->addEvent(EVENTID(EventPosAnimateStartEx)); main_lay->GetEventSet()->addEvent(EVENTID(EventPosAnimateStopEx)); main_lay->GetEventSet()->subscribeEvent(EVT_POS_ANI_START_EX, Subscriber(&AccelerateQualityWnd::OnMainLayPosAniEx, this)); main_lay->GetEventSet()->subscribeEvent(EVT_POS_ANI_STOP_EX, Subscriber(&AccelerateQualityWnd::OnMainLayPosAniEx, this)); } |
- 只要触发了这两个事件中的一个,就会调用OnMainLayPosAniEx这个函数,分辨是哪个事件,是通过
e->nValue
,也就是一开始创建的时候传过去的atv_out和atv_back - 每个动画在开始前会触发一个事件,在结束后触发一个事件。
该动画效果是先改变背景的阿尔法值,然后改变窗口位置,并在出场结束后,启动了一个定时器,这是因为需要在一定时间后,开始改变关闭按钮的文案,让文案显示倒计时。 - 而隐藏的时候,值是atv_back,可以看到,开始事件里没有做事情,而结束事件里,启动了另一个动画,是对阿尔法值的改变。
就是当改变背景阿尔法值的动画走完后,又启动一个新的动画,用来改变窗口位置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
bool AccelerateQualityWnd::OnMainLayPosAniEx(EventArgs* pEvt) { if (pEvt->GetID() == EventPosAnimateStartEx::EventID) { EventPosAnimateStartEx *e = (EventPosAnimateStartEx*)pEvt; if (atv_out == e->nValue) { auto rect = GetWindowRect(); sobj_cast<SWindow>(pEvt->sender)->Move(CRect(rect.left, rect.top + rect.Height(), rect.right, rect.bottom + rect.Height())); sobj_cast<SWindow>(pEvt->sender)->SetVisible(true, true); } if (atv_back == e->nValue) { } } if (pEvt->GetID() == EventPosAnimateStopEx::EventID) { EventPosAnimateStopEx *e = (EventPosAnimateStopEx*)pEvt; if (atv_out == e->nValue) { m_page_evaluate.StartTimer(); } if (atv_back == e->nValue) { sobj_cast<SWindow>(pEvt->sender)->SetVisible(false, true); auto alphaani = _AlphaAniHelper->Add(FindChildByName2<SWindow>(L"bkg"), 500, 255, 0, atv_back, CREATEINTERPOLATOR(SDecelerateInterpolator::GetClassName())); if (alphaani) alphaani->StartAni(); } } return true; } |
关于通知
- 这个g_pRequest的内容,是在CMainDlg的构造里面调用XunYouInitializeInterfaces传过去的CMainDlg对象
- 因为CMainDlg是继承自CXunYouRequestImpl,而CXunYouRequestImpl是继承自IXunYouRequest
- 所以别的模块调用了AsyncNotify,实质是调到了CMainDlg的AsyncNotify里面,然后使用PostMessageW发了WM_ASYNC_NOTIFY消息,而前两个参数被使用MAKEWPARAM打包成WPARAM
- 而在CMainDlg的消息映射里面,OnAsyncNotify映射了WM_ASYNC_NOTIFY消息
- 在OnAsyncNotify里面,遍历所有加载了的组件,找到对应的type,然后调用它的OnNotify,然后就到了上面的AccelerateQualityEvaluateUIComponent::OnNotify这里。
1 |
g_pRequest->AsyncNotify(XY_ComponentType_AccelerateQualityEvaluate, WM_XY_COM_ASYNC_SHOW, NULL); |
解析
ISwndContainer
- 抽象类,继承自一个抽象类ITimelineHandler
SWindow
- 包含了ISwndContainer对象
创建过程
- 传进去的是一个SWindow对象,用这个SWindow对象创建了SAlphaAni
- 执行了SAlphaAni对象的SAlphaAni
ITimelineHandler
- 抽象类,接口OnNextFrame
SAlphaAni
- 继承自一个抽象类ITimelineHandler,实现OnNextFrame
- StartAni触发开始动画事件,ISwndContainer
- StopAni,ISwndContainer
- OnNextFrame
SwndContainerImpl
- 继承自ISwndContainer和SWindow
分析
- 创建了一个动画事件对象,这个动画事件对象保存了目标控件对象,同时这个动画事件对象有个基类是ITimelineHandler
- 而目标控件对象,有个成员变量,叫ISwndContainer,这个ISwndContainer的基类也是ITimelineHandler
- 所以当调用这个StartAni的时候,除了触发这个动画开始事件,还把动画事件对象的this设置给了目标控件对象m_obj保存的ISwndContainer成员
- ISwndContainer成员拿个传过来的动画事件对象后,就在list里面查找,没找到,就添加到列表里面
- 而在SwndContainerImpl的OnNextFrame这个函数里面,会把这个列表拷出来一份,做一个循环调用,就是把每个存起来的动画事件对象的OnNextFrame调一遍
- 也就是,上一步会调到一开始创建的那个动画事件对象的OnNextFrame函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
void SAlphaAni::OnNextFrame() { m_iStep++; if (m_iStep > m_nSteps) { StopAni(); _SetUIObjectAlpha(m_obj, m_endalpha); EventAlphaAnimateStopEx evt(m_obj); evt.aniid = m_id; evt.nValue = m_value; m_obj->FireEvent(evt); m_manager->Del(m_id); return; } EventAlphaAnimateStepEx evt2(m_obj); evt2.aniid = m_id; evt2.nValue = m_value; evt2.nStep = m_iStep; m_obj->FireEvent(evt2); float fPos = m_aniInterpoloator->getInterpolation(m_iStep*1.0f / m_nSteps); m_noewalpha = m_beginalpha + fPos*(m_endalpha - m_beginalpha); _SetUIObjectAlpha(m_obj, int(m_noewalpha)); } |
- 可以看到,一个动画会按多少次完成,是按照我们设置的时间来算的,totaltime除以10,就是次数
- 每一次执行,都会创建一个EventAlphaAnimateStepEx对象,会记录id,value,以及当前的执行次数,然后对目标窗口去触发这个事件
- 然后,第一次启动的时候动画事件对象的时候,还启动了个定时器TIMER_NEXTFRAME
1 2 3 4 5 6 |
BOOL SHostWnd::RegisterTimelineHandler( ITimelineHandler *pHandler ) { BOOL bRet = SwndContainerImpl::RegisterTimelineHandler(pHandler); if(bRet && m_lstTimelineHandler.GetCount()==1) SWindow::SetTimer(TIMER_NEXTFRAME,10); return bRet; } |
1 2 3 4 5 |
BOOL SWindow::SetTimer(char id,UINT uElapse) { STimerID timerID(m_swnd,id); return (BOOL)::SetTimer(GetContainer()->GetHostHwnd(),DWORD(timerID),uElapse,NULL); } |
- 可见,这个定时器消息,是发给了hostwnd,然后在SHostWnd中响应的
1 2 3 4 5 6 7 8 9 10 11 12 |
void SHostWnd::OnSwndTimer( char cTimerID ) { if(cTimerID==TIMER_CARET) { SASSERT(m_bCaretShowing); _DrawCaret(m_ptCaret,m_bCaretActive); m_bCaretActive=!m_bCaretActive; }else if(cTimerID==TIMER_NEXTFRAME) { if(!::IsIconic(m_hWnd)) OnNextFrame(); } } |
- 当最后一步的时候,会在StopAni里面杀掉对应的定时器,然后会对目标控件对象触发一个EventAlphaAnimateStopEx的事件
本文为原创文章,版权归Aet所有,欢迎分享本文,转载请保留出处!
你可能也喜欢
- ♥ Windows物理内存虚拟内存03/28
- ♥ C++_友元、联合体、内联、static、指针、深浅拷贝06/21
- ♥ C++_多态、类型转换、数据段、BSS段、类型视图06/21
- ♥ C++17_第三篇06/29
- ♥ STL_queue06/07
- ♥ 51CTO:C++语言高级课程一08/07