项目地址github:https://github.com/Eastwu5788/XMLYFM 如果您觉得不错,记得给一个star
高仿喜马拉雅FM(第二弹) 本篇文章有点长了,更多的内容在第二弹继续更新

最新用空闲时间写了一下喜马拉雅FM这款APP。

干货效果演示

多音频下载、本地播放功能演示

LocalDownload.gif

网络音频播放效果演示

PlayDetail.gif

效果演示

推荐页面效果

recom.gif

分类页面效果

cate.gif

广播页面效果

radio.gif

榜单页面效果

Rank.gif

主播页面效果

Anchor.gif

订阅听页面效果

Dingyue.gif

下载听页面效果

xiazai.gif

我的页面效果

Mine.gif

分析

  • 发现tab中有五个小分类,分别对应五个页面,所有在“发现”的控制器中使用了UIPageViewController来控制五个子控制器。
    +从Charles抓出来的接口来看,“推荐”页面一共调用了三个接口,分别请求了推荐、热门、直播的内容,所以在这里选择了Reactivecocoa来实现接口的并发访问
- (void)refreshDataSource {       @weakify(self);RACSignal *signalRecommend = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {@strongify(self);[self requestRecommendList:^{[subscriber sendNext:nil];}];return nil;}];    RACSignal *signalHotAndGuess = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {@strongify(self);[self requestHotAndGuessList:^{[subscriber sendNext:nil];}];return nil;}];RACSignal *signalLiving = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {@strongify(self);[self requestLiving:^{[subscriber sendNext:nil];}];return nil;}];[[RACSignal combineLatest:@[signalRecommend,signalHotAndGuess,signalLiving]] subscribeNext:^(id x) {@strongify(self);[(RACSubject *)self.updateContentSignal sendNext:nil];}];
}
  • 在“推荐”页面中有几个轮播图,仔细观察会发现它的轮播图一直想左转换,所以这里的轮播图片需要做一下特殊处理。以实现无限轮播的效果
- (void)setModel:(XMLYFindFocusImagesModel *)model  {      _model = model;    [self.adverScrollView removeAllSubViews];    self.adverScrollView.contentSize = CGSizeMake(kScreenWidth * _model.list.count, 150);     //1.向scrollView中增加UIImageView的时候,需要在最后一张图片后面将第一张图片添加上去    for(NSInteger index = 0; index <= _model.list.count; index++)   {      //2.如果是最后一张图片,则放置第一张图片XMLYFindFocusImageDetailModel \*detail = index == _model.list.count ? _model.list.firstObject : [_model.list objectAtIndex:index];UIImageView \*imageView = [[UIImageView alloc] init];imageView.frame = CGRectMake(kScreenWidth \* index, 0, kScreenWidth, 150);[imageView yy_setImageWithURL:[NSURL URLWithString:detail.pic] options:YYWebImageOptionSetImageWithFadeAnimation];[self.adverScrollView addSubview:imageView];}
}
  • 在轮播图滚动动画结束后需要做一下判断,如果当前滚动到了最后一张图片,则立即将scrollView的偏移调整到初始位置,这样一个无限轮播就完成了。
- (void)scrollViewDidScroll:(UIScrollView \*)scrollView {   NSInteger curPage = self.adverScrollView.contentOffset.x / kScreenWidth;    if(curPage == self.model.list.count) {    [self.adverScrollView setContentOffset:CGPointMake(0, 0) animated:NO];    }
}
  • 在有轮播图的地方肯定少不了定时器,如果将定时器直接放在cell中,就会因为cell的复用导致定时器出现问题,所有一般是将定时器放在控制器中。但是这样的话也带来一个问题,就是由于定时器的存在,如果要求定时器的生命周期和控制器相同(也就是在控制器dealloc的时候才取消定时器).这样的控制器是无法调用dealloc的,会造成控制器虽然已经退出但是定时器依然在正常工作。所以这里专门为控制器设计了一个定时器的单例帮助类,这样的话就可以在dealloc中去销毁所有的定时器。
@interface XMLYFindRecommendHelper : NSObject
#pragma mark - Common
//生成帮助类单例
+ (instancetype)helper;    //销毁所有的定时器
- (void)destoryAllTimer;#pragma mark - Live    //  开启为直播设置的定时器
- (void)startLiveTimer;    //销毁直播的定时器
- (void)destoryLiveTimer;    #pragma mark - Header    //开启头部的定时器
- (void)startHeadTimer;    //销毁头部的定时器
- (void)destoryHeaderTimer;
@end
  • 在广播页面中,有一个根据当前时间显示不同的问候语的小功能。比如现在是早上6点钟,应该显示“早安*北京”。这里就需要用到NSDateFormatter,但是NSDateFormatter的比较消耗性能,所以我专门写了一个XMLYTimeHelper类来管理所有的时间转换操作。在这个类中对NSDateFormatter做了缓存处理,并使用dispatch_semaphore_t保证了线程安全。
//根据字符串生成相应的NSDateFormatter,比如"yyyy-MM-dd HH:mm:ss"
static force_inline NSDateFormatter *XMLYDataCreateFormatter(NSString *string) {NSDateFormatter *formatter = [[NSDateFormatter alloc] init];formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];formatter.dateFormat = string;return formatter;
}//用户直接调用此方法,传入"yyyy-MM-dd HH:mm:ss"这样的字符串生成NSDateFormatter
static force_inline NSDateFormatter *XMLYDateFormatter(NSString *string) {//1.检查输入的合法性if(!string || ![string isKindOfClass:[NSString class]] || string.length == 0) return nil;//2.初始化单例参数static CFMutableDictionaryRef cache;static dispatch_semaphore_t lock;static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{cache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);lock = dispatch_semaphore_create(1);});//3.加锁dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);//4.查询当前字符串是否已经存在相应的NSDateformatterNSDateFormatter *formatter = CFDictionaryGetValue(cache, (__bridge const void *)(string));//5.解锁dispatch_semaphore_signal(lock);//6.如果缓存中没有,则需要重新生成if(!formatter) {formatter = XMLYDataCreateFormatter(string);//7.重新生成成功,存入缓存if(formatter) {dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);CFDictionarySetValue(cache, (__bridge const void *)(string), (__bridge const void *)(formatter));dispatch_semaphore_signal(lock);}}return formatter;
}
  • 2016.09.09这次主要是完成了榜单页面和主播页面,榜单页面没有什么特别的东西,主播页面主要使用UICollectionView实现三个cell等分整个屏幕,以实现每个section里面的自动布局。如果想实现无边界的布局,需要重写一下系统的UICollectionViewFlowLayout布局类,否则总是会有一小块边界被显示出来
- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect {//解决issueNSArray* attributes = [[NSArray alloc] initWithArray:[super layoutAttributesForElementsInRect:rect] copyItems:YES];for(NSInteger i = 1,max = attributes.count; i < max; i++) {UICollectionViewLayoutAttributes *currentLayoutAttributes = attributes[i];UICollectionViewLayoutAttributes *prevLayoutAttributes = attributes[i - 1];NSInteger maximumSpacing = 0;NSInteger origin = CGRectGetMaxX(prevLayoutAttributes.frame);if(origin + maximumSpacing + currentLayoutAttributes.frame.size.width < self.collectionViewContentSize.width) {CGRect frame = currentLayoutAttributes.frame;frame.origin.x = origin + maximumSpacing;currentLayoutAttributes.frame = frame;}}return attributes;
}

注意,在获取父类的layoutAttributes数组的时候一定要选择copy,否则会报一个issue

2016-09-09 10:20:10.687 XMLYFM[1453:240776] Logging only once for UICollectionViewFlowLayout cache mismatched frame
2016-09-09 10:20:10.688 XMLYFM[1453:240776] UICollectionViewFlowLayout has cached frame mismatch for index path <NSIndexPath: 0xc000000000200116> {length = 2, path = 1 - 1} - cached value: {{106, 415}, {106.66666666666667, 162.53968253968256}}; expected value: {{106.5, 415}, {106.66666666666667, 162.53968253968256}}
2016-09-09 10:20:10.688 XMLYFM[1453:240776] This is likely occurring because the flow layout subclass XMLYAnchorFlowLayout is modifying attributes returned by UICollectionViewFlowLayout without copying them
  • 在我的github上有我一年多以前写的一个通过重写UICollectionViewLayout实现瀑布流的小demo,大家有兴趣可以去看看

  • 在“下载听”页面中有一个显示当前已占用空间和可用空间的功能,关于计算当前可用空间我这里有一个函数,可以直接拿去用。不谢

    static int64_t XMLYDiskSpaceFree() {NSError *error = nil;NSDictionary *attrs = [[NSFileManager defaultManager] attributesOfFileSystemForPath:NSHomeDirectory() error:&error];if (error) return -1;int64_t space =  [[attrs objectForKey:NSFileSystemFreeSize] longLongValue];if (space < 0) space = -1;return space;
    }
  • 在“我的”页面中有一个下拉放大的效果,网上有很多实现的方法,我这里用了一个很简单的方法,在UITableView上增加一个子视图,作为真正的头部视图,遮盖掉tableHeaderView,在ScrollView滚动的时候,改变子视图的大小就行了。

1.创建子视图

- (XMLYMineHeaderView *)headerView {if(!_headerView) {//真正的头部视图_headerView = [[XMLYMineHeaderView alloc] initWithFrame:CGRectMake(0, 0, kScreenWidth, 288)];//设置tableHeaderView的大小与头视图相同self.tableView.tableHeaderView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, kScreenWidth, 288)];//将头部视图作为UITableView子视图,遮盖掉原来的tableHeaderView[self.tableView addSubview:_headerView];}return _headerView;
}

2.TableView滚动时调整headerView的大小

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {CGFloat offsetY = scrollView.contentOffset.y;if (offsetY <= 0) {self.headerView.frame = CGRectMake(offsetY / 2.0, offsetY, kScreenWidth - offsetY, 288 - offsetY);}
}

3.将headerView的frame计算放在layoutSubViews方法中,这样每一次改变headerView的frame,此方法都会走一遍,从而随之调整子视图的位置。但是千万不要把复杂位置计算放在里面

- (void)layoutSubviews { // height 288[super layoutSubviews];CGFloat hspace = (self.frame.size.width - kScreenWidth) / 2.0f;CGFloat centx = self.frame.size.width / 2.0f;//背景视图self.backImageView.frame = CGRectMake(hspace, 0, kScreenWidth, self.frame.size.height);self.alphaView.frame = CGRectMake(hspace, 0, kScreenWidth, self.frame.size.height);//节目管理self.managerButton.frame = CGRectMake(centx - 10 - 104.0f, self.frame.size.height - 36.0 - 37.0f, 104.0f, 37.0f);//录音按钮self.recordButton.frame = CGRectMake(centx + 10, self.managerButton.frame.origin.y, 104.0f, 37.0f);//子标题 self.subTitleLabel.frame = CGRectMake(centx - 150.0f, self.recordButton.frame.origin.y - 24.0f - 15.0f, 300, 15);//点击登录按钮self.userNameButton.frame = CGRectMake(centx - 100.0f, self.subTitleLabel.frame.origin.y - 10 - 18.0, 200.0f, 18.0f);self.avatarImageView.frame = CGRectMake(centx - 45.0, self.userNameButton.frame.origin.y - 10 - 90.0, 90, 90);//设置按钮self.settingButton.frame = CGRectMake(12 + hspace, self.avatarImageView.frame.origin.y - 20, 20, 20);}

欢迎大家关注我的微信公众号

qrcode_for_gh_c3cd7518dd64_1280.jpg

高仿喜马拉雅FM(第一弹)相关推荐

  1. Swift 3 0 高仿喜马拉雅FM

    高仿喜马拉雅FM gitHub: https://github.com/LinXunFeng/LXFFM 说明 基于Swift 3.0 编写而成,运行环境要求: Xcode 8.0 LXFFM 原OC ...

  2. android仿喜马拉雅APP状态栏,源码分享:高仿喜马拉雅FM

    最新用空闲时间高仿了一下喜马拉雅FM这款APP,目前主要完成了发现栏目中的推荐页面. 效果演示 分析 +发现tab中有五个小分类,分别对应五个页面,所有在"发现"的控制器中使用了U ...

  3. 类似于喜马拉雅的php源码,源码分享:高仿喜马拉雅FM

    最新用空闲时间高仿了一下喜马拉雅FM这款APP,目前主要完成了发现栏目中的推荐页面. 效果演示 分析 +发现tab中有五个小分类,分别对应五个页面,所有在"发现"的控制器中使用了U ...

  4. 高仿喜马拉雅听Android客户端,Zhumulangma

    ### 注意:请将lib_common下manifest中喜马拉雅key替换为自己的,不然会提示访问超过限制. https://github.com/TanZhiL/Zhumulangma 本项目仅 ...

  5. 高仿163网站广告弹出层(每天定时24小时弹出一次)

    高仿163网站广告弹出层(每天定时24小时弹出一次) 这里和京东首页定时弹出广告功能一样:用JS实现网站首页弹出广告:超级炫酷的定时弹出图片广告:淘宝网站广告弹出层实现. 高仿163网站广告弹出层(每 ...

  6. 工作篇 之 高仿微信双击消息弹出可自由复制

    LZ-Says:书山有路勤为径,学海无涯苦作舟. 前言 最近呐,难已琢磨. 很喜欢,却又很忧愁. 喜欢的是,找到了自己认可的.喜欢的工作: 忧愁的是,压力山大. I Love-! 举个栗子 Enmmm ...

  7. CoordinatorLayout+ViewPager+Behavior仿喜马拉雅FM首页

    主要节点 效果 实现方案 Behavior设置在哪里? ViewPager样式修改 渐变效果处理 一张图的渐变方案(自定义View绘制)例如 两张图片的渐变方案,适合图片,预缓存效果更好 三张图片的方 ...

  8. Swift仿写喜马拉雅FM

    前言: 最近抽空面了几家公司,大部分都是从基础开始慢慢深入项目和原理.面试内容还是以OC为主,但是多数也都会问一下Swift技术情况,也有例外全程问Swift的公司(做区块链项目),感觉现在虽然大多数 ...

  9. (android高仿系列)今日头条 --新闻阅读器 (二)

    高仿今日头条 --- 第一篇:(android高仿系列)今日头条 --新闻阅读器 (一) 上次,已经完成了头部新闻分类栏目的拖动效果. 这篇文章是继续去完善APP 今日头条  这个新闻阅读器的其他功能 ...

最新文章

  1. 【C++】模板函数的声明和定义必须在同一个文件中
  2. 简历受HR欢迎的四大特点
  3. python开发安卓程序-如何使用python开发android应用
  4. X皮书之shell 常用代码
  5. ajax为什么有时候不行,为什么不能用ajax调用
  6. Python数值计算:一 使用Pylab绘图(1)
  7. 智能指针可以放到容器中么_Rust语言入门教程 智能指针篇
  8. Nginx编译-安装-配置-优化实践总结
  9. 【Android综合应用】01 SmartRefreshLayout
  10. HTML5_2(视频)
  11. [C++] 导入FLTK几乎所有头文件
  12. 前端使用cesium加载地球
  13. 小科普:浏览器的神玩法,将开启万能版浏览器?
  14. 笔迹宽度估计的低质量文本图像二值化(Robust Document Image Binarization Technique for Degraded Document Images)
  15. 【软件测试】什么样的项目适合做自动化测试?自动化测试有需要那些技术?
  16. 7.6-7.13牟尼沟、若尔盖旅游日记
  17. 能ping通但是不能打开网页问题的解决方法
  18. word文档多级列表设置
  19. 服务过美国总统竞选的非传统投票UI [解析及DEMO]
  20. 方差、协方差、四分位差笔记

热门文章

  1. 爱情36技之一劳永逸
  2. 【easyui】解决easyui闪屏问题
  3. 给小朋友讲什么是计算机ppt,计算机是什么给小学生讲计算机知识ppt.ppt
  4. html位置插入透明动画文字,视频加移动水印 视频添加图片加文字水印 设置透明漂浮移动并控制显示时间...
  5. 达观RPA网银自动对账机器人,财务一键对账So Easy!
  6. echarts柱状图 小方格的格式 (象形柱状图绘制)
  7. centos7的virbr0问题
  8. Python 爬取新浪财经部分股票的历史交易数据
  9. 三星Note系列刷入Kali Nethunter
  10. FFmpeg命令行实现两路/多路视频拼接 合并 合成 同时播放