一 部分功能图(后面会完善)

二 讲解思路

1 项目目录结构搭建

2 抽取工具类

3 自定义cell

4 分层思想

5 业务逻辑

三 项目目录搭建和相关配置

1 采用搭建搭建结构思路 : MCV模式

—-> 1.1 文件夹图片 :

2 注意 一 : 当我们在创建目录的时候,直接将info.plist文件拖入到System的时候,编译的时候,会报错.原因是找不到info.plist文件.

—-> 2.1 处理方式 : 直接找到工程文件,然后找到General,里面会有一个现实info的按钮,点击即可解决.

3 注意二 : 修改好了注意一的点,然后编译的时候会出现警告,警告提示info.plist文件已经加载过了,这是找到对应的info.plist文件,按照下面配置就可解决.

4 设置应用图标和应用启动图片我这里就不再讲了,前面博客中都有说明

5 分析采用纯代码搭建还是采用storyboard搭建项目结构?

—-> 5.1 通过app的部分效果图可以知道,采用storyboard是最简单的选择(UINavigationController;UITableViewController;UIViewController)

6 由于需要点击某行的cell然后跳转页面,这里我们直接绑定一个segue

四 创建模型

1 设置展示在tableView中数据的模型

2 根据提供数据的plist文件,设置需要写在模型中的属性

—-> 2.1 经验 : 按照下面的方式操作可以提高开发效率(一些开发细节)
—-> 2.2 图一 :通过下面的方式打开plist文件

—-> 2.3 图二 : 赋值打开后的plist文件中一对字典数据

—-> 2.4 直接在模型的文件中通过替换的方式直接对2.3的数据操作,可以很快的设置需要的属性.

3 模型属性代码

    /** 歌曲名称 */var name: String?/** 歌曲文件名称 */var filename: String?/** 歌曲文件名称 */var lrcname: String?/** 演唱者 */var singer: String?/** 歌手头像 */var singerIcon: String?/** 专辑图片 */var icon: String?

4 用KVC的方式来实现转模型

//重写了下面的方法,此方法是必须重写的override init() {super.init()}/** KVC */init(dict : [String : AnyObject]) {super.init()setValuesForKeysWithDictionary(dict)}

5 容错处理

—-> 5.1 原因 : 如果不重写下面的代码,当字典中的key和value有对不上号的情况下,会直接报错,所以这里我们做出容错处理.
//异常处理override func setValue(value: AnyObject?, forUndefinedKey key: String) {}

五 封装工具类

1 封装原因 : 提供接口,代码复用

2 返回模式 : 采用闭包回调的方式将数据回调出去

3 最终目的 : 此方法的设计,只需要在外界调用的时候,负责提供音乐中的数据,具体怎么提供,就是工具类中的问题了.

4 采用闭包回调的好处 : 如果以后想讲请求的方式更换为网络请求,那么如果采用闭包回调的方式就不需要修改接口,直接将接口内部的实现修改成网络请求就可以达到目的.(这里是直接加载本地的数据)

5 工具类文件夹

6 代码部分 :

—-> 6.1 判断
—-> 6.2 遍历
class XFJQQMusicDataTool: NSObject {//** 设计一个方法,用来返回数据模型 */class func getMusicList( result: (musicMs : [XFJQQMusicModel]) ->()) {//加载本地的plist文件guard let path = NSBundle.mainBundle().pathForResource("Musics.plist", ofType: nil) else {result(musicMs: [XFJQQMusicModel]())return}//加载文件的内容guard let dictArray = NSArray(contentsOfFile: path) else {result(musicMs: [XFJQQMusicModel]())return}//把字典转成模型var resultMs = [XFJQQMusicModel]()//遍历for dict in dictArray {let musicM = XFJQQMusicModel(dict: dict as! [String : AnyObject])resultMs.append(musicM)}//以闭包的形式返回出去result(musicMs: resultMs)}
}

六 UITableViewController的相关设置

1 展示数据的tableView文件(注意和storyboard中的控制器绑定)

2 设置tableView的背景图片

3 设置状态栏的样式

4 设置导航条隐藏

5 上面都是对tableView的处理方式,我们直接采用类扩展,然后将三个方法放在一个供外界提供的方法中,让外界通过直接调用一个方法,就能对三个要求的设置

6 代码 :

extension XFJQQListTVC {//初始化设置func setUpInit() {setUpTableView()setUpNavigationBar()}//通过一个方法来设置tableView的相关设置private  func setUpTableView() {//tableView的背景图片let backView = UIImageView(image: UIImage(named: "QQListBack"))tableView.backgroundView = backView//cell的高度tableView.rowHeight = 60//分割线tableView.separatorStyle = .None}//设置导航条隐藏private  func setUpNavigationBar() {navigationController?.navigationBarHidden = true}//设置状态栏的样式override func preferredStatusBarStyle() -> UIStatusBarStyle {return .LightContent}
}

7 在viewDidLoad中直接调用setUpInit()方法和对工具类提供的方法调用

override func viewDidLoad() {super.viewDidLoad()XFJQQMusicDataTool.getMusicList { (musicMs) -> () in//将返回的数组装入模型中self.musicMs = musicMs}        setUpInit()}

8 在展示内容的tableView中提供一个展示数据的模型的set方法

—-> 8.1 目的 : 当数据只要发生改变,就刷新tableView
//只要有数据变化,就刷新表格var musicMs : [XFJQQMusicModel] = [XFJQQMusicModel]() {didSet {tableView.reloadData()}}

七 自定义cell

1 自定义原因 : 系统的cell无法满足需求

2 采用方式 : xib(由于每行cell内容的样式都一样)

3 创建继承与UITableViewCell的文件,同时创建xib

4 xib的相关设置

—-> 4.1 我这里只说明绑定cell的ID,以便重复利用(其它的不说了)

5 通过拖线的方式,拿到xib中的属性

    //** 歌手的头像  */@IBOutlet weak var iconImageView: UIImageView!//** 歌名 */@IBOutlet weak var songName: UILabel!//** 演唱者 */@IBOutlet weak var singerName: UILabel!

6 重写模型的set方法,以便外界通过set方法来赋值(这里我直接给上代码,没什么好说明的,注意容错处理就行)

    //重写模型的set方法,用模型给xib的属性赋值var musicM : XFJQQMusicModel? {didSet {//容错处理if musicM?.icon != nil {iconImageView.image = UIImage(named:musicM!.icon!)}songName.text = musicM?.namesingerName.text = musicM?.singer}}

7 有app的效果图我们需要对明星的头像处理

    //当加载xib的时候一定会调用override func awakeFromNib() {super.awakeFromNib()//处理头像iconImageView.layer.cornerRadius = iconImageView.width * 0.5iconImageView.layer.masksToBounds = true}

8 在cell的内部提供一个方法,让外界直接调用就可以拿到cell,不需要将该方法写在创建cell的数据源方法中,只要负责提供一个接口就行,至于怎么实现的,就交给cell内部,外界不需要管(自定义cell的核心部分,需要大家学习学习,并且掌握).

    //提供一个方法,返回由xib创建的cellclass  func cellWithTableView(tableView : UITableView) ->XFJQQMusicListCell {let cellID = "cellID"var cell = tableView.dequeueReusableCellWithIdentifier(cellID) as? XFJQQMusicListCellif cell == nil {//检查是否有循环利用print("创建cell")cell = NSBundle.mainBundle().loadNibNamed("XFJQQMusicListCell", owner: nil, options: nil).first as? XFJQQMusicListCell}return cell!}

9 取消点击cell的高亮状态(也是属于cell的,直接写在cell的内部就可以)

    //重写cell的hightlight方法,一遍点击cell的时候cell不在高亮override func setHighlighted(highlighted: Bool, animated: Bool) {}

10 cell的动画效果(核心动画)

—-> 10.1 该部分也是属于cell内部的事情,直接将代码写在自定义cell内部就可以
—-> 10.2 怎么样构建接口?通过由外界传入的枚举值,用switch来作为判断以哪种模式来执行cell的动画
—-> 10.3 在cell的内部定义枚举值
//动画的样式
enum AnimationType {case Rotationcase Translation
}
—-> 10.4 cell的动画
///MARK : - 动画(核心动画)
extension XFJQQMusicListCell {func beginAnimation(type : AnimationType) {//用switch做判断(执行哪种动画)switch type {case .Rotation://移除上一个动画self.layer.removeAnimationForKey("rotation")//做怎样的动画let animation = CAKeyframeAnimation(keyPath: "transform.rotation.z")//动画之间的角度animation.values = [M_PI * 0.25, 0]//动画时长animation.duration = 0.2//添加动画self.layer.addAnimation(animation, forKey: "rotation")case .Translation:self.layer.removeAnimationForKey("translation")let animation = CAKeyframeAnimation(keyPath: "transform.translation.x")animation.values = [60, 0]animation.duration = 0.3self.layer.addAnimation(animation, forKey: "translation")}}
}

11 数据源方法

—-> 11.1 tableView的行数(由模型的个数决定)
    //** 行 */override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {return musicMs.count}
—-> 11.2 每个cell显示的内容(获取自定义cell,内容有模型决定)
    //** cell显示的内容 */override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {//通过方法获得celllet cell = XFJQQMusicListCell.cellWithTableView(tableView)//取出模型let musicM = musicMs[indexPath.row]//赋值(cell.musicM: 模型的set方法,通过该方法来赋值)cell.musicM = musicM//返回cellreturn cell}
—-> 11.3 开始做动画(调用cell中动画的API,注意将cell的类型转为自定义的类型)
    //开始做动画override func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {//将系统的cell转为xib的celllet cell = cell as! XFJQQMusicListCell//开始动画cell.beginAnimation(AnimationType.Translation)}

八 封装对音乐操作的工具类(分三层调用的思想)–>第一层

1 封装工具类的原因 : 对外界提供一个接口,在内部只负责对音乐的播放,暂停,快进和快退等功能.外面不需要关心内部怎么实现的,值负责调用接口就可以

2 创建工具类文件

3 播放音乐文件

—-> 3.1 设置接口 : 需要让外界传入一个音乐文件名,然后在方法内做出一系列的判断,才开始播放音乐文件
class XFJQQMusicTool: NSObject {//创建播放器var avplayer : AVAudioPlayer?//设置一个方法,又外界直接传入一个文件名,然后就直接播放func getplayMusic(name : String) ->(){//获取传入的文件url(并且判断)guard  let url = NSBundle.mainBundle().URLForResource(name, withExtension: nil) else {return}//由url来判断播放的是否是同一首歌if url == avplayer?.url {//播放的是同一首歌avplayer?.play()return}//播放传入的url的歌曲do {avplayer = try AVAudioPlayer(contentsOfURL: url)}catch{//打印错误信息print(error)return}//准备播放avplayer?.prepareToPlay()//开始播放avplayer?.play()}

4 提供两个接口作为控制播放器中间按钮控制暂停和重新播放的状态(内部直接控制,外界只需要调用接口)

    //** 重新播放 */func resumeCurrentMusic() ->() {avplayer?.play()}//** 暂停播放 */func pauseCurrentMusic() ->() {avplayer?.pause()}

九 音乐的业务逻辑工具类的封装(分层思想第二层)

1 封装原因 : 通过该曾来调用最内部的第一层来实现相应的业务逻辑

2 封装第二层工具类的文件

3 设置单例 : 保证每次创建的都是同一个对象

    //设置单例static let shareInstance = XFJQQMusicOperationTool()

4 用设置属性记录当前播放音乐的角标的方式来实现点击上一首和下一首音乐的播放(提供一个set方法)

//设置一个属性记录正在播放的音乐的角标(并且提供set方法)var index = 0 {didSet {if index < 0{index = (musicList?.count ?? 0) - 1}if index > (musicList?.count ?? 0) - 1{index = 0}}}

5 上一首歌曲(内部直接调用播放音乐的方法)–>因为每次点击上一首歌曲的时候,都是从头开始播放的

    //** 上一首 */func preMusic() ->() {index -= 1//判断if let tempList = musicList {//取出模型let musicM = tempList[index]//根据模型播放音乐getplayMusic(musicM)}}

6 下一首歌曲播放(内部直接调用播放音乐的方法)–> 因为每次点击下一首歌曲的时候,都是从头开始播放的

    //** 下一首 */func nextMusic() ->() {index += 1//判断if let tempList = musicList {//取出模型let musicM = tempList[index]//根据模型播放音乐getplayMusic(musicM)}}

7 播放音乐(直接在该类中创建一个方法来调用第一层中播放音乐的方法.注意需要闯入音乐的文件名)

    //创建模型的音乐播放列表var musicList : [XFJQQMusicModel]?//创建操作音乐的对象let tool = XFJQQMusicTool()//提供一个方法由外界通过这个方法播放音乐func getplayMusic(musicM : XFJQQMusicModel) ->() {//通过传入的模型,拿到模型中歌名属性(filename)let fileName = musicM.filename ?? ""//完成对歌曲的播放tool.getplayMusic(fileName)//容错处理(如果没有歌曲,就直接退出)if musicList == nil {return}//记录下当前在播放的音乐的索引index = (musicList?.indexOf(musicM))!}

8 播放和暂停(通过调用第一层中的方法来实现)

    //** 播放 */func playCurrentMusic() ->() {tool.resumeCurrentMusic()}//** 暂停 */func pauseCurrentMusic() ->() {tool.pauseCurrentMusic()}

9 在点击cell的数据源方法中,直接调用第二层中的接口,就能达到播放音乐的目的(第三层调用)

    //用户点击了某行cell,播放对应的歌曲override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {//取出行模型let musicM = musicMs[indexPath.row]//播放XFJQQMusicOperationTool.shareInstance.getplayMusic(musicM)//通过绑定的suge,当点击某行cell的时候,跳转到下一个界面performSegueWithIdentifier("listToDetail", sender: nil)}

十 展示歌词

1 分析 : 通过app的功能可以看出来,拖动显示歌词的view可以往左滑动,那么显示歌词的view可以暂时用普通的view来做,装view的父控件就是UIScrollerView,因为需要滚动的.

2 创建显示暂停,开始,上一首和下一首的类(详情页)

3 布局详情页(这里就不细说了,但是要注意毛玻璃的设置可以通过代码,也可以通过直接在storyboard中拖一个UIToolBar控件来设置)

4 通过从storyboard中的拖线拿到相应的控件

—-> 4.1 下面这个类只负责属性(这样分层也是一种方便管理的思想)
///MARK : - 只负责属性
class XFJQQDetailVC: UIViewController {//** 歌手头像 */@IBOutlet weak var foreImageView: UIImageView!//** 歌词能显示的并且滚动的View */@IBOutlet weak var lrcBackView: UIScrollView!//** 进度 */@IBOutlet weak var progressSlider: UISlider!//** 歌名 */@IBOutlet weak var lrcLabel: UILabel!//** 展示歌词的view */var lrcview : UIView?
}

5 拖过拖线处理暂停,开始,上一首和下一首的业务逻辑

—-> 5.1 扩充一个类扩展,将方法写在里面(注意里面的方法是通过拿到单例来调用的)
    //** 下一首音乐 */@IBAction func nextButton(){XFJQQMusicOperationTool.shareInstance.nextMusic()}//** 上一首音乐 */@IBAction func previous(){XFJQQMusicOperationTool.shareInstance.preMusic()}//** 播放和暂停 */@IBAction func playAndPause(button : UIButton){button.selected = !button.selected//判断if button.selected {XFJQQMusicOperationTool.shareInstance.pauseCurrentMusic()}else {XFJQQMusicOperationTool.shareInstance.playCurrentMusic()}}

6 在上面的类扩展中我们通过设置两个方法来调用下面部分的代码

    //** 设置方法用来添加调用一次性的设置 */private func setUpOnce() ->() {addLrcView()setProgressSlider()}//** 设置方法用来添加调用多次的设置 */private func setUpLrcFrame() ->() {setUpLrcViewFrame()setUpForeImage()}

7 设置进度条的背景图片(只需要设置一次)

    //** 设置进度条的背景图片 */private func setProgressSlider() ->() {progressSlider.setThumbImage(UIImage(named: "player_slider_playback_thumb"), forState: UIControlState.Normal)}

8 计算显示歌词的view的frame(执行多次)

    //** 计算显示歌词的view的frame */private func setUpLrcViewFrame() {//lrcview的frame值lrcview?.frame = lrcBackView.bounds//lrcview的x值lrcview?.x = lrcBackView.width//设置拖动显示歌词的view的contensizelrcBackView.contentSize = CGSizeMake(lrcBackView.width * 2.0, 0)}

9 负责添加显示歌词的控件view(只需要添加一次即可)

    //** 负责添加控件(只需要添加一次即可) */private func addLrcView() ->() {//创建显示歌词的viewlrcview = UIView()//设置背景颜色lrcview?.backgroundColor = UIColor.redColor()//添加到滚动的view中lrcBackView.addSubview(lrcview!)//开启分页模式lrcBackView.pagingEnabled = true//隐藏水平滚动条lrcBackView.showsHorizontalScrollIndicator = false//设置scorllView的代理lrcBackView.delegate = self}

10 设置图片的圆角和状态栏的样式

    //** 设置圆角图片 */private func setUpForeImage() ->() {foreImageView.layer.cornerRadius = foreImageView.width * 0.5foreImageView.layer.masksToBounds = true}//设置状态栏的样式override func preferredStatusBarStyle() -> UIStatusBarStyle {return .LightContent}

11 在第6点中我们直接通过两个方法来调用同时也达到了分层的调用

    //这些方法只需要调用一次即可override func viewDidLoad() {super.viewDidLoad()setUpOnce()}// 在视图加载好之后, 有可能, 里面拿到的不是真实的最终frame, 有可能是xib里面的大小//该方法会频繁调用,但是调用越频繁,frame越准确override func viewWillLayoutSubviews() {super.viewWillLayoutSubviews()setUpLrcFrame()}

12 当拖动显示歌词的view的时候,背景图片慢慢的呈现透明样式,可直接通过下面代理方法解决

///MARK : - scrollView的代理方法的实现
extension XFJQQDetailVC : UIScrollViewDelegate {func scrollViewDidScroll(scrollView: UIScrollView) {let alpha = 1 - scrollView.contentOffset.x / scrollView.width//设置背景图片的透明度foreImageView.alpha = alpha//设置显示歌名的label透明度lrcLabel.alpha = alpha}
}

十一 总结

1 可能出现错误的地方 : 在处理详情页中间的图片圆角可能会出现图片不是很圆的情况?

2 直接在viewWillLayoutSubviews中设置才是最准确的(解决).

3 掌握工具类的分层思想对初学者来说可能有点不能接受,但是能明白将是一种非常实用的思想(理解).

4 注意自定义cell中,在自定义cell的内部提供一个放发,直接返回cell,不需要将cell的实现写在数据源方法中,只需要在数据源方法中调用接口,就能达到目的(掌握).

5 最后,希望大家尽可能的去理解吧,不懂的可以随时给我留言.如果大家觉得我写的博客还满意的话,麻烦大家关注我的官方博客,谢谢!!!!

swift版QQ音乐播放器(一)相关推荐

  1. swift版QQ音乐播放器(二)

    一 完善部分的QQ音乐效果图 二 需要完善点 1 歌曲的切换和暂停播放 2 歌曲当前播放时间和歌曲总时间的更新 3 进度条的处理 4 歌手头像处理 5 头像动画效果 6 歌词的进度显示 8 完善细节 ...

  2. 基于jQuery仿QQ音乐播放器网页版代码

    基于jQuery仿QQ音乐播放器网页版代码是一款黑色样式风格的网页QQ音乐播放器样式代码.效果图如下: 在线预览    源码下载 实现的代码. html代码: <div class=" ...

  3. qq音乐播放器2014最新版 v10.23.4377 官方版

    qq音乐播放器2014最新版 v10.23.4377 官方版 软件大小:11MB 软件语言:简体中文 软件性质:常用软件 软件授权:官方版 更新时间:2014-05-06 应用平台:/Win8/Win ...

  4. qq音乐播放器2014最新版 v10.21.4270 官方版

    qq音乐播放器2014最新版 v10.21.4270 官方版 软件大小:11MB 软件语言:简体中文 软件性质:常用软件 软件授权:官方版 更新时间:2014-03-31 应用平台:/Win8/Win ...

  5. jQuery仿QQ音乐播放器

    本文通过Html+CSS+jQuery开发仿QQ版的音乐播放器,是前端技术的综合应用,所用素材来源于网络,仅供学习分享使用,如有不足之处,还请指正. 涉及知识点 在本例中用到的知识点如下,按jQuer ...

  6. QQ音乐播放器-jQuery实现

    QQ音乐播放器 案例展示 案例实现的功能 静态页面的布局 歌曲信息的动态显示 鼠标悬停,功能按钮和文字高亮 歌曲信息的动态显示 歌曲播放 进度条显示和动态移动 纯净模式的模板设置和歌词写入 案例布局 ...

  7. html5卡拉OK音乐播放器,QQ音乐播放器怎么打开卡拉OK模式

    QQ音乐播放器怎么打开卡拉OK模式 时间:2020-08-05 12:25:56 责任编辑:随便就行 QQ音乐播放器怎么打开卡拉OK模式?QQ音乐播放器是生活中常用的音乐播放器,很多人在使用QQ音乐播 ...

  8. QQ音乐播放器部分笔记

    QQ音乐播放器案例 1. 基本结构分析 三个部分: 头部区域 中间内容区 左边歌曲列表信息 上边按钮工具条和下边的滑动列表[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img- ...

  9. Qt5学习 模仿qq音乐播放器样式(2)——点击动画效果+歌词颜色变换展示

    拖的太久,主要再上一篇文章中,新学习了相关知识,做了右键菜单,点击按钮动画切换窗口和播放时歌词颜色显示当前播放位置. 主要为了实现功能的展示,所以很多文件读取都直接采用了本地文件这种比较low的方式. ...

最新文章

  1. URL加随机数的作用
  2. java获取昨天日期
  3. python绘制概率密度曲线_[python常用图件绘制#04]核密度曲线图(峰值点显示)
  4. SAP Spartacus 自定义 Component 的使用 - SimpleResponsiveBannerComponent
  5. mysql 存储过程 is_Mysql存储过程语法问题...
  6. 《Android游戏开发详解》一1.8 控制流程第2部分——while和for循环
  7. rpm软件管理程序,yum仓库的作用
  8. .net vue漂亮登录界面_一文弄懂前端框架Vue 的核心——数据绑定,为升职涨薪加分
  9. 将python 脚本转换为exe格式
  10. 【转载】一些常用的WebServices 天气,IP,邮编,Emai
  11. codeforces-1009D Relatively Prime Graph
  12. 危机2.0时代,企业任重而道远
  13. EXCEL破冰 - 锁定单元格样式和输入格式
  14. Jordan CP3 11 Performance Reviews
  15. biopython中文指南_Biopython的列表和限制类型
  16. 计算机毕业设计Java爱馨敬老院网站(源码+系统+mysql数据库+lw文档)
  17. 为什么新疆人吃的羊肉膻味那么重?
  18. JS中文排序(Ext中文排序补丁)
  19. 你为何没有成为领导者
  20. 记录kafka consumer 消费失败

热门文章

  1. 使用Markdown绘制UML图
  2. Head First Servlets and JSP(二)
  3. 外行怎么快速林java_我自学编程,只为月薪过万,就这么简单!(外行也可看)...
  4. Axure RP 8.1.0.3377 for Mac
  5. Android从零单排之免费短信验证
  6. python整体向右缩进两个级别_关于python:IndentationError:unindent与任何外部缩进级别都不匹配...
  7. 【唐老狮】C#四部曲之C#基础:习题汇总
  8. 西门子PLC中STL语言状态字
  9. 利用Unicode控制字符-RLO构造欺骗性文件后缀
  10. lowlevel_init 函数分析