动态歌词显示技术在现在的音乐播放器已经很常见了。作为程序员的你,有没有想着自己实现动态歌词颜色滚动覆盖效果呢?实际上,实现这个效果其实并不难,只是需要点基础知识罢了。而我们这里使用剪切区域,可以实现逐字的颜色覆盖,而不是逐行的颜色覆盖哦。
所谓“外行看热闹,内行看门道”,作为程序员的我们,不应该留恋于这些特效的使用和摆弄,而是要看看这个技术如何实现,是不是自己也能实现出来。如果能,那是多么激动人心的事情呀。
而这个技术点,在win32就可以做到。很多人一说到动态歌词效果,就想到C#、WPF等实现。虽然是可以做出来这个效果,然而在WPF出现之前,你还能怎么样呢?既然我们是自己研究学习,就没有必要留恋于WPF这样的简单的界面编程。为了提高自己的技术,提高自己的水平,使用更加基础的技术知识实现,是非常有意义的。因为这个应用会增加你学习基础技术的兴趣,也会让你对基础技术了解更加深入,久而久之,你水平自然大幅提升。像WPF这样的界面编程,学过点编程的都可以比较快速的实现,那还有什么意思呢?(排除项目开发和经济利益的角度,纯粹从技术角度说的,你不同意这个说法,请无视!)
那么,动态歌词效果只是我在学习剪切区域的时候,自然而然想到了可以用到这个技术实现,就动手试了一下。果不其然,效果达到了,也证明我理解剪切区域是正确的。结果是一箭双雕,皆大欢喜!
那么下面正式介绍Win32使用剪切区域实现酷狗动态歌词特效:动态歌词颜色滚动覆盖效果。首先你要理解剪切区域的概念,此概念的详细讲解,请参考《 GDI中的剪切区域(裁剪区域)是什么》,此文是纯粹在概念上详细分析了剪切区域是什么。然后请参考文章《 GDI中的剪切区域的设置和使用代码详解》,此文是从代码上使用剪切区域,来加深概念的理解。
动态歌词需要用到剪切区域组合,所以,请参考文章《 区域组合函数CombineRgn的使用详解》。
那么我们就是要实现这个颜色覆盖效果。那么最原先的蓝色字符串,我们一开始就输出来就好了。难点就在于红色的颜色覆盖,而且是准确的覆盖在蓝色的字上面。看上去挺难的,实际上,如果我们直接在蓝色字符串位置输出同样的一句话,同时将颜色设置为红色,不就搞定了。所以,对于颜色覆盖这一点,我们是清楚了的。颜色覆盖的代码如下:
如果你对TextOut函数有疑问,请阅读《 TextOut如何显示多行文本,TextOut函数使用全面分析》。
为了让字更大,我们创建一个字体,而创建字体的参数太多了,所以,我们使用字体结构体参数的方式创建,只设置字体的高度和粗细,其他都置0。而字体我们都在窗口创建的时候创建好,也就是在WM_CREATE消息中创建字体,全局保存。代码如下:
那么我们最后就差一点,那就是如何慢慢的覆盖蓝色的字符串为红色,而且覆盖的不是按照字符数,而是按照像素来的。因为按照字符的话,我们可以一次只输入一定的红色字符覆盖原先的蓝色字符串即可。那么这个就很简单了。然而,在唱歌的时候,有时候一个字要持续很长时间,所以在这个字上要慢慢的覆盖,那么以字符为单位覆盖就没办法了。
我们这里就用剪切区域技术。剪切区域就是显示绘制的范围,超出剪切区域的绘制都是直接放弃的。所以,即使你输出的红色字符串是完全覆盖原先的蓝色字符串的。但是,剪切区域设置之后,非剪切区域则不会绘制了,这样就依然保持这原先的蓝色,在剪切区域中的文字就被覆盖为红色了。
利用剪切区域,就实现了蓝色字符串的一部分被覆盖为了红色。接下来要实现的就是,让剪切区域慢慢变宽,这样就看到了红色覆盖从左到右依次变红,就是滚动红色覆盖蓝色歌词的动态歌词颜色滚动覆盖效果了。那么扩大剪切区域,就使用CombineRgn函数完成,此函数的介绍在文章开头有提到。我们在当前剪切区域紧接的右边创建一个小的剪切区域,然后合并到当前的前切区域中,这样当前剪切区域就变宽了,也就能够显示更多的红色字符了。如此,就达到了红色滚动覆盖了。
矩形区域的创建说明,参见文章《 GDI中矩形区域(含圆角矩形区域)的三种创建方法》。
所以,合并剪切区域的代码如下:
下面是完整代码:
而这个技术点,在win32就可以做到。很多人一说到动态歌词效果,就想到C#、WPF等实现。虽然是可以做出来这个效果,然而在WPF出现之前,你还能怎么样呢?既然我们是自己研究学习,就没有必要留恋于WPF这样的简单的界面编程。为了提高自己的技术,提高自己的水平,使用更加基础的技术知识实现,是非常有意义的。因为这个应用会增加你学习基础技术的兴趣,也会让你对基础技术了解更加深入,久而久之,你水平自然大幅提升。像WPF这样的界面编程,学过点编程的都可以比较快速的实现,那还有什么意思呢?(排除项目开发和经济利益的角度,纯粹从技术角度说的,你不同意这个说法,请无视!)
那么,动态歌词效果只是我在学习剪切区域的时候,自然而然想到了可以用到这个技术实现,就动手试了一下。果不其然,效果达到了,也证明我理解剪切区域是正确的。结果是一箭双雕,皆大欢喜!
那么下面正式介绍Win32使用剪切区域实现酷狗动态歌词特效:动态歌词颜色滚动覆盖效果。首先你要理解剪切区域的概念,此概念的详细讲解,请参考《 GDI中的剪切区域(裁剪区域)是什么》,此文是纯粹在概念上详细分析了剪切区域是什么。然后请参考文章《 GDI中的剪切区域的设置和使用代码详解》,此文是从代码上使用剪切区域,来加深概念的理解。
动态歌词需要用到剪切区域组合,所以,请参考文章《 区域组合函数CombineRgn的使用详解》。
好了,有了上面的三篇基础文章的分析后,我们可以开始讲动态歌词的实现原理了。如果你看完了上面的文章,应该是听得懂下面的原理的。如果听不懂,请回去看基础文章。
我们先看看实现的动态歌词颜色滚动覆盖效果:
【滚动了一部分的歌词,覆盖为了红色】
【滚动了大部分的文字,从左到右滚动覆盖】
动态歌词覆盖的效果是这样的:原先的歌词是蓝色的字符串,然后歌词滚动后,红色的覆盖了蓝色的字符串,随着时间的流逝,蓝色歌词字符串被红色的覆盖的越来越多,直到全部覆盖完,即表示此句歌词唱完。那么我们就是要实现这个颜色覆盖效果。那么最原先的蓝色字符串,我们一开始就输出来就好了。难点就在于红色的颜色覆盖,而且是准确的覆盖在蓝色的字上面。看上去挺难的,实际上,如果我们直接在蓝色字符串位置输出同样的一句话,同时将颜色设置为红色,不就搞定了。所以,对于颜色覆盖这一点,我们是清楚了的。颜色覆盖的代码如下:
//输出底层的蓝色字符串
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;
}