iOS 开发项目之 QQ 音乐
来源:互联网 发布:淘宝立即购买是灰色的 编辑:程序博客网 时间:2024/05/16 01:16
一 框架搭建
- 添加一个毛玻璃效果
- (void)setupBlurGlass{ // 1.创建UIToolbar UIToolbar *toolbar = [[UIToolbar alloc] init]; toolbar.barStyle = UIBarStyleBlack; [self.albumView addSubview:toolbar]; // 2.给UIToolbar添加约束 [toolbar mas_makeConstraints:^(MASConstraintMaker *make) { /* make.width.equalTo(self.albumView.mas_width); make.height.equalTo(self.albumView.mas_height); make.centerX.equalTo(self.albumView.mas_centerX); make.centerY.equalTo(self.albumView.mas_centerY); */ make.edges.equalTo(self.albumView); }];}
- 设置状态栏字体为白色
- (UIStatusBarStyle)preferredStatusBarStyle{ return UIStatusBarStyleLightContent;}
- 歌手图片绘制成圆形
// 颜色抽成一个宏#define GGColor(r,g,b) ([UIColor colorWithRed:r/255.0 green:g/255.0 blue:b/255.0 alpha:1.0])- (void)viewWillLayoutSubviews{ [super viewWillLayoutSubviews]; // 设置歌手图片的圆角 self.iconView.layer.cornerRadius = self.iconView.bounds.size.width * 0.5; self.iconView.layer.masksToBounds = YES; self.iconView.layer.borderWidth = 8; self.iconView.layer.borderColor = GGColor(40, 40, 40).CGColor;}
二 播放音乐
- 抽取工具类MusicTool,管理所有歌曲
static NSArray *_musics;static GGMusic *_playingMusic;+ (void)initialize{ // 使用MJExtension 框架 _musics = [GGMusic objectArrayWithFilename:@"Musics.plist"]; _playingMusic = _musics[0];}+ (NSArray *)musics{ return _musics;}// 获取当前正在播放的歌曲+ (GGMusic *)playingMusic{ return _playingMusic;}// 设置正在播放的歌曲+ (void)setPlayingMusic:(GGMusic *)playingMusic{ _playingMusic = playingMusic;}
- 用工具类播放音乐
#pragma mark - 开始播放歌曲- (void)startPlayingMusic{ // 1.取出当前播放的歌曲 GGMusic *playingMusic = [GGMusicTool playingMusic]; // 2.设置界面的基本展示 self.albumView.image = [UIImage imageNamed:playingMusic.icon]; self.iconView.image = [UIImage imageNamed:playingMusic.icon]; self.songLabel.text = playingMusic.name; self.singerLabel.text = playingMusic.singer; // 3.开始播放歌曲 AVAudioPlayer *player = [GGAudioTool playMusicWithMusicName:playingMusic.filename]; self.currentTimeLabel.text = [NSString stringWithTime:player.currentTime]; // 当前时间 self.totalTimeLabel.text = [NSString stringWithTime:player.duration]; // 总时间 self.player = player; // 4.给iconView添加旋转动画 [self addIconViewAnimation]; // 5.添加定时器 [self startProgressTimer];}
- 新建分类NSString+GGTimeToString,时间转换拼接为字符串
+ (NSString *)stringWithTime:(NSTimeInterval)time{ NSInteger min = time / 60; NSInteger second = (NSInteger)time % 60; return [NSString stringWithFormat:@"%02ld:%02ld", min, second];}
三 旋转动画和进度完善
- 给iconView添加旋转动画
- (void)addIconViewAnimation{ // 1.创建动画(基本动画) CABasicAnimation *rotationAnim = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"]; // 2.设置动画的属性 rotationAnim.fromValue = @(0); rotationAnim.toValue = @(M_PI * 2); rotationAnim.duration = 40.0; rotationAnim.repeatCount = NSIntegerMax; // 3.添加到iconView的layer [self.iconView.layer addAnimation:rotationAnim forKey:nil];}
- 更新进度条,搞一个定时器
#pragma mark - 对定时器的操作- (void)startProgressTimer{ [self updateProgress]; // 主动调用,防止1s间隔 self.progressTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(updateProgress) userInfo:nil repeats:YES]; [[NSRunLoop mainRunLoop] addTimer:self.progressTimer forMode:NSRunLoopCommonModes];}- (void)stopProgressTimer{ [self.progressTimer invalidate]; self.progressTimer = nil;}- (void)updateProgress{ // 改变滑块的位置 self.slider.value = self.player.currentTime / self.player.duration; // 设置当前播放时间的Label的文字 self.currentTimeLabel.text = [NSString stringWithTime:self.player.currentTime];}
四 进度条事件处理
滑动进度条需要以下几个操作步骤:
- 1.移除定时器
- 2.滑动过程中改变当前播放时间
- 3.用户松手时播放当前时间对应音乐
定时器移除代码
- (void)stopProgressTimer{ [self.progressTimer invalidate]; self.progressTimer = nil;}
- 根据滑动位置比例计算出当前时间
#pragma mark 滑块的值改变- (IBAction)sliderValueChange:(UISlider *)sender { // 1.进度条当前进度的比例 CGFloat ratio = sender.value; // 2.根据当前的比例,计算当前的时间 NSTimeInterval currentTime = self.player.duration * ratio; // 3.改变currentTimeLabel的显示的文字 self.currentTimeLabel.text = [NSString stringWithTime:currentTime];}
- 用户松手时播放当前位置对应时间的音乐
- 注意:进度条想要监听点击需要添加一个手势
#pragma mark 用户结束点击- (IBAction)sliderTouchUpInside:(UISlider *)sender { // 1.改变歌曲播放的进度 // 1.1.进度条当前进度的比例 CGFloat ratio = sender.value; // 1.2.根据当前的比例,计算当前的时间 NSTimeInterval currentTime = self.player.duration * ratio; // 1.3.改变歌曲播放的时间 self.player.currentTime = currentTime; // 2.添加定时器 [self startProgressTimer];}
- 进度条的点击
#pragma mark 进度条的点击- (IBAction)sliderClick:(UITapGestureRecognizer *)sender { // 1.获取进度条的比例 // 1.1.获取用户点击的位置 CGPoint point = [sender locationInView:sender.view]; // 1.2.计算比例 CGFloat ratio = point.x / self.slider.bounds.size.width; // 2.计算当前应该播放的时间 NSTimeInterval currentTime = ratio * self.player.duration; // 3.改变歌曲播放的进度 self.player.currentTime = currentTime; // 4.更新进度(定时器会有1s 间隔) [self updateProgress];}
五 对歌曲控制的事件
- 上一首
- 抽取工具类方法
+ (GGMusic *)previousMusic{ // 1.取出当前歌曲的下标值 NSUInteger currentIndex = [_musics indexOfObject:_playingMusic]; // 2.计算上一个歌曲的下表 NSInteger previousIndex = currentIndex - 1; if (previousIndex < 0) { previousIndex = _musics.count - 1; } // 3.取出上一个歌曲 return [_musics objectAtIndex:previousIndex];}
- (IBAction)previousMusic { // 0.取出当前歌曲 GGMusic *playingMusic = [GGGMusicTool playingMusic]; // 1.停止当前歌曲 [GGAudioTool stopMusicWithMusicName:playingMusic.filename]; // 2.取出下一首歌曲,并且播放 GGMusic *previousMusic = [GGMusicTool previousMusic]; [GGAudioTool playMusicWithMusicName:previousMusic.filename]; // 3.设置上一首歌曲成为当前播放的歌曲 [GGMusicTool setPlayingMusic:previousMusic]; // 4.改变界面信息成为上一首歌曲的信息 [self startPlayingMusic];}
- 下一首
+ (GGMusic *)nextMusic{ // 1.取出当前歌曲的下标值 NSUInteger currentIndex = [_musics indexOfObject:_playingMusic]; // 2.计算下一个歌曲的下表 NSInteger nextIndex = currentIndex + 1; if (nextIndex > _musics.count - 1) { nextIndex = 0; } // 3.取出上一个歌曲 return [_musics objectAtIndex:nextIndex];}
- 上一首和下一首代码类似,抽成1个方法
- (void)switchMusicWithNextMusic:(BOOL)isNextMusic{ // 0.取出当前歌曲 GGMusic *playingMusic = [GGMusicTool playingMusic]; // 1.停止当前歌曲 [GGAudioTool stopMusicWithMusicName:playingMusic.filename]; // 2.取出下一首歌曲,并且播放 GGMusic *changeMusic = nil; if (isNextMusic) { changeMusic = [GGMusicTool nextMusic]; } else { changeMusic = [GGMusicTool previousMusic]; } [GGAudioTool playMusicWithMusicName:changeMusic.filename]; // 3.设置上一首歌曲成为当前播放的歌曲 [GGMusicTool setPlayingMusic:changeMusic]; // 4.改变界面信息成为上一首歌曲的信息 [self startPlayingMusic];}
- 暂停/播放
- 默认图片是选中状态
- (IBAction)playOrPauseMusic:(UIButton *)sender { // 1.改变按钮的状态 sender.selected = !sender.isSelected; // 2.根据歌曲是否在播放,来决定暂停还是播放 if (self.player.isPlaying) { [self.player pause]; // 暂停动画 [self.iconView.layer pauseAnimate]; // 停止进度定时器 [self stopProgressTimer]; } else { [self.player play]; // 继续动画 [self.iconView.layer resumeAnimate]; // 添加进度定时器 [self startProgressTimer]; }}
- 暂停/继续核心动画根控制器没什么关系,抽取分类CALayer+GGAnimate
- (void)pauseAnimate{ CFTimeInterval pausedTime = [self convertTime:CACurrentMediaTime() fromLayer:nil]; self.speed = 0.0; self.timeOffset = pausedTime;}
- (void)resumeAnimate{ CFTimeInterval pausedTime = [self timeOffset]; self.speed = 1.0; self.timeOffset = 0.0; self.beginTime = 0.0; CFTimeInterval timeSincePause = [self convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime; self.beginTime = timeSincePause;}
六 添加歌词的 View
- 歌词的 View 为一个UIScrollView
- 根据偏移量不同,变化 view 的透明度
- 遵守UIScrollViewDelegate
- 根据偏移量不同,变化 view 的透明度
#pragma mark - 实现LrcView的代理方法- (void)scrollViewDidScroll:(UIScrollView *)scrollView{ // 1.计算scrollView偏移量 CGFloat offsetRatio = scrollView.contentOffset.x / scrollView.bounds.size.width; // 2.设置歌词的Label和iconView的透明度 self.iconView.alpha = 1 - offsetRatio; self.lrcLabel.alpha = 1 - offsetRatio;}
歌词展示用 tableview 实现GGLrcView
- 初始化出来就是一个tableview
- (instancetype)initWithCoder:(NSCoder *)aDecoder{if (self = [super initWithCoder:aDecoder]) { [self setupTableView];}return self;}
- 添加 tableview
- (void)setupTableView{// 1.创建tableViewUITableView *tableView = [[UITableView alloc] init];self.tableView = tableView;// 2.添加到歌词的View中[self addSubview:tableView];// 3.设置tableView的属性self.tableView.dataSource = self;self.tableView.rowHeight = 35;}
UIScrollView中使用 Autolayout
- 注意点:添加约束,必须要多添加2个约束(距离右边和下边)
- (void)layoutSubviews{ [super layoutSubviews]; // 给tableView添加约束 [self.tableView mas_makeConstraints:^(MASConstraintMaker *make) { make.top.equalTo(self.mas_top); make.left.equalTo(self.mas_left).offset(self.bounds.size.width); make.height.equalTo(self.mas_height); make.width.equalTo(self.mas_width); make.bottom.equalTo(self.mas_bottom); make.right.equalTo(self.mas_right); }]; // 清除tableView的背景颜色和边线 self.tableView.backgroundColor = [UIColor clearColor]; self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone; // 设置tableView的上下的内边距 self.tableView.contentInset = UIEdgeInsetsMake(self.bounds.size.height * 0.5, 0, self.bounds.size.height * 0.5, 0);}
- 实现tableview 的数据源方法
#pragma mark - 实现tableView的数据源方法- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{ return 20;}- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{ static NSString *ID = @"LrcCell"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID]; if (cell == nil) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:ID]; // 设置cell的背景 cell.backgroundColor = [UIColor clearColor]; cell.selectionStyle = UITableViewCellSelectionStyleNone; // 设置label的属性 cell.textLabel.textColor = [UIColor whiteColor]; cell.textLabel.font = [UIFont systemFontOfSize:14.0]; cell.textLabel.textAlignment = NSTextAlignmentCenter; } cell.textLabel.text = @"测试数据123"; return cell;}
七 歌词的解析
- 字典转模型(一句歌词转成模型)GGLrcLine
/** 显示的歌词文字 */@property (nonatomic, copy) NSString *text;/** 时间 */@property (nonatomic, assign) NSTimeInterval time;
- 模型对外提供下面方法
- (instancetype)initWithLrcString:(NSString *)lrcString{ if (self = [super init]) { // [01:20.74]想这样没担忧 唱着歌 一直走 NSArray *lrclineArray = [lrcString componentsSeparatedByString:@"]"]; self.text = lrclineArray[1]; self.time = [self timeWithString:[lrclineArray[0] substringFromIndex:1]]; } return self;}
- 时间需要解析成秒,抽取为方法
- (NSTimeInterval)timeWithString:(NSString *)timeString{ // 01:20.74 NSArray *timeArray = [timeString componentsSeparatedByString:@":"]; NSInteger min = [timeArray[0] integerValue]; NSInteger second = [[timeArray[1] componentsSeparatedByString:@"."][0] integerValue]; NSInteger haomiao = [[timeArray[1] componentsSeparatedByString:@"."][1] integerValue]; return min * 60 + second + haomiao * 0.01;}
+ (instancetype)lrcLineWithLrcString:(NSString *)lrcString{ return [[self alloc] initWithLrcString:lrcString];}
- 解析歌词抽取工具类
+ (NSArray *)lrcToolWithLrcName:(NSString *)lrcName{ // 1.获取歌词的路径 NSString *filePath = [[NSBundle mainBundle] pathForResource:lrcName ofType:nil]; // 2.读取该文件中的歌词 NSString *lrcString = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil]; // 3.获取歌词的数组 NSArray *lrcArray = [lrcString componentsSeparatedByString:@"\n"]; // 4.将一句歌词转成模型对象,放入到一个数组中 NSMutableArray *tempArray = [NSMutableArray array]; for (NSString *lrclineString in lrcArray) { // 4.1.过滤不需要的歌词的行 if ([lrclineString hasPrefix:@"[ti:"] || [lrclineString hasPrefix:@"[ar:"] || [lrclineString hasPrefix:@"[al:"] || ![lrclineString hasPrefix:@"["]) { continue; } // 4.2.解析每一句歌词转成模型对象 GGLrcLine *lrcLine = [GGLrcLine lrcLineWithLrcString:lrclineString]; [tempArray addObject:lrcLine]; } return tempArray;}
- 重写 set 方法
#pragma mark - 重写setLrcName的方法- (void)setLrcName:(NSString *)lrcName{ _lrcName = lrcName; // 解析歌词 self.lrcLines = [GGLrcTool lrcToolWithLrcName:lrcName]; // 刷新列表 [self.tableView reloadData]; // 让tableView滚动到最上面 [self.tableView setContentOffset:CGPointMake(0, - self.tableView.bounds.size.height * 0.5) animated:YES];}
八 将当前播放时间实时传递给LrcView
- 建一个歌词定时器
/** 歌词的定时器 */@property (nonatomic, strong) CADisplayLink *lrcTimer;
- 对歌词定时器的操作
#pragma mark 歌词的定时器- (void)startLrcTimer{ self.lrcTimer = [CADisplayLink displayLinkWithTarget:self selector:@selector(updateLrcInfo)]; [self.lrcTimer addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];}- (void)stopLrcTimer{ [self.lrcTimer invalidate]; self.lrcTimer = nil;}- (void)updateLrcInfo{ self.lrcView.currentTime = self.player.currentTime;}
- 拿到实时时间
- 根据当前播放时间, 让歌词滚到正确的位置
#pragma mark - 重写setCurrentTime方法- (void)setCurrentTime:(NSTimeInterval)currentTime{ _currentTime = currentTime; // 找出需要显示的歌词 NSInteger count = self.lrcLines.count; for (int i = 0; i < count; i++) { // 1.拿到i位置的歌词 GGLrcLine *currentLrcLine = self.lrcLines[i]; // 2.拿出i+1位置的歌词 NSInteger nextIndex = i + 1; if (nextIndex >= count) return; GGLrcLine *nextLrcLine = self.lrcLines[nextIndex]; // 3.当前时间大于i位置歌词的时间并且小于i+1位置的歌词的时间 if (currentTime >= currentLrcLine.time && currentTime < nextLrcLine.time && self.currentIndex != i) { /* [01:03.45]你是我的小呀小苹果儿 63.45 63.46 64.43 i 12 [01:07.06]就像天边最美的云朵 67.06 13 */ // 计算i位置的IndexPath NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0]; NSIndexPath *previousPath = [NSIndexPath indexPathForRow:self.currentIndex inSection:0]; // 记录i位置的下标 self.currentIndex = i; // 刷新i位置的cell(播放时字体变大) [self.tableView reloadRowsAtIndexPaths:@[indexPath, previousPath] withRowAnimation:UITableViewRowAnimationNone]; // 让tableView的i位置的cell,滚动到中间位置 [self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionTop animated:YES]; // 设置外面的歌词 self.lrcLabel.text = currentLrcLine.text; } // 4.当正在播放某一个歌词(self.currentIndex == i) if (self.currentIndex == i) { // 4.1.计算当前已经播放的比例 CGFloat progress = (currentTime - currentLrcLine.time) / (nextLrcLine.time - currentLrcLine.time); // 4.2.告知当前的歌词的Label进度 NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0]; GGLrcCell *cell = [self.tableView cellForRowAtIndexPath:indexPath]; cell.lrcLabel.progress = progress; // 4.3.改变外面的歌词Label的进度 self.lrcLabel.progress = progress; } }}
- 刷新数据,播放时字体变大
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{ // 1.创建cell GGLrcCell *cell = [GGLrcCell lrcCellWithTableView:tableView]; if (indexPath.row == self.currentIndex) { cell.lrcLabel.font = [UIFont systemFontOfSize:18.0]; } else { cell.lrcLabel.font = [UIFont systemFontOfSize:14.0]; cell.lrcLabel.progress = 0; } // 2.给cell设置数据 GGLrcLine *lrcline = self.lrcLines[indexPath.row]; cell.lrcLabel.text = lrcline.text; return cell;}
自定义 Label,实现 Label 按播放进度渐变
- 重绘一下 Label
- (void)drawRect:(CGRect)rect{[super drawRect:rect];CGRect drawRect = CGRectMake(0, 0, rect.size.width * self.progress, rect.size.height);[[UIColor greenColor] set];// UIRectFill(drawRect);// R = S * Da S是现在正在画的内容透明为1,Da 之前 label 内容的透明度UIRectFillUsingBlendMode(drawRect, kCGBlendModeSourceIn); // 文字渐变}
- 实时调用
- (void)setProgress:(CGFloat)progress{_progress = progress;[self setNeedsDisplay];}
将Label 添加到 cell 中GGLrcCell
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{ if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) { // 1.创建自定义的Label GGLrcLabel *lrcLabel = [[GGLrcLabel alloc] init]; // 2.添加cell中 [self.contentView addSubview:lrcLabel]; // 3.给自定义的label添加约束 [lrcLabel mas_makeConstraints:^(MASConstraintMaker *make) { make.center.equalTo(self.contentView); }]; // 4.设置label的属性 lrcLabel.textColor = [UIColor whiteColor]; lrcLabel.font = [UIFont systemFontOfSize:14.0]; // 5.设置cell的属性 self.backgroundColor = [UIColor clearColor]; self.selectionStyle = UITableViewCellSelectionStyleNone; // 6.让成员属性的lrcLabel指向对象 self.lrcLabel = lrcLabel; } return self;}
- 播放页面显示歌词
// 歌词的Label@property (weak, nonatomic) IBOutlet GGLrcLabel *lrcLabel;// 5.将外面显示歌词的Label的对象,赋值给lrcView的引用 self.lrcView.lrcLabel = self.lrcLabel;
注意:切换歌曲,刷新 i 位置的 cell 时会报错(上一首歌词量与下一首歌词量不同)
// 计算i位置的IndexPath NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0]; NSArray *indexPaths = nil; if (self.currentIndex >= count) { indexPaths = @[indexPath]; } else { NSIndexPath *previousPath = [NSIndexPath indexPathForRow:self.currentIndex inSection:0]; indexPaths = @[indexPath, previousPath]; }
九 音乐后台播放
1.开启后台模式
2.设置音频会话模式
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // 1.获取音频会话 AVAudioSession *session = [AVAudioSession sharedInstance]; // 2.设置音频会话的类型 [session setCategory:AVAudioSessionCategoryPlayback error:nil]; // 3.激活音频会话(静音状态依然可以播放) [session setActive:YES error:nil]; return YES;}
- 设置锁屏界面
#pragma mark - 设置锁屏界面的信息- (void)setupLockScreenInfo{ // 1.拿到当前播放的歌曲 GGMusic *playingMusic = [GGMusicTool playingMusic]; // 2.设置锁屏界面的内容 // 2.1.获取锁屏界面中心 MPNowPlayingInfoCenter *infoCenter = [MPNowPlayingInfoCenter defaultCenter]; // 2.2.设置显示的信息 NSMutableDictionary *dict = [NSMutableDictionary dictionary]; // 2.2.1.设置歌曲名称 [dict setValue:playingMusic.name forKey:MPMediaItemPropertyAlbumTitle]; // 2.2.2.设置歌手名称 [dict setValue:playingMusic.singer forKey:MPMediaItemPropertyArtist]; // 2.2.3.设置专辑封面 UIImage *image = [UIImage imageNamed:playingMusic.icon]; MPMediaItemArtwork *artwork = [[MPMediaItemArtwork alloc] initWithImage:image]; [dict setValue:artwork forKey:MPMediaItemPropertyArtwork]; // 2.2.4.设置歌曲的总时长 [dict setValue:@(self.player.duration) forKey:MPMediaItemPropertyPlaybackDuration]; infoCenter.nowPlayingInfo = dict; // 2.3.让应用程序可以接受远程事件 [[UIApplication sharedApplication] beginReceivingRemoteControlEvents];}
- 处理远程事件
- (void)remoteControlReceivedWithEvent:(UIEvent *)event{ switch (event.subtype) { case UIEventSubtypeRemoteControlPlay: case UIEventSubtypeRemoteControlPause: [self playOrPauseMusic:self.playOrPauseBtn]; break; case UIEventSubtypeRemoteControlNextTrack: [self nextMusic]; break; case UIEventSubtypeRemoteControlPreviousTrack: [self previousMusic]; break; default: break; }}
0 0
- iOS 开发项目之 QQ 音乐
- 【iOS开发】仿QQ音乐头像旋转
- iOS开发UI基础—10常用UI控件综合示例之QQ音乐
- ios开发---音乐播放器之怎么获取音乐列表
- iOS开发之网络音乐播放器(SC音乐)(一)
- ios音乐播放器-仿QQ音乐
- IOS开发之选择系统音乐
- 猫猫学iOS之ipad开发qq空间项目横竖屏幕适配
- iOS之ipad开发qq空间项目横竖屏幕适配
- AJ学IOS 之ipad开发qq空间项目横竖屏幕适配
- iOS开发之模仿qq通讯录
- iOS开发之模仿qq通讯录源代码!
- iOS开发之多媒体篇-仿QQ音乐播放器思路
- iOS 之音乐播放
- iOS 开发 初级:Audio音频之播放iPod Library音乐
- iOS开发之音乐播放----监听系统音量变化
- iOS开发之音乐播放----监听耳机拔插
- iOS开发之音乐播放器专辑图片旋转动画
- DOM操作-range
- Java时间戳转化为今天、昨天、明天(字符串格式)
- 下拉刷新--第三方开源--PullToRefresh
- OC_08_01 KVC
- Oracle体系结构(二)---日志处理机制
- iOS 开发项目之 QQ 音乐
- Yocto tips (19): Yocto SDK Toolchian的使用
- Convolutional Neural Networks Posts
- webBrowser中操作网页元素全攻略
- redis安装
- 让ImageView可以使用gif的方法
- Android组件的概念
- 推荐系统公共资源汇总
- JVM监测