歌词处理-歌词变色 - (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类型,传递数据,这样就可以实现歌词变色了

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 11,945评论 4 60
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,056评论 25 707
  • 大学生对于钱,职业,人生和爱情的态度与迷茫,就像你问一个哲学家:“你是谁,你从哪里来,你到哪里去?”是一样的,似乎...
    小啤酒肚阅读 189评论 0 0
  • 其实我知道我的问题,因为一直在逃避去解决问题导致我的焦虑越来越重,我不想在这样痛苦下去了,so,改变从现在开始吧!...
    努力成长的小树阅读 254评论 0 1
  • 这一天下午最后一节课是英语课,也是这个学期最后一节课,英语老师分析期末卷子试题,这个学期就结束了。离高考也...
    有机蜗牛阅读 499评论 0 3