這里自定義了一個(gè)Label,通過DrawRect方法獲取Label的圖形上下文,使用混合填充的方式實(shí)現(xiàn)Label繪制顏色
- 先介紹一下混合填充的參數(shù):
void UIRectFillUsingBlendMode(CGRect rect, CGBlendMode blendMode);
CGBlendMode參數(shù)為一個(gè)枚舉類型:
/* 對(duì)應(yīng)公式(其余是固定的):
result, source, and destination colors with alpha;
Ra, Sa, and Da are the alpha components of these colors.
R --> result
S --> source
D --> destination
kCGBlendModeNormal, R = S + D*(1 - Sa)
kCGBlendModeMultiply,
kCGBlendModeScreen,
kCGBlendModeOverlay,
kCGBlendModeDarken,
kCGBlendModeLighten,
kCGBlendModeColorDodge,
kCGBlendModeColorBurn,
kCGBlendModeSoftLight,
kCGBlendModeHardLight,
kCGBlendModeDifference,
kCGBlendModeExclusion,
kCGBlendModeHue,
kCGBlendModeSaturation,
kCGBlendModeColor,
kCGBlendModeLuminosity,
kCGBlendModeClear, R = 0
kCGBlendModeCopy, R = S
kCGBlendModeSourceIn, R = S*Da
kCGBlendModeSourceOut, R = S*(1 - Da)
kCGBlendModeSourceAtop, R = S*Da + D*(1 - Sa)
kCGBlendModeDestinationOver, R = S*(1 - Da) + D
kCGBlendModeDestinationIn, R = D*Sa
kCGBlendModeDestinationOut, R = D*(1 - Sa)
kCGBlendModeDestinationAtop, R = S*(1 - Da) + D*Sa
kCGBlendModeXOR, R = S*(1 - Da) + D*(1 - Sa)
kCGBlendModePlusDarker, R = MAX(0, (1 - D) + (1 - S))
kCGBlendModePlusLighter R = MIN(1, S + D)
*/
kCGBlendModeNormal樣式公式為:
R = S + D * ( 1 - Sa )
結(jié)果 = 源顏色 + 目標(biāo)顏色 * (1-源顏色各透明組件的透明度)
以kCGBlendModeNormal為例,在這里,我們填充的是一個(gè)顏色,顏色的透明度為1,也就是源顏色透明度為1,所以Sa = 1
R = S + D*(1 - Sa) --> R = S + D*(1 - 1) --> R = S
這種情況下, kCGBlendModeNormal 和kCGBlendModeCopy類型是一樣的效果(使用的就是源顏色填充)
kCGBlendModeCopy, R = S
- 實(shí)現(xiàn)歌詞變色我們需要使用到的是kCGBlendModeSourceIn:
kCGBlendModeSourceIn, R = S*Da -> 結(jié)果 = 源顏色*目標(biāo)透明度
我們這個(gè)案例中的源顏色和目標(biāo)顏色:
源顏色 --> 就是要繪制上去的顏色/填充色 ([[UIColor greenColor] setFill];)
目標(biāo)顏色 --> Label當(dāng)前的顏色(文字顏色和透明),上下文中已經(jīng)有的顏色
D: Label
默認(rèn)的文字部分有有顏色 透明度是1
其余部分使用的是透明色 透明度是0
S: 填充色(源顏色)
當(dāng)前圖形上下文中的內(nèi)容的不透明度
結(jié)合公式: R = S*Da ,當(dāng)混合填充時(shí)
文字部分: R = S * Da (Da=1) -> R = S -> 顯示的就是源顏色(填充色)
其余部分: R = S * Da (Da=0) -> R = 0 -> 不進(jìn)行填充/顯示目標(biāo)顏色原有的顏色(透明色)
- 聲明屬性,存放當(dāng)前變色歌詞進(jìn)度,在setter方法中執(zhí)行重繪
// 更新進(jìn)度的時(shí)候執(zhí)行重繪
- (void)setProgress:(CGFloat)progress{
_progress = progress;
// 執(zhí)行重繪
[self setNeedsDisplay];
}
- 設(shè)置歌詞變色的進(jìn)度
lrc格式的歌詞文件無法實(shí)現(xiàn)根據(jù)節(jié)奏設(shè)置變色進(jìn)度,這里取平均值:
每一句歌詞在每句歌詞顯示的總時(shí)間內(nèi),勻速的變色
平均速度進(jìn)行計(jì)算 : (當(dāng)前播放時(shí)間 - 當(dāng)前句起始時(shí)間) / 當(dāng)前句總時(shí)間
當(dāng)前句總時(shí)間: (下一句的起始時(shí)間 - 當(dāng)前句的起始時(shí)間)
因?yàn)楦柙~變色進(jìn)度也是需要實(shí)時(shí)更新的,所以也是需要在控制器下的定時(shí)器方法內(nèi)執(zhí)行的,這里就用到了當(dāng)當(dāng)前歌詞索引為最后一條時(shí),自定義的一條虛擬歌詞對(duì)象
CGFloat averageProgress = ([JSMusciManager sharedMusicManager].currentTime - currentLyric.initialTime) / (nextLyric.initialTime - currentLyric.initialTime);
接下來就是導(dǎo)入自定義Label頭文件,身份檢測器下綁定,修改Label類型,傳遞數(shù)據(jù),這樣就可以實(shí)現(xiàn)歌詞變色了
自定義Label代碼:
#import "JSColorLabel.h"
@implementation JSColorLabel
- (void)drawRect:(CGRect)rect {
// 調(diào)用父類方法: 將Label上的文字繪制上
[super drawRect:rect];
// 設(shè)置填充色
// [[UIColor greenColor] setStroke]; // 描邊
[[UIColor greenColor] setFill]; // 填充
// 設(shè)置填充色的區(qū)域 (默認(rèn)文字為白色,填充后為綠色,只需要根據(jù)當(dāng)前歌詞顯示進(jìn)度來改變填充的寬度,其他不變)
rect = CGRectMake(rect.origin.x, rect.origin.y, rect.size.width *self.progress, rect.size.height);
// 渲染
// 在某個(gè)區(qū)域中使用混合模式進(jìn)行填充
/*
kCGBlendModeNormal公式: R = S + D*(1 - Sa) --> 結(jié)果 = 源顏色 + 目標(biāo)顏色 * (1-源顏色各透明組件的透明度)
在這里;
源顏色 --> 就是要繪制上去的顏色/填充色 ([[UIColor greenColor] setFill];)
目標(biāo)顏色 --> Label當(dāng)前的顏色(白色和透明),上下文中已經(jīng)有的顏色
*/
UIRectFillUsingBlendMode(rect, kCGBlendModeSourceIn);
/* 對(duì)應(yīng)公式(其余是固定的):
result, source, and destination colors with alpha;
Ra, Sa, and Da are the alpha components of these colors.
R --> result
S --> source
D --> destination
kCGBlendModeNormal, R = S + D*(1 - Sa)
kCGBlendModeMultiply,
kCGBlendModeScreen,
kCGBlendModeOverlay,
kCGBlendModeDarken,
kCGBlendModeLighten,
kCGBlendModeColorDodge,
kCGBlendModeColorBurn,
kCGBlendModeSoftLight,
kCGBlendModeHardLight,
kCGBlendModeDifference,
kCGBlendModeExclusion,
kCGBlendModeHue,
kCGBlendModeSaturation,
kCGBlendModeColor,
kCGBlendModeLuminosity,
kCGBlendModeClear, R = 0
kCGBlendModeCopy, R = S
kCGBlendModeSourceIn, R = S*Da
kCGBlendModeSourceOut, R = S*(1 - Da)
kCGBlendModeSourceAtop, R = S*Da + D*(1 - Sa)
kCGBlendModeDestinationOver, R = S*(1 - Da) + D
kCGBlendModeDestinationIn, R = D*Sa
kCGBlendModeDestinationOut, R = D*(1 - Sa)
kCGBlendModeDestinationAtop, R = S*(1 - Da) + D*Sa
kCGBlendModeXOR, R = S*(1 - Da) + D*(1 - Sa)
kCGBlendModePlusDarker, R = MAX(0, (1 - D) + (1 - S))
kCGBlendModePlusLighter R = MIN(1, S + D)
*/
}
// 更新進(jìn)度的時(shí)候執(zhí)行重繪
- (void)setProgress:(CGFloat)progress{
_progress = progress;
// 執(zhí)行重繪
[self setNeedsDisplay];
}
@end
控制器下更新歌詞方法中計(jì)算平均進(jìn)度,并給Label的progress屬性賦值
// 更新歌詞
- (void)updateLyric{
// 當(dāng)前歌詞
JSLyricModel *currentLyric = self.lyricModelArray[self.currentLyricIndex];
// 下一句歌詞 ( 2.判斷越界問題)
JSLyricModel *nextLyric = nil;
if (self.currentLyricIndex == self.lyricModelArray.count - 1) {
// 創(chuàng)建一個(gè)最大的下一句歌詞
nextLyric = [[JSLyricModel alloc]init];
// 給自定義出來的最后一條歌詞設(shè)置數(shù)據(jù) (設(shè)置成最后一條歌詞的數(shù)據(jù))
nextLyric.content = currentLyric.content;
// 因?yàn)楫?dāng)前索引已經(jīng)是最后一條歌詞,所以上面的歌詞賦值就相當(dāng)于nextLyric.content = [self.lyricModelArray lastObject].content;
// 直接設(shè)置成歌曲的總時(shí)長
nextLyric.initialTime = [JSMusciManager sharedMusicManager].duration;
}else{
nextLyric = self.lyricModelArray[self.currentLyricIndex + 1];
}
// 正向調(diào)整進(jìn)度(判斷越界問題): 判斷時(shí)間,改變當(dāng)前的歌詞的索引 : 當(dāng)前播放時(shí)間 > 下一句歌詞的起始時(shí)間 歌詞索引 +1
if ([JSMusciManager sharedMusicManager].currentTime > nextLyric.initialTime && self.currentLyricIndex < self.lyricModelArray.count - 1) {
self.currentLyricIndex++;
// 拖拽進(jìn)度條時(shí),只需要顯示最近當(dāng)前歌詞,防止拖動(dòng)歌詞逐條跳動(dòng)
[self updateLyric];
// 1. 當(dāng)累加到正確的當(dāng)前歌詞索引時(shí),下面才給歌詞賦值,否則遞歸調(diào)用返回
return;
// 如果不進(jìn)行遞歸調(diào)用直接return: 這里更新數(shù)據(jù)的定時(shí)器間隔時(shí)間為0.1s,假如將進(jìn)度條拖拽到歌詞索引60的位置,那么等到定時(shí)器自動(dòng)調(diào)用到到歌詞索引為60的歌詞數(shù)據(jù)時(shí),需要6s的時(shí)間才可以
}
// 反向調(diào)整進(jìn)度(判斷越界問題): 當(dāng)前時(shí)間 < 當(dāng)前句歌詞的初始時(shí)間 歌詞索引-1
if ([JSMusciManager sharedMusicManager].currentTime < currentLyric.initialTime && self.currentLyricIndex > 0) {
self.currentLyricIndex--;
[self updateLyric];
return;
}
// 設(shè)置歌詞
self.verticalLyricLabel.text = self.lyricModelArray[self.currentLyricIndex].content;
self.horizonLyricLabel.text = self.lyricModelArray[self.currentLyricIndex].content;
#pragma mark -- 設(shè)置歌詞變色
/* 設(shè)置歌詞變色進(jìn)度
平均速度進(jìn)行計(jì)算 : (當(dāng)前播放時(shí)間 - 當(dāng)前句起始時(shí)間) / 當(dāng)前句總時(shí)間
當(dāng)前句總時(shí)間 : 下一句的起始時(shí)間 - 當(dāng)前句的起始時(shí)間)
*/
CGFloat averageProgress = ([JSMusciManager sharedMusicManager].currentTime - currentLyric.initialTime) / (nextLyric.initialTime - currentLyric.initialTime);
self.horizonLyricLabel.progress = averageProgress;
self.verticalLyricLabel.progress = averageProgress;
}
為了將功能模塊獨(dú)立出來,所以每個(gè)小的功能都封裝了一個(gè)方法
updateLyric(更新歌詞方法)會(huì)在updateData(更新數(shù)據(jù)的方法)中調(diào)用
updateData是一個(gè)定時(shí)器計(jì)時(shí)調(diào)用的方法
self.timer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(updateData) userInfo:nil repeats:YES];