美文网首页H5音乐播放
歌词处理-滚动歌词视图 - (Obj-C)

歌词处理-滚动歌词视图 - (Obj-C)

作者: ShenYj | 来源:发表于2016-07-21 18:36 被阅读182次

    模仿QQ音乐播放器歌词视图,默认进入视图:

    歌词视图_1.png

    当手指从右向左滑动时,出现一个滚动歌词视图

    配图######

    接下来就来模拟普通视图和滚动歌词视图切换

    • 视图层级结构分析:
    滚动歌词层级结构.png ScrollView视图结构.png
    1. 创建一个透明的UIView,覆盖掉中间的CenterView
    2. 在这个View中,先添加一个水平方向滚动的ScrollView(命名为HorizontalScrollView)
      HorizontalScrollView  ContentSize =  2 * ScreenSize
    
    1. 在HorizontalScrollView中继续添加一个垂直方向滚动的ScrollView(命名为VerticalScrollView)
     VerticalScrollView  ContentSize =  ScreenSize
    
    1. 在VerticalScrollView中添加多个Label,每个Label用来显示一行歌词
    2. 判断Label,设置当前歌词所在的Label frame,实现放大效果
    • 搭建UI 关键代码
    // 设置歌词视图
    - (void)setupLyricView{
        
        // 添加控件
        [self addSubview:self.horizontalScrollView];
        // 设置约束
        [self.horizontalScrollView mas_makeConstraints:^(MASConstraintMaker *make) {
            // 占满视图
            make.edges.mas_equalTo(self);
        }];
        // 设置水平滚动ScrollView的ContentSize (垂直方向不希望滚动,所以设置为0)
        self.horizontalScrollView.contentSize = CGSizeMake(SCREEN_SIZE.width * 2, 0);
        
        // 水平滚动ScrollView添加垂直滚动的ScrollView
        [self.horizontalScrollView addSubview:self.verticalScrollView];
        // 设置垂直滚动ScrollView的约束
        [self.verticalScrollView mas_makeConstraints:^(MASConstraintMaker *make) {
            make.top.mas_equalTo(self);
            make.left.mas_equalTo(self.horizontalScrollView).mas_offset(SCREEN_SIZE.width);
            make.size.mas_equalTo(self);
            
        }];
        self.horizontalScrollView.backgroundColor = [UIColor greenColor];
        self.verticalScrollView.backgroundColor = [UIColor orangeColor];
    }
    

    这样ScrollView的基本视图就添加完了,暂时先只设置了水平方向的ScrollView的ContentSize,垂直滚动需要根据歌词Label来计算:

    歌词视图_2.png

    细节方面:设置中心的View背景色为透明,关闭滚动指示条,开启分页效果(水平)

    歌词视图_3.png
    • horizontalScrollView滚动时,实现渐隐效果

    根据偏移量,设置控制器下中心View视图透明度,因为在自定义View中,需要修改的View视图在控制器内,这里使用了Block,也可以使用代理

    先声明一个属性

    @property (nonatomic,copy) void(^scrollBlock)(CGFloat offSetPercent);
    

    设置水平ScrollView代理,实现代理方法,调用Block

    #pragma mark -- UIScrollViewDelegate
    
    // 滚动水平方向的ScrollView时,根据滚动设置控制器下中心View视图的透明度(实现渐隐效果)
    - (void)scrollViewDidScroll:(UIScrollView *)scrollView{
        if (scrollView == self.horizontalScrollView) {
    
            self.scrollBlock(1-scrollView.contentOffset.x/SCREEN_SIZE.width);
        }
    }
    
    

    如果是代理,在控制器下设置代理对象实现方法即可,这里我使用的是Block,通过回调的数据,设置中心视图的渐隐效果

    // 设置中心视图的渐隐效果
     __weak typeof(self) weakSelf = self;
    [self.centerLyricView setScrollBlock:^(CGFloat percentAlpa) {
            
        NSLog(@"%f",percentAlpa);
        weakSelf.verticalCenterView.alpha = percentAlpa;
    }];
    

    这样水平滚动就处理完了,接下来是VerticalScrollView部分的处理

    • 声明一个属性,用来存放每首歌曲的全部歌词
    // 当前歌曲的歌词模型数组
    @property (nonatomic,strong) NSArray *lyricModelArray;
    
    • 重写属性的setter方法

    在里面根据每首歌曲的歌词数量创建显示歌词的Label
    通过数组长度确定了Label个数,通过Label个数决定了VerticalScrollView的ContentSize

    #pragma mark -- 重写setter方法
    - (void)setLyricModelArray:(NSArray *)lyricModelArray{
        
        _lyricModelArray = lyricModelArray;
        
        // 存放歌词的Label
        for (int i = 0; i < lyricModelArray.count; i ++) {
            // 创建歌词模型
            JSLyricModel *model = lyricModelArray[i];
            
            // 创建Label
            UILabel *lyricLabel = [[UILabel alloc]init];
            lyricLabel.textColor = [UIColor whiteColor];
            [self.verticalScrollView addSubview:lyricLabel];
            // 设置约束
            [lyricLabel mas_makeConstraints:^(MASConstraintMaker *make) {
                make.centerX.mas_equalTo(self.verticalScrollView);
                make.height.mas_equalTo(LyricLabelHeight);
                // 索引 * 高度
                make.top.mas_equalTo(LyricLabelHeight*i);
                
            }];
            
            // 给Label设置数据
            lyricLabel.text = model.content;
        }
        
        // 设置垂直滚动ScrollView的ContentSize
        self.verticalScrollView.contentSize = CGSizeMake(0, LyricLabelHeight * lyricModelArray.count);
        
        
    }
    
    • 给滚动歌词视图分发数据

    因为不需要实时传递,只需要在控制器下,获取到每一首解析后的歌词数据时,一次传递即可
    设置数据的方法,在每次切换歌曲时都会被调用,所以在设置数据方法中为滚动歌曲视图传递数据

        // 给垂滚动视图传递歌词数据
        self.centerLyricView.lyricModelArray = self.lyricModelArray;
    

    这样基本视图搭建完成:

    歌词视图_4.png
    • 细节处理 设置内边距&偏移

    给VerticalScrollView设置内边距和偏移量,让歌词划出时,第一句歌词默认居中显示
    需要注意的是要在layoutSubviews来设置,这里才能拿到当前view的真实Frame,如果在属性的setter方法中,拿到的不是有效数据,默认按照4s的屏幕尺寸计算(最小屏幕计算)

    - (void)layoutSubviews{
        [super layoutSubviews];
        // 设置外边距
        self.verticalScrollView.contentInset = UIEdgeInsetsMake((self.bounds.size.height-LyricLabelHeight) * 0.5, 0, 0, 0);
        self.verticalScrollView.contentOffset = CGPointMake(0, -(self.bounds.size.height-LyricLabelHeight) * 0.5);
    }
    
    • 歌词跟随滚动,当前歌词字体放大

    因为控制器下已经计算过当前歌词索引,所以直接声明属性传递即可

    // 当前歌词索引
    @property (nonatomic,assign) NSInteger currentLyricIndex;
    

    控制器下传递数据(在更新歌词方法中获取到索引):

    self.centerLyricView.currentLyricIndex = self.currentLyricIndex;
    

    重写歌词索引的setter方法:

    1.根据索引设置滚动效果

    这里需要使用到LayoutSubViews中设置VerticalScrollView的内边距/偏移量,所以抽取一个宏

    #define VERTICAL_SCROLLVIEW_OFFSET ((self.bounds.size.height-LyricLabelHeight) * 0.5)
    
    // 设置滚动 (根据索引设置偏移量实现滚动: 偏移量 = 索引 * Label高度 )
    self.verticalScrollView.contentOffset = CGPointMake(0, currentLyricIndex * LyricLabelHeight);
    

    关键点:
    需要注意切换歌曲时,通过VerticalScrollView的subViews可以获取到歌词Label,清除之前的Label子视图,否则歌词的Label会叠加显示(歌词模型数组setter方法中):

    // 每次切歌先移除子视图  makeObjectsPerformSelector让所有对象都会去执行某一个方法
    [self.verticalScrollView.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
    

    2.当前歌词字号放大

    设置当前Label的font大于正常的Label

    currentLabel.font = [UIFont systemFontOfSize:21]; // 放大字体
    

    当切换下一句歌词时,恢复上一句歌词的Label

    // 将之前索引对应的歌词字体大小恢复
    UILabel *previousLyricLabel = self.verticalScrollView.subviews[_currentLyricIndex];
    previousLyricLabel.font = [UIFont systemFontOfSize:17];// 字体默认大小17
    

    关键点:
    假如上一句歌词索引是0,拖拽到索引20时,这时上一句歌词的索引并不是当前索引-1,所以直接利用了带下划线的属性,来获取上一句歌词对应的Label

    /*
        在_currentLyricIndex = currentLyricIndex;
        赋值前  _currentLyricIndex --> 上一句歌词的索引
    */
    _currentLyricIndex = currentLyricIndex;
    

    3.当前歌词变色

    上一篇文章中实现歌词变色自定义了一个Label,只需要将创建的Label改为自定义Label类型,当前View下,已经有了当前歌词索引,当前歌词数组,在上一篇设置歌词变色中已经在控制器下计算了变色的进度,所以只需要一个变色的进度就可以了

    声明属性:

    // 当前歌词的进度
    @property (nonatomic,assign) CGFloat currentLyricProgress;
    

    控制器下传递进度数据

    self.centerLyricView.currentLyricProgress = averageProgress;
    

    重写currentLyricProgress 属性setter方法中,获取到当前歌词Label,给自定义Label的进度属性赋值

    // 当前歌词进度setter方法
    - (void)setCurrentLyricProgress:(CGFloat)currentLyricProgress{
        
        _currentLyricProgress = currentLyricProgress;
        
        // 设置当前Label进度
        JSColorLabel *currentLabel = self.verticalScrollView.subviews[self.currentLyricIndex];
        currentLabel.progress = currentLyricProgress;
        
    }
    
    

    与设置Label字号一样,还需要恢复上一句歌词的颜色,在重写当前索引属性方法中通过_currentLyricIndex拿到上一句歌词

    // 恢复上一句歌词的颜色
    JSColorLabel *previousLabel = self.verticalScrollView.subviews[_currentLyricIndex];
    previousLabel.progress = 0;
    
    歌词视图_5.png

    最后,防止切歌的时候索引越界,在重写索引属性的setter方法中,恢复上一句歌词状态时,需要进行判断

        // 切歌索引处理,防止索引越界
        if (currentLyricIndex != 0) { // 索引=0 代表在切歌
            
            // 将之前索引对应的歌词字体大小和颜色恢复
            JSColorLabel *previousLyricLabel = self.verticalScrollView.subviews[_currentLyricIndex];
            previousLyricLabel.progress = 0;                        // 恢复上一句歌词的颜色
            previousLyricLabel.font = [UIFont systemFontOfSize:17]; // 恢复上一句歌词的字体默认大小
        }
    

    到此,滚动歌词视图设置完毕

    完整代码:
    .h

    #import <UIKit/UIKit.h>
    
    @interface JSCenterLyricView : UIView
    // 滚动时偏移量占屏幕的比例
    @property (nonatomic,copy) void(^scrollBlock)(CGFloat offSetPercent);
    // 当前歌曲的歌词模型数组
    @property (nonatomic,strong) NSArray *lyricModelArray;
    // 当前歌词索引
    @property (nonatomic,assign) NSInteger currentLyricIndex;
    // 当前歌词的进度
    @property (nonatomic,assign) CGFloat currentLyricProgress;
    
    @end
    

    .m

    #import "JSCenterLyricView.h"
    #import "JSLyricModel.h"
    #import "JSColorLabel.h"
    #import "Masonry.h"
    
    #define SCREEN_SIZE ([UIScreen mainScreen].bounds.size)
    #define VERTICAL_SCROLLVIEW_OFFSET ((self.bounds.size.height-LyricLabelHeight) * 0.5)
    
    // 静态全局变量 存放Label的高度 宏处于预编译阶段,会延长编译时间
    static CGFloat const LyricLabelHeight = 40;
    
    @interface JSCenterLyricView () <UIScrollViewDelegate>
    
    // 水平滚动ScrollView
    @property (nonatomic,strong) UIScrollView *horizontalScrollView;
    // 垂直滚动ScrollView
    @property (nonatomic,strong) UIScrollView *verticalScrollView;
    
    
    @end
    
    @implementation JSCenterLyricView
    
    
    /*      
         initWithCoder : 从文件创建时调用,相当于初始化
          awakeFromNib也可以,相当于ViewDidLoad,initWithCoder调用顺序先于awakeFromNib
     */
    - (instancetype)initWithCoder:(NSCoder *)coder
    {
        self = [super initWithCoder:coder];
        if (self) {
            
            [self setupLyricView];
        }
        return self;
    }
    
    // 设置歌词视图
    - (void)setupLyricView{
        
        // 添加控件
        [self addSubview:self.horizontalScrollView];
        // 设置约束
        [self.horizontalScrollView mas_makeConstraints:^(MASConstraintMaker *make) {
            // 占满视图
            make.edges.mas_equalTo(self);
        }];
        // 设置水平滚动ScrollView的ContentSize (垂直方向不希望滚动,所以设置为0)
        self.horizontalScrollView.contentSize = CGSizeMake(SCREEN_SIZE.width * 2, 0);
        
        // 水平滚动ScrollView添加垂直滚动的ScrollView
        [self.horizontalScrollView addSubview:self.verticalScrollView];
        // 设置垂直滚动ScrollView的约束
        [self.verticalScrollView mas_makeConstraints:^(MASConstraintMaker *make) {
            make.top.mas_equalTo(self);
            make.left.mas_equalTo(self.horizontalScrollView).mas_offset(SCREEN_SIZE.width);
            make.size.mas_equalTo(self.horizontalScrollView);
            
        }];
        
        
        // 关闭滚动指示条 (水平滚动开启分页)
        self.horizontalScrollView.pagingEnabled = YES;
        self.horizontalScrollView.bounces = NO;
        self.horizontalScrollView.showsVerticalScrollIndicator = NO;
        self.horizontalScrollView.showsHorizontalScrollIndicator = NO;
        self.verticalScrollView.showsHorizontalScrollIndicator = NO;
        self.verticalScrollView.showsVerticalScrollIndicator = NO;
        
    }
    
    #pragma mark -- 重写setter方法
    // 歌词模型数组setter方法
    - (void)setLyricModelArray:(NSArray *)lyricModelArray{
        
        // 每次切歌先移除子视图  makeObjectsPerformSelector让所有对象都会去执行某一个方法
        [self.verticalScrollView.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
        
        _lyricModelArray = lyricModelArray;
        
        // 存放歌词的Label
        for (int i = 0; i < lyricModelArray.count; i ++) {
            // 创建歌词模型
            JSLyricModel *model = lyricModelArray[i];
            
            // 创建Label
            JSColorLabel *lyricLabel = [[JSColorLabel alloc]init];
            lyricLabel.textColor = [UIColor whiteColor];
            [self.verticalScrollView addSubview:lyricLabel];
            // 设置约束
            [lyricLabel mas_makeConstraints:^(MASConstraintMaker *make) {
                make.centerX.mas_equalTo(self.verticalScrollView);
                make.height.mas_equalTo(LyricLabelHeight);
                // 索引 * 高度
                make.top.mas_equalTo(LyricLabelHeight*i);
                
            }];
            
            // 给Label设置数据
            lyricLabel.text = model.content;
        }
        
        // 设置垂直滚动ScrollView的ContentSize
        self.verticalScrollView.contentSize = CGSizeMake(0, LyricLabelHeight * lyricModelArray.count);
        
        
    }
    
    // 歌词索引setter方法
    - (void)setCurrentLyricIndex:(NSInteger)currentLyricIndex{
        
        // 切歌索引处理,防止索引越界
        if (currentLyricIndex != 0) { // 索引=0 代表切换歌曲
            
            // 将之前索引对应的歌词字体大小和颜色恢复
            JSColorLabel *previousLyricLabel = self.verticalScrollView.subviews[_currentLyricIndex];
            previousLyricLabel.progress = 0;                        // 恢复上一句歌词的颜色
            previousLyricLabel.font = [UIFont systemFontOfSize:17]; // 恢复上一句歌词的字体默认大小
        }
    
        /*
            在_currentLyricIndex = currentLyricIndex;
            赋值前  _currentLyricIndex --> 上一句歌词的索引
         */
        _currentLyricIndex = currentLyricIndex;
        
        
        // 设置滚动 (根据索引设置偏移量实现滚动: 偏移量 = 默认偏移量 + 索引 * Label高度 )
        [self.verticalScrollView setContentOffset:CGPointMake(0, -VERTICAL_SCROLLVIEW_OFFSET + currentLyricIndex * LyricLabelHeight) animated:YES];
        
        // 设置当前Label字号放大  (根据索引取出Label)
        JSColorLabel *currentLabel = self.verticalScrollView.subviews[currentLyricIndex];
        // 设置当前Label字体大小
        currentLabel.font = [UIFont systemFontOfSize:21]; // 放大字体
        
    }
    
    
    // 当前歌词进度setter方法
    - (void)setCurrentLyricProgress:(CGFloat)currentLyricProgress{
      
        _currentLyricProgress = currentLyricProgress;
        
        // 设置当前Label进度
        JSColorLabel *currentLabel = self.verticalScrollView.subviews[self.currentLyricIndex];
        currentLabel.progress = currentLyricProgress;
        
    }
    
    
    
    - (void)layoutSubviews{
        [super layoutSubviews];
        
        // 设置内边距
        self.verticalScrollView.contentInset = UIEdgeInsetsMake(VERTICAL_SCROLLVIEW_OFFSET, 0, VERTICAL_SCROLLVIEW_OFFSET, 0);
        // 设置默认的偏移量
        self.verticalScrollView.contentOffset = CGPointMake(0, -VERTICAL_SCROLLVIEW_OFFSET);
    }
    
    #pragma mark -- 懒加载
    
    - (UIScrollView *)horizontalScrollView{
        
        if (_horizontalScrollView == nil) {
            _horizontalScrollView = [[UIScrollView alloc]init];
            _horizontalScrollView.delegate = self;
        }
        return _horizontalScrollView;
    }
    - (UIScrollView *)verticalScrollView{
        
        if (_verticalScrollView == nil) {
            _verticalScrollView = [[UIScrollView alloc]init];
        }
        return _verticalScrollView;
    }
    
    #pragma mark -- UIScrollViewDelegate
    
    // 滚动水平方向的ScrollView时,根据滚动设置控制器下中心View视图的透明度(实现渐隐效果)
    - (void)scrollViewDidScroll:(UIScrollView *)scrollView{
        if (scrollView == self.horizontalScrollView) {
    
            self.scrollBlock(1-scrollView.contentOffset.x/SCREEN_SIZE.width);
        }
    }
    
    
    
    @end
    

    相关文章

      网友评论

      本文标题:歌词处理-滚动歌词视图 - (Obj-C)

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