1. 程式人生 > >iOS 開發仿網易雲音樂歌詞海報

iOS 開發仿網易雲音樂歌詞海報

atomic keyword draw write mode () 解析 efault album

技術分享圖片


使用網易雲音樂也是一個巧合,我之前一直使用QQ音樂聽歌,前幾天下 app 手機內存告急。於是就把QQ音樂給卸載掉了,正好晚上朋友圈裏有一個朋友用網易雲音樂分享了一首歌曲,於是我也就嘗試下載了網易雲音樂,這一下載就讓我從QQ音樂粉轉黑了。

從設計的角度來看,網易雲音樂的界面簡潔,慷慨,不像kugou音樂一打開就是各種廣告。讓人心煩。也不像QQ音樂那樣動不動就各種音質,各種沖鉆(不為用戶需求考慮。僅僅想賺錢,差評)。最關鍵的是它推薦的歌真是好聽,實在是太懂我了,真的是非常用心的在做音樂。

廢話不多說了,今天給大家帶來一篇山寨網易雲音樂歌詞海報生成的文章。自從發現了這個功能,我已經在朋友圈刷屏了。

既然這麽喜歡,那為何不自己來實現一下呢!

首先,有些童鞋可能還不清楚什麽是歌詞海報,我在這裏就先簡單的作一個說明:我們在聽歌的時候難免會有那麽幾句歌詞在腦海中余音繚繞。網易雲音樂就有這麽一個功能,你能夠查看你喜歡的歌詞然後選中它們,然後App會將這些歌詞附加到那些文藝的背景中去生成一張海報,這樣你就能夠將它分享到你的朋友圈裏去,做一個裝逼的文藝青年。

技術分享圖片 技術分享圖片技術分享圖片

設計思路:

  1. 解析歌詞文件,在界面上用UITableView載入
  2. 長按界面。將UITableView切換至可編輯狀態
  3. 將選中的歌詞保存
  4. 依據歌詞的數量在UIImageView上動態創建UILabel
  5. 將UIImageView保存為圖片存至相冊

代碼實現:

眼下代碼解析的歌詞文件都是lrc的格式。比如網易。QQ。他們都有自己的海量的歌詞數據,在網上搜索歌詞文件也能搜索到非常多。比如次樣式的:

[00:01.16] 不為誰而作的歌 - 林俊傑
[00:05.96] 詞:林秋離
[00:08.16] 曲:林俊傑
[00:27.51] 原諒我這一首不為誰而作的歌
[00:34.12] 感覺上仿佛窗外的夜色
[00:41.24] 以前有那一刻
[00:44.18] 回頭居然認不得須要從記憶再摸索的人
[00:54.85] 和他們關心的 的地方
[01:01.50]
和那些走過的請等一等 [01:10.55] 夢為努力澆了水

有了歌詞文件還不行。我們得把歌詞和時間都解析出來,這就要用到我們的歌詞解析功能了,代碼例如以下:

#import <Foundation/Foundation.h>

@interface LrcParseUtil : NSObject
//時間
@property (nonatomic,strong) NSMutableArray *timerArray;
//歌詞
@property (nonatomic,strong) NSMutableArray *wordArray;

-(NSString *)getLrcFile:(NSString *)lrc;

//解析歌詞
-(void) parseLrc:(NSString*)lrc;

@end
#import "LrcParseUtil.h"

@implementation LrcParseUtil
@synthesize timerArray = _timerArray;
@synthesize wordArray = _wordArray;


-(instancetype) init{
    self=[super init];
    if(self!=nil){
        self.timerArray=[[NSMutableArray alloc] init];
        self.wordArray=[[NSMutableArray alloc] init];
    }
    return  self;
}

-(NSString *)getLrcFile:(NSString *)lrc{
    NSString* filePath=[[NSBundle mainBundle] pathForResource:lrc ofType:@"lrc"];
    return  [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
}

-(void)parseLrc:(NSString *)lrc{
    if(![lrc isEqual:nil]){
        NSArray *sepArray=[lrc componentsSeparatedByString:@"["];
        NSArray *lineArray=[[NSArray alloc] init];
        for(int i=0;i<sepArray.count;i++){
            if([sepArray[i] length]>0){
                lineArray=[sepArray[i] componentsSeparatedByString:@"]"];
                if(![lineArray[0] isEqualToString:@"\n"]){
                    [self.timerArray addObject:lineArray[0]];
                    [self.wordArray addObject:lineArray.count>1?lineArray[1]:@""];
                }
            }
        }
    }
}

@end

上面我們僅僅是將歌詞文件轉化為數據存儲到了我們的內存中,接下來要把這些數據顯示給用戶,這裏我們就要用到UITableView這個強大的控件了,至於這個空間的使用我這裏就不在闡述了,代碼例如以下:

@interface ViewController ()

@end

@implementation ViewController
@synthesize lrcTableView = _lrcTableView;
@synthesize parseUtil = _parseUtil;
@synthesize selectLrcView = _selectLrcView;

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    self.title = @"Silicon Demo";
    [self.view setBackgroundColor:[UIColor blackColor]];

    self.lrcTableView = [[UITableView alloc] initWithFrame:self.view.bounds];
    self.lrcTableView.delegate = self;
    self.lrcTableView.dataSource = self;

    [self.lrcTableView setBackgroundColor:[UIColor clearColor]];
    self.lrcTableView.separatorStyle = UITableViewCellSeparatorStyleNone;

    [self.view addSubview:self.lrcTableView];

    //解析歌詞
    self.parseUtil = [[LrcParseUtil alloc] init];
    [self.parseUtil parseLrc:[self.parseUtil getLrcFile:@"demoSong"]];
}


- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}


#pragma -mark tableview
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
    return 40.0f;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
    if(!self.selectLrcView){
        self.selectLrcView = [SelectLrcViewController new];
    }

    [self.selectLrcView setLrcData:self.parseUtil.wordArray];

    CATransition *animation = [CATransition animation];
    [animation setDuration:0.3];
    [animation setType: kCATransitionFade];
    [animation setSubtype: kCATransitionFromTop];
    [animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault]];

    [self.navigationController pushViewController:self.selectLrcView animated:NO];
    [self.navigationController.view.layer addAnimation:animation forKey:nil];
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{

    return [self.parseUtil.wordArray count];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    UITableViewCell *cell = [self.lrcTableView dequeueReusableCellWithIdentifier:@"cell"];
    if(!cell){
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];
    }

    cell.textLabel.text = self.parseUtil.wordArray[indexPath.row];
    cell.textLabel.textColor = [UIColor whiteColor];
    cell.textLabel.textAlignment = NSTextAlignmentCenter;
    cell.textLabel.font = [UIFont systemFontOfSize:15];
    cell.backgroundColor=[UIColor clearColor];


    return cell;
}

@end

效果例如以下:

技術分享圖片


UITableView控件原生自帶了選擇功能,所以我這邊圖省力就先用原生自帶的實現歌詞選擇功能(日後會更新成自己定義的), 代碼例如以下:

#import "SelectLrcViewController.h"

@interface SelectLrcViewController ()

@end

@implementation SelectLrcViewController
@synthesize lrcWordsArray = _lrcWordsArray;
@synthesize wordsTableView = _wordsTableView;
@synthesize selectedLrcs = _selectedLrcs;

- (instancetype)init{
    self = [super init];

    self.lrcWordsArray = [[NSMutableArray alloc] init];
    self.selectedLrcs = [[NSMutableArray alloc] init];

    return self;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"%@", self.navigationController.navigationBar);

    // Do any additional setup after loading the view.
    UIBarButtonItem *tempBarItem = [[UIBarButtonItem alloc] initWithTitle:@"生成圖片" style:UIBarButtonItemStylePlain target:self action:@selector(generatePic)];

    self.navigationItem.rightBarButtonItem = tempBarItem;

    self.wordsTableView = [[UITableView alloc] initWithFrame:self.view.bounds];
    self.wordsTableView.delegate = self;
    self.wordsTableView.dataSource = self;
    [self.wordsTableView setBackgroundColor:[UIColor clearColor]];
    self.wordsTableView.separatorStyle = UITableViewCellSeparatorStyleNone;
    self.wordsTableView.editing = YES;
    self.wordsTableView.allowsMultipleSelectionDuringEditing = YES;

    [self.view addSubview:self.wordsTableView];
}

- (void)viewWillAppear:(BOOL)animated{
    [super viewWillAppear:animated];

    [self.wordsTableView reloadData];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.

}

- (void)setLrcData:(NSMutableArray *)lrcdata{
    self.lrcWordsArray = lrcdata;
}

- (void)generatePic{
    NSArray *selectIndex = [self.wordsTableView indexPathsForSelectedRows];
    if([selectIndex count] == 0 || !selectIndex){
        NSLog(@"請選擇歌詞");

        return;
    }

    [self.selectedLrcs removeAllObjects];

    for (int i = 0; i < [selectIndex count]; i++) {
        NSIndexPath *index = selectIndex[i];
        [self.selectedLrcs addObject:[self.lrcWordsArray objectAtIndex:index.row]];
    }

    if([self.selectedLrcs count] == 0){
        return;
    }

    if(!self.lrcShareView){
        self.lrcShareView = [LrcShareViewController new];
    }

    [self.lrcShareView setLrcContent:self.selectedLrcs];

    CATransition *animation = [CATransition animation];
    [animation setDuration:0.3];
    [animation setType: kCATransitionFade];
    [animation setSubtype: kCATransitionFromTop];
    [animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault]];

    [self.navigationController pushViewController:self.lrcShareView animated:NO];
    [self.navigationController.view.layer addAnimation:animation forKey:nil];
}

#pragma -mark tableview
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
    return 40.0f;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
    NSLog(@"");
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
    return [self.lrcWordsArray count];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    UITableViewCell *cell = [self.wordsTableView dequeueReusableCellWithIdentifier:@"cell"];
    if(!cell){
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];
    }

    cell.textLabel.text = self.lrcWordsArray[indexPath.row];
    cell.textLabel.textColor = [UIColor whiteColor];
    cell.textLabel.textAlignment = NSTextAlignmentCenter;
    cell.textLabel.font = [UIFont systemFontOfSize:15];
    cell.backgroundColor=[UIColor clearColor];

    return cell;
}

效果例如以下:
技術分享圖片


最後一步就是生成歌詞海報了。考慮到圖片資源對App安裝包大小造成的影響,這裏採用了對背景圖片進行拉伸的辦法,在iOS 5.0之前,我們用此API進行對圖片的拉伸:

- (UIImage *)stretchableImageWithLeftCapWidth:(NSInteger)leftCapWidth topCapHeight:(NSInteger)topCapHeight;

往往會將拉伸的大小設置為1像素。然後保證其它的地方不變,這樣縱使我們的控件大小再怎麽改變,圖片也不會出現拉伸的情況。

但這個API在iOS 5之後就被廢棄了,在這裏我們該用它iOS 6以後新出的AP對圖進行拉伸:

- (UIImage *)resizableImageWithCapInsets:(UIEdgeInsets)capInsets resizingMode:(UIImageResizingMode)resizingMode  

有人可能會問。為什麽不用圖形上下文的方式在背景圖片上繪制文字,我之前已嘗試過可是生成海報後的像素實在是令人捉急,於是就把這個思路給pass了;後來經過細致分析,通過在UIImageView中加入subView也就是UILabel,然後通過下面代碼生成的海報達到的效果令人愜意。代碼例如以下:

 UIGraphicsBeginImageContextWithOptions(self.imageView.bounds.size, YES, 0.0);
    [self.imageView.layer renderInContext:UIGraphicsGetCurrentContext()];
    UIImage *bitmap = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

這裏附上,生成海報的所有代碼:

#import "LrcShareViewController.h"
#import "ConstantUtil.h"
@interface LrcShareViewController ()

@end

@implementation LrcShareViewController
@synthesize shareLrcs = _shareLrcs;
@synthesize imageView = _imageView;
@synthesize scrollView = _scrollView;
@synthesize paperLayer = _paperLayer;
@synthesize shareBtn = _shareBtn;
@synthesize saveBtn = _saveBtn;
@synthesize pane = _pane;

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.

    //set scrollView and share & save Btn
    _scrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds];
    _scrollView.backgroundColor = [UIColor clearColor];
    _scrollView.delegate = self;
    _scrollView.contentSize = self.view.bounds.size;
    _scrollView.scrollEnabled = YES;
    [self.view addSubview:_scrollView];

    [self addShareAndSaveBtn];
}

- (void)viewWillAppear:(BOOL)animated{
    // init the imageView
    self.imageView = [UIImageView new];

    // resize the background image , adapte to the lrcs
    self.paperLayer = [self drawLrcsWithImageContext:[UIImage imageNamed:@"simple.png"]];
    [self.imageView setImage:self.paperLayer];

    //add labels on the imageView
    [self addLyricToBackground:self.shareLrcs];
    [self.scrollView addSubview:self.imageView];
}


- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

/*
 *@brief 加入分享與保存button
 */
- (void)addShareAndSaveBtn{
    self.pane = [[UIView alloc] initWithFrame:CGRectMake(0, self.view.bounds.size.height - 35.0f, self.view.bounds.size.width, 35.f)];
    [self.pane setBackgroundColor:[UIColor clearColor]];

    self.shareBtn = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width / 2, 35.0f)];
    [self.shareBtn setBackgroundColor:[UIColor colorWithRed:23.0f green:24.0f blue:24.0f alpha:0]];
    [self.shareBtn setTitle:@"分享" forState:UIControlStateNormal];
    [self.shareBtn setTintColor:[UIColor whiteColor]];
    [self.shareBtn addTarget:self action:@selector(socialCCshare) forControlEvents:UIControlEventTouchDown];

    self.saveBtn = [[UIButton alloc] initWithFrame:CGRectMake(self.view.bounds.size.width / 2 + 1, 0, self.view.bounds.size.width / 2, 35.0f)];
    [self.saveBtn setBackgroundColor:[UIColor colorWithRed:23.0f green:24.0f blue:24.0f alpha:0]];
    [self.saveBtn setTitle:@"保存" forState:UIControlStateNormal];
    [self.saveBtn setTintColor:[UIColor whiteColor]];
    [self.saveBtn addTarget:self action:@selector(saveToPhoto) forControlEvents:UIControlEventTouchDown];

    UIImageView *line = [[UIImageView alloc] initWithFrame:CGRectMake(self.view.bounds.size.width / 2, 0, 1, 35)];
    [line setBackgroundColor:[UIColor whiteColor]];

    [self.pane addSubview:self.shareBtn];
    [self.pane addSubview:self.saveBtn];
    [self.pane addSubview:line];
    [self.view addSubview:self.pane];
}

- (void)socialCCshare{
    NSLog(@"分享");
}

/*
 *@brief 保存至手機相冊
 */
- (void)saveToPhoto{
    UIGraphicsBeginImageContextWithOptions(self.imageView.bounds.size, YES, 0.0);
    [self.imageView.layer renderInContext:UIGraphicsGetCurrentContext()];
    UIImage *bitmap = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    UIImageWriteToSavedPhotosAlbum(bitmap, self, @selector(image:didFinishSavingWithError:contextInfo:), NULL);
}

- (void)image: (UIImage *) image didFinishSavingWithError: (NSError *) error contextInfo: (void *) contextInfo
{
    NSString *msg = nil ;
    if(error != NULL){
        msg = @"保存圖片失敗" ;
    }else{
        msg = @"保存圖片成功" ;
    }

    NSLog(@"%@", msg);
}

- (void)setLrcContent:(NSMutableArray *)selectLrcs{
    self.shareLrcs = selectLrcs;
}

/*
 *@brief 動態加入歌詞到UIImageView
 *@param lrcsArray 歌詞
 */
- (void)addLyricToBackground:(NSMutableArray *)lrcsArray{

    CGFloat point_x = 40.0f;
    CGFloat point_y = 50.0f;
    CGFloat t_per_size = 15.0f;
    CGFloat row_height = 20.0f;
    CGFloat margin = 10.0f;

    for(int i = 0; i < [lrcsArray count]; i++){
        //get lrc from array
        NSString *lrc = [lrcsArray objectAtIndex:i];
        int lrcLen = lrc.length;

        //create a label to show the lrc
        UILabel *lrcLabel = [[UILabel alloc] initWithFrame:CGRectMake(point_x, point_y + i * (row_height + margin), lrcLen * t_per_size, row_height)];

        [lrcLabel setText:lrc];
        lrcLabel.font = [UIFont fontWithName:@"Arial" size:15];
        lrcLabel.textColor = [UIColor lightGrayColor];
        [self.imageView addSubview:lrcLabel];
    }

    //note the song‘s name
    NSString *songName = @"── [不為誰而做的歌]";
    CGFloat y_songName = self.imageView.frame.size.height - 90.0f;
    CGFloat width_songName = self.imageView.frame.size.width - 80.0f;
    UILabel *songFrom = [[UILabel alloc] initWithFrame:CGRectMake((self.view.bounds.size.width - width_songName)/2, y_songName, width_songName, row_height)];
    [songFrom setText:songName];
    songFrom.font = [UIFont fontWithName:@"Arial" size:15];
    songFrom.textColor = [UIColor lightGrayColor];
    [songFrom setTextAlignment:NSTextAlignmentRight];
    [self.imageView addSubview:songFrom];
}


/*
 * @brief 拉伸背景圖片達到滿足背景的要求
 * @param layerImage 背景圖片
 */
- (UIImage *)drawLrcsWithImageContext:(UIImage *)layerImage{
    CGFloat rowHeight = 20.0f;
    CGFloat margins = 5.0f;

    /*
     *背景海報的高度
     *header iphone 固定為80px
     *footer iphone 固定為120px
     */
    CGFloat imageHeight = (rowHeight + margins) * [self.shareLrcs count];
    //背景海報的寬度為屏幕固定寬度
    CGFloat imageWidth = self.view.bounds.size.width;

    [self.imageView setFrame:CGRectMake(0, 0, imageWidth, 200 + imageHeight)];

    CGFloat top = layerImage.size.height /2 - 1;
    CGFloat left = layerImage.size.width /2 - 1;
    CGFloat bottom = layerImage.size.height /2 - 1;
    CGFloat right = layerImage.size.width /2 - 1;

    // 設置端蓋的值
    UIEdgeInsets edgeInsets = UIEdgeInsetsMake(top, left, bottom, right);
    // 設置拉伸的模式
    UIImageResizingMode mode = UIImageResizingModeStretch;
    // 拉伸圖片
    UIImage *newImage = [layerImage resizableImageWithCapInsets:edgeInsets resizingMode:mode];

    return newImage;
}

@end

效果圖例如以下:
技術分享圖片


總結

此功能在界面效果和用戶體驗上離網易還差非常遠,可是主要的核心已經實現。當然實現這樣的效果可能有100種方法。歡迎大家來指正。我也會繼續更新代碼像網易靠攏。

GitHub地址:https://github.com/ShenJieSuzhou/https—github.com-ShenJieSuzhou-LrcShareDemo.git


好了。

祝大家生活愉快。多多收獲友誼和愛情。假設想獲取很多其它的訊息,請掃描下方二維碼關註我的微信公眾號:

技術分享圖片

iOS 開發仿網易雲音樂歌詞海報