美文网首页iOS 控件定制iOS高级开发
歌词处理-歌词变色 - (Obj-C)

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

作者: ShenYj | 来源:发表于2016-07-21 14:20 被阅读793次

    这里自定义了一个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类型,传递数据,这样就可以实现歌词变色了

    歌词变色.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];
    
    

    相关文章

      网友评论

      • b1eacb30758d:楼主方便分享一下Demo吗?
        ShenYj:@媛小白 互相帮助,共同进步:smile:
        b1eacb30758d:@ShenYj 谢谢楼主
        ShenYj:@媛小白 https://github.com/ShenYj/QQPlayer.git

      本文标题:歌词处理-歌词变色 - (Obj-C)

      本文链接:https://www.haomeiwen.com/subject/kcsbjttx.html