转载请说明原出处,谢谢~·http://blog.csdn.net/zhuhongshu/article/details/49026605
最近一段时间没写博客了,感觉最近没有遇到什么必须解决的bug。在一年前我把自己写的仿酷狗音乐播放器Demo写到博客时,我在博客末尾写过以后会做异形窗体和窗体动画的功能。异形窗体在半年前大概做完并且集成到我的库里了,但是窗体动画Demo没有写到博客。之前就有网友问我窗体动画的制作方法,一直懒着没写,不好意思···。
今天把窗体动画的制作思路和Demo说明一下。实际上,异形窗体写完了,也就可以说窗体动画功能也写完了,因为窗体动画效果只是在使用半透明异形窗体技术基础上的一个应用。关于异形窗体的博客:使用duilib开发半透明异形窗体程序(附源码和demo)
首先把效果图展示一下,本来是有79个特效,但是由于csdn博客的图片限制,只能录一小部分了···:
这里特别声明,我使用的这个动画特效算法不是自己写的,而是使用了开源的界面库UiFeature的特效算法组件,非常感觉UiFeature作者的开源(UiFeature的下载地址:http://yunpan.cn/cZLr5zDIdW8ZT (提取码:ec84)),这同样是个很不错的界面库,UiFeature的动画特效是其一大亮点,而动画特效是个独立的DLL,我这里直接移植到我的Duilib窗体动画Demo里。
说明一下窗体动画的开发步骤和注意点:
1、开启窗体半透明异形功能(原生DuiLib不支持,不过包括我在内的不少维护Duilib的朋友的库都支持,其他的很多界面库也都支持),这个是根本,因为使用UpdateLayeredWindow函数使窗体拥有半透明异形功能后,就可以使用位图来绘制出窗体,而动画实际上就是有规律变化的连续位图。所以把动画位图定时贴到异形窗体上,就是窗体动画了~~。
2、自定义一个容器控件,作为窗体的根容器
3、开始动画时,截取自定义控件的渲染位图,用来计算动画效果
4、为自定义容器增加一个定时器来用动画算法定时计算动画效果
5、重写控件的DoPaint方法,把计算后的动画位图绘制出来
实际上窗体动画是属于控件动画的范畴,在半透明异形窗体中,根容器的效果无疑就是窗体的效果,所以窗体动画的开发实际就转变为了控件动画的开发。把上述方法的根容器改为控件,那就是控件动画的开发步骤。
首先我的库支持透明异性功能,这满足了第一个要求。接下来定义一个容器控件,只要把他作为窗体xml的根容器,就可以做出窗体动画
class AnimLayout : public CVerticalLayoutUI, public IUIEffectCallBack { public: AnimLayout(); ~AnimLayout(); virtual LPCTSTR GetClass() const override; virtual LPVOID GetInterface(LPCTSTR pstrName) override; virtual void DoPaint(HDC hDC, const RECT& rcPaint) override; virtual void SetAttribute(LPCTSTR pstrName, LPCTSTR pstrValue) override; bool StartEffect(); void OnTimer(int iCurFrame); public: // 每一 个 动画开始时回调 virtual void OnUiEffectBegin(WPARAM effectKey, DWORD animaType) override; // 每一 个 动画结束时回调 virtual void OnUiEffectEnd(WPARAM effectKey, DWORD animaType) override; // 每一 帧 动画绘制时回调 virtual void OnUiEffectDraw() override; private: bool m_bPlaying; HDC m_hMemDc; HBITMAP m_hOldBitmap; HBITMAP m_hTransBitmap; DWORD m_dwEffectNum; IUiEffectManagerImpl* m_pEffectManager; IUIEffect* m_pEffect; public: static const LPCTSTR kAnimLayoutClass; static const LPCTSTR kAnimLayoutInterface; };
当需要使用动画特效时,调用StartEffect函数,函数源码为:
bool AnimLayout::StartEffect() { if (m_bPlaying) return false; if (m_dwEffectNum > 80) { m_dwEffectNum = 2; return false; } //LPDWORD pBmpBits = NULL; m_hMemDc = ::CreateCompatibleDC(m_pManager->GetPaintDC()); m_hTransBitmap = CRenderEngine::GenerateBitmap(m_pManager, this, m_rcItem); if (m_hTransBitmap == NULL) return false; m_hOldBitmap = (HBITMAP) ::SelectObject(m_hMemDc, m_hTransBitmap); BITMAP bmDst; GetObject(m_hTransBitmap, sizeof(bmDst), &bmDst); SIZE szMemDc = { bmDst.bmWidth, bmDst.bmHeight }; //修补一下Alpha通道,一些控件(Richedit)会让Alpha为0 RECT rcRestore = m_rcItem; CRenderEngine::RestoreAlphaColor((LPBYTE)bmDst.bmBits, bmDst.bmWidth, &rcRestore); // 填充动画参数 AnimationParam animParam; animParam.effectKey = (WPARAM)this; //控件指针 animParam.animationEffect = m_dwEffectNum++; //动画类型,从2-80,1为自定义动画,并没有移植过来 animParam.animationFrequency = 20; //动画间隔 animParam.bShow = TRUE; //动画顺序 animParam.hBitmap = m_hTransBitmap; animParam.pBmpData = (BYTE*)bmDst.bmBits; animParam.bmpSize = szMemDc; animParam.hdc = m_hMemDc; BOOL bRet = m_pEffect->AppendAnimation(animParam); ASSERT(bRet); m_bPlaying = true; // 这里是同步执行的,Animation函数在动画完毕后返回 bRet = m_pEffect->Animation(dynamic_cast<IUIEffectCallBack*>(this), 0); ASSERT(bRet); // 递归演示所有动画效果,这只是为了演示效果,实际开发不要这样做! StartEffect(); return true; }
在代码里可以看到,开启动画时,调用CRenderEngine类的GenerateBitmap静态函数,使用这个函数可以获取到某个控件的渲染位图,这就满足了第三个要求。接下来应该是开启定时器来使用算法来定时计算位图,如果要精度要求不高那就可以直接调用CPaintManagerUI类的SetTimer方法,并且在自定义控件里响应定时消息来计算位图, 计算后调用Invalidate函数来刷新控件。
不过UiFeature的这个特效组件的Animation函数,已经自带了定时功能,并且在填充了参数后会自动计算了位图,只要重写IUIEffectCallBack接口的OnUiEffectDraw方法来刷新窗体就可以了。所以和正常开发步骤有一些区别。
最后重写DoPaint函数:
void AnimLayout::DoPaint(HDC hDC, const RECT& rcPaint) { if (!m_bPlaying) { __super::DoPaint(hDC, rcPaint); return; } typedef BOOL(WINAPI *LPALPHABLEND)(HDC, int, int, int, int, HDC, int, int, int, int, BLENDFUNCTION); static LPALPHABLEND lpAlphaBlend = (LPALPHABLEND) ::GetProcAddress(::GetModuleHandle(_T("msimg32.dll")), "AlphaBlend"); BLENDFUNCTION bf = { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA }; lpAlphaBlend(hDC, m_rcItem.left, m_rcItem.top, m_rcItem.right - m_rcItem.left, m_rcItem.bottom - m_rcItem.top, m_hMemDc, 0, 0, m_rcItem.right - m_rcItem.left, m_rcItem.bottom - m_rcItem.top, bf); }
在函数里,判断如果正在使用动画特效,就不进行控件的正常绘制,而是直接调用AlphaBlend函数把计算后的位图贴到窗体dc上,就完成了第五步。 至此就做完了窗体动画!
总结: