Win32使用剪切区域实现酷狗动态歌词特效:动态歌词颜色滚动覆盖效果

动态歌词显示技术在现在的音乐播放器已经很常见了。作为程序员的你,有没有想着自己实现动态歌词颜色滚动覆盖效果呢?实际上,实现这个效果其实并不难,只是需要点基础知识罢了。而我们这里使用剪切区域,可以实现逐字的颜色覆盖,而不是逐行的颜色覆盖哦。
    所谓“外行看热闹,内行看门道”,作为程序员的我们,不应该留恋于这些特效的使用和摆弄,而是要看看这个技术如何实现,是不是自己也能实现出来。如果能,那是多么激动人心的事情呀。
    而这个技术点,在win32就可以做到。很多人一说到动态歌词效果,就想到C#、WPF等实现。虽然是可以做出来这个效果,然而在WPF出现之前,你还能怎么样呢?既然我们是自己研究学习,就没有必要留恋于WPF这样的简单的界面编程。为了提高自己的技术,提高自己的水平,使用更加基础的技术知识实现,是非常有意义的。因为这个应用会增加你学习基础技术的兴趣,也会让你对基础技术了解更加深入,久而久之,你水平自然大幅提升。像WPF这样的界面编程,学过点编程的都可以比较快速的实现,那还有什么意思呢?(排除项目开发和经济利益的角度,纯粹从技术角度说的,你不同意这个说法,请无视!)
    那么,动态歌词效果只是我在学习剪切区域的时候,自然而然想到了可以用到这个技术实现,就动手试了一下。果不其然,效果达到了,也证明我理解剪切区域是正确的。结果是一箭双雕,皆大欢喜!
    那么下面正式介绍Win32使用剪切区域实现酷狗动态歌词特效:动态歌词颜色滚动覆盖效果。首先你要理解剪切区域的概念,此概念的详细讲解,请参考《 GDI中的剪切区域(裁剪区域)是什么》,此文是纯粹在概念上详细分析了剪切区域是什么。然后请参考文章《 GDI中的剪切区域的设置和使用代码详解》,此文是从代码上使用剪切区域,来加深概念的理解。
    动态歌词需要用到剪切区域组合,所以,请参考文章《 区域组合函数CombineRgn的使用详解》。

    好了,有了上面的三篇基础文章的分析后,我们可以开始讲动态歌词的实现原理了。如果你看完了上面的文章,应该是听得懂下面的原理的。如果听不懂,请回去看基础文章。

    我们先看看实现的动态歌词颜色滚动覆盖效果:

Win32使用剪切区域实现酷狗动态歌词特效:动态歌词颜色滚动覆盖效果1

【滚动了一部分的歌词,覆盖为了红色】

Win32使用剪切区域实现酷狗动态歌词特效:动态歌词颜色滚动覆盖效果2

【滚动了大部分的文字,从左到右滚动覆盖】

    动态歌词覆盖的效果是这样的:原先的歌词是蓝色的字符串,然后歌词滚动后,红色的覆盖了蓝色的字符串,随着时间的流逝,蓝色歌词字符串被红色的覆盖的越来越多,直到全部覆盖完,即表示此句歌词唱完。
    那么我们就是要实现这个颜色覆盖效果。那么最原先的蓝色字符串,我们一开始就输出来就好了。难点就在于红色的颜色覆盖,而且是准确的覆盖在蓝色的字上面。看上去挺难的,实际上,如果我们直接在蓝色字符串位置输出同样的一句话,同时将颜色设置为红色,不就搞定了。所以,对于颜色覆盖这一点,我们是清楚了的。颜色覆盖的代码如下:
//输出底层的蓝色字符串 
SetTextColor(hdc,RGB(0,0,255)); 
TextOut(hdc,50,50,txt,lstrlen(txt)); 
//在相同的位置输出红色字覆盖蓝色的字 
SetTextColor(hdc,RGB(255,0,0)); 
TextOut(hdc,50,50,txt,lstrlen(txt));
    看到没,就这些代码就实现了颜色覆盖,而且准确覆盖。哈哈哈,其实很简单吧。txt字符数组当然是事先准备好的。
    如果你对TextOut函数有疑问,请阅读《 TextOut如何显示多行文本,TextOut函数使用全面分析》。
    为了让字更大,我们创建一个字体,而创建字体的参数太多了,所以,我们使用字体结构体参数的方式创建,只设置字体的高度和粗细,其他都置0。而字体我们都在窗口创建的时候创建好,也就是在WM_CREATE消息中创建字体,全局保存。代码如下:
memset(&logFont,0,sizeof(logFont)); 
logFont.lfHeight=50; 
logFont.lfWeight=200; 
hFont = CreateFontIndirect(&logFont);
    那么字体、字符串、颜色覆盖都准备好了。我们要让颜色覆盖慢慢的滚动,那就要用一个计时器。我们同样在WM_CREATE消息中创建一个计时器,设置100ms的间隔,代码如下:
SetTimer(hwnd,1,100,NULL);
    在计时器消息WM_TIMER中,我们只是简单的让客户区无效,导致客户区重绘即可。动态歌词的动态滚动覆盖,就是在WM_PAINT消息中完成。我们回到歌词的动态滚动覆盖这个中心来。
    那么我们最后就差一点,那就是如何慢慢的覆盖蓝色的字符串为红色,而且覆盖的不是按照字符数,而是按照像素来的。因为按照字符的话,我们可以一次只输入一定的红色字符覆盖原先的蓝色字符串即可。那么这个就很简单了。然而,在唱歌的时候,有时候一个字要持续很长时间,所以在这个字上要慢慢的覆盖,那么以字符为单位覆盖就没办法了。
    我们这里就用剪切区域技术。剪切区域就是显示绘制的范围,超出剪切区域的绘制都是直接放弃的。所以,即使你输出的红色字符串是完全覆盖原先的蓝色字符串的。但是,剪切区域设置之后,非剪切区域则不会绘制了,这样就依然保持这原先的蓝色,在剪切区域中的文字就被覆盖为红色了。
    利用剪切区域,就实现了蓝色字符串的一部分被覆盖为了红色。接下来要实现的就是,让剪切区域慢慢变宽,这样就看到了红色覆盖从左到右依次变红,就是滚动红色覆盖蓝色歌词的动态歌词颜色滚动覆盖效果了。那么扩大剪切区域,就使用CombineRgn函数完成,此函数的介绍在文章开头有提到。我们在当前剪切区域紧接的右边创建一个小的剪切区域,然后合并到当前的前切区域中,这样当前剪切区域就变宽了,也就能够显示更多的红色字符了。如此,就达到了红色滚动覆盖了。
    矩形区域的创建说明,参见文章《 GDI中矩形区域(含圆角矩形区域)的三种创建方法》。
    所以,合并剪切区域的代码如下:
HRGN hrgn= CreateRectRgn(200+i,50,200+i+20,100); 
CombineRgn(hRgn,hRgn,hrgn,RGN_OR);//将创建的区域合并到当前区域 
i+=10;//更新下一个小区域的创建起始位置 
DeleteObject(hrgn);//区域用完要删除
    实际上,我们可以看到,在每一次客户区重绘的时候,都要合并形成新的区域,然后设置为当前的剪切区域。不要以为设置一次当前剪切区域后,就不用再设置了哦。因为每次消息处理的时候,DC都是重新获取的,都是初始化的默认值哦。
    下面是完整代码:
#include "windows.h" 
#include <tchar.h> 
TCHAR txt[]=_T("C++技术网http://www.cjjjs.cn,提供免费配套的C语言、C++语言、Win32编程、Linux编程、经典书籍下载、零基础编程课程、项目开发经验、学习经验、工作经验、数据结构、算法思维、程序员学习发展等一站式的服务。"); 
HRGN hRgn; 
// - 项目是Unicode字符集 
LRESULT CALLBACK WinProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam) 
{ 
    HDC hdc; 
    PAINTSTRUCT ps; 
    static int i=0; 
    static LOGFONT logFont; 
    static HFONT hFont; 
    static RECT rectClient; 
    switch (message) 
    { 
    case WM_TIMER: 
        InvalidateRect(hwnd,NULL,FALSE); 
        return 0; 
    case WM_CREATE: 
        SetTimer(hwnd,1,100,NULL); 
        hRgn = CreateRectRgn(50,50,200,100); 
        memset(&logFont,0,sizeof(logFont)); 
        logFont.lfHeight=50; 
        logFont.lfWeight=200; 
        hFont = CreateFontIndirect(&logFont); 
        GetClientRect(hwnd,&rectClient); 
            return 0; 
        case WM_PAINT: 
            { 
                hdc = BeginPaint(hwnd,&ps); 
                //形成新的区域 
                HRGN hrgn= CreateRectRgn(200+i,50,200+i+20,100);//创建一个小区域 
                CombineRgn(hRgn,hRgn,hrgn,RGN_OR);//将创建的区域合并到当前区域 
                i+=10;//更新下一个小区域的创建起始位置 
                DeleteObject(hrgn);//区域用完要删除 
 
                //输出底层的蓝色字符串 
                SelectObject(hdc,hFont); 
                SetTextColor(hdc,RGB(0,0,255)); 
                TextOut(hdc,50,50,txt,lstrlen(txt)); 
                //设置剪切区域 
                SelectObject(hdc,hRgn);//剪切区开始生效 
                //在相同的位置输出红色字覆盖蓝色的字 
                SetTextColor(hdc,RGB(255,0,0)); 
                TextOut(hdc,50,50,txt,lstrlen(txt));               
                EndPaint(hwnd,&ps); 
            } 
            return 0; 
        case WM_DESTROY: 
            DeleteObject(hRgn);//删除区域 
            PostQuitMessage(0); 
            return 0; 
        default: 
        break;//跳出到默认处理 
    } 
    return DefWindowProc(hwnd, message, wParam, lParam); 
} 
 
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrev,LPSTR lpCmd,int iShow) 
{ 
    TCHAR ClassName[] = _T("MyClass"); 
    TCHAR title1[] = _T("C++技术网http://www.cjjjs.cn"); 
    WNDCLASS wndClass; 
    wndClass.cbClsExtra=0; 
    wndClass.cbWndExtra=0; 
    wndClass.hbrBackground= (HBRUSH)GetStockObject(WHITE_BRUSH); 
    wndClass.hCursor=LoadCursor(NULL,IDC_ARROW); 
    wndClass.hIcon=LoadIcon(NULL,IDI_APPLICATION); 
    wndClass.hInstance = hInstance; 
    wndClass.lpfnWndProc = WinProc; 
    wndClass.lpszClassName = ClassName; 
    wndClass.lpszMenuName=NULL; 
    wndClass.style=CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS; 
 
    if(!RegisterClass(&wndClass))return 0; 
    HWND hwnd = CreateWindow(ClassName,title1,WS_OVERLAPPEDWINDOW,10,100,800,400,NULL,NULL,hInstance,NULL); 
    ShowWindow(hwnd,SW_SHOWNORMAL); 
 
    MSG msg; 
    while (GetMessage(&msg,NULL,0,0)) 
    { 
        TranslateMessage(&msg); 
        DispatchMessage(&msg); 
    } 
    return msg.wParam; 
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值