歌词处理-歌词变色 - (Obj-C)

这里自定义了一个Label,通过DrawRect方法获取Label的图形上下文,使用混合填充的方式实现Label绘制颜色

  • 先介绍一下混合填充的参数:
void UIRectFillUsingBlendMode(CGRect rect, CGBlendMode blendMode);

CGBlendMode参数为一个枚举类型:

    /*              对应公式(其余是固定的):
        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 )
    结果 = 源颜色 + 目标颜色 * (1-源颜色各透明组件的透明度)


以kCGBlendModeNormal为例,在这里,我们填充的是一个颜色,颜色的透明度为1,也就是源颜色透明度为1,所以Sa = 1

  R = S + D*(1 - Sa) --> R = S + D*(1 - 1) --> R = S

这种情况下, kCGBlendModeNormal 和kCGBlendModeCopy类型是一样的效果(使用的就是源颜色填充)

    kCGBlendModeCopy,        R = S

  • 实现歌词变色我们需要使用到的是kCGBlendModeSourceIn:
    kCGBlendModeSourceIn,    R = S*Da -> 结果 = 源颜色*目标透明度
我们这个案例中的源颜色和目标颜色:

    源颜色   -->  就是要绘制上去的颜色/填充色  ([[UIColor greenColor] setFill];)
    目标颜色 -->  Label当前的颜色(文字颜色和透明),上下文中已经有的颜色
D: Label
        默认的文字部分有有颜色     透明度是1  
        其余部分使用的是透明色     透明度是0

S: 填充色(源颜色)
        当前图形上下文中的内容的不透明度

      结合公式: R = S*Da ,当混合填充时

文字部分:  R = S * Da (Da=1) -> R = S -> 显示的就是源颜色(填充色)
其余部分:  R = S * Da (Da=0) -> R = 0 -> 不进行填充/显示目标颜色原有的颜色(透明色)

  • 声明属性,存放当前变色歌词进度,在setter方法中执行重绘
// 更新进度的时候执行重绘
- (void)setProgress:(CGFloat)progress{

    _progress = progress;
    // 执行重绘
    [self setNeedsDisplay];
}
  • 设置歌词变色的进度

lrc格式的歌词文件无法实现根据节奏设置变色进度,这里取平均值:
每一句歌词在每句歌词显示的总时间内,匀速的变色

    
     平均速度进行计算 : (当前播放时间 - 当前句起始时间) / 当前句总时间
     当前句总时间: (下一句的起始时间 - 当前句的起始时间)

因为歌词变色进度也是需要实时更新的,所以也是需要在控制器下的定时器方法内执行的,这里就用到了当当前歌词索引为最后一条时,自定义的一条虚拟歌词对象

    CGFloat averageProgress = ([JSMusciManager sharedMusicManager].currentTime - currentLyric.initialTime) / (nextLyric.initialTime - currentLyric.initialTime);

接下来就是导入自定义Label头文件,身份检测器下绑定,修改Label类型,传递数据,这样就可以实现歌词变色了

歌词处理-歌词变色 - (Obj-C)_第1张图片
歌词变色.png

自定义Label代码:

#import "JSColorLabel.h"

@implementation JSColorLabel


- (void)drawRect:(CGRect)rect {
    // 调用父类方法: 将Label上的文字绘制上
    [super drawRect:rect];
    
    // 设置填充色
    // [[UIColor greenColor] setStroke]; // 描边
    [[UIColor greenColor] setFill]; // 填充
    
    // 设置填充色的区域 (默认文字为白色,填充后为绿色,只需要根据当前歌词显示进度来改变填充的宽度,其他不变)
    rect = CGRectMake(rect.origin.x, rect.origin.y, rect.size.width *self.progress, rect.size.height);
    
    // 渲染
    // 在某个区域中使用混合模式进行填充
    /*
        kCGBlendModeNormal公式: R = S + D*(1 - Sa) --> 结果 = 源颜色 + 目标颜色 * (1-源颜色各透明组件的透明度)
     在这里;
            源颜色  -->  就是要绘制上去的颜色/填充色  ([[UIColor greenColor] setFill];)
            目标颜色 --> Label当前的颜色(白色和透明),上下文中已经有的颜色
     
     */
    UIRectFillUsingBlendMode(rect, kCGBlendModeSourceIn);
    
    /*              对应公式(其余是固定的):
     
        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)
     */
}

// 更新进度的时候执行重绘
- (void)setProgress:(CGFloat)progress{
    
    _progress = progress;
    
    // 执行重绘
    [self setNeedsDisplay];
}

@end

控制器下更新歌词方法中计算平均进度,并给Label的progress属性赋值

// 更新歌词
- (void)updateLyric{
    
    // 当前歌词
    JSLyricModel *currentLyric = self.lyricModelArray[self.currentLyricIndex];
    
    // 下一句歌词  ( 2.判断越界问题)
    JSLyricModel *nextLyric = nil;
    if (self.currentLyricIndex == self.lyricModelArray.count - 1) {
        
        // 创建一个最大的下一句歌词
        nextLyric = [[JSLyricModel alloc]init];
        // 给自定义出来的最后一条歌词设置数据  (设置成最后一条歌词的数据)
        nextLyric.content = currentLyric.content;
        // 因为当前索引已经是最后一条歌词,所以上面的歌词赋值就相当于nextLyric.content = [self.lyricModelArray lastObject].content;
        // 直接设置成歌曲的总时长
        nextLyric.initialTime = [JSMusciManager sharedMusicManager].duration;
        
    }else{
        
        nextLyric = self.lyricModelArray[self.currentLyricIndex + 1];
    }
    
    // 正向调整进度(判断越界问题): 判断时间,改变当前的歌词的索引  : 当前播放时间 > 下一句歌词的起始时间 歌词索引 +1
    if ([JSMusciManager sharedMusicManager].currentTime > nextLyric.initialTime && self.currentLyricIndex < self.lyricModelArray.count - 1) {
        
        self.currentLyricIndex++;
        
        //  拖拽进度条时,只需要显示最近当前歌词,防止拖动歌词逐条跳动
        [self updateLyric];
        // 1. 当累加到正确的当前歌词索引时,下面才给歌词赋值,否则递归调用返回
        return;
        // 如果不进行递归调用直接return: 这里更新数据的定时器间隔时间为0.1s,假如将进度条拖拽到歌词索引60的位置,那么等到定时器自动调用到到歌词索引为60的歌词数据时,需要6s的时间才可以
        
    }
    
    // 反向调整进度(判断越界问题): 当前时间 < 当前句歌词的初始时间 歌词索引-1
    if ([JSMusciManager sharedMusicManager].currentTime < currentLyric.initialTime && self.currentLyricIndex > 0) {
        
        self.currentLyricIndex--;
        [self updateLyric];
        return;
    }
    
    // 设置歌词
    self.verticalLyricLabel.text = self.lyricModelArray[self.currentLyricIndex].content;
    self.horizonLyricLabel.text = self.lyricModelArray[self.currentLyricIndex].content;
    
#pragma mark -- 设置歌词变色
    
    /*          设置歌词变色进度
     
         平均速度进行计算 : (当前播放时间 - 当前句起始时间) / 当前句总时间
            当前句总时间 :   下一句的起始时间 - 当前句的起始时间)
     
     */
    
    CGFloat averageProgress = ([JSMusciManager sharedMusicManager].currentTime - currentLyric.initialTime) / (nextLyric.initialTime - currentLyric.initialTime);
    
    self.horizonLyricLabel.progress = averageProgress;
    self.verticalLyricLabel.progress = averageProgress;
    
}

为了将功能模块独立出来,所以每个小的功能都封装了一个方法
updateLyric(更新歌词方法)会在updateData(更新数据的方法)中调用
updateData是一个定时器计时调用的方法

self.timer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(updateData) userInfo:nil repeats:YES];

你可能感兴趣的:(歌词处理-歌词变色 - (Obj-C))