一 完善部分的QQ音乐效果图
二 需要完善点
1 歌曲的切换和暂停播放
2 歌曲当前播放时间和歌曲总时间的更新
3 进度条的处理
4 歌手头像处理
5 头像动画效果
6 歌词的进度显示
8 完善细节
三 添加歌曲展示页面中的动画效果
1 代码书写位置 : 由于展示歌词的控制器的UITableViewController,那么我们可以使用代理方法.当用户拖动tableView的时候,会调用一个方法,在该方法中实现动画效果
2 思路 : 通过拿到第一个cell和最后一个cell来计算中间cell的索引值,然后遍历所有的索引,取出cell在分别计算各个cell的x值.就可以达到目的.
3 直接奉上代码 :
//** 实现一种动画效果 */
override func scrollViewDidScroll(scrollView: UIScrollView) {
//获取指定的cell
guard let indexRows = tableView.indexPathsForVisibleRows else {
return
}
//** 获取中间的cell */
let firstCell = indexRows.first?.row ?? 0
let lastCell = indexRows.last?.row ?? 0
//** 计算中间的cell */
let middleCell = Int(Float(firstCell + lastCell) * 0.5)
//** 遍历指定的所有的cell */
for indexRow in indexRows {
//取出cell
let cell = tableView.cellForRowAtIndexPath(indexRow)
cell?.x = CGFloat(abs(indexRow.row - middleCell) * 40)
print(indexRow.row - middleCell)
}
}
四 处理音乐切换和暂停播放
1 通过在storyboard中自动布局,达到下面图中的效果(这里我就不一 一细说了,都是基本)
2 通过从storyboard拖线的方式我们可以拿到需要设置的东西
—-> 2.1 注意摆放位置 : 这里我们按照经常需要改变的属性和只改变一次属性的模式来摆放代码.
3 代码部分 :
—-> 3.1 只需要改变一次的属性(里面包含了上一篇中的部分属性)
//** 歌曲名称 */
@IBOutlet weak var nameLabel: UILabel!
//** 歌手名称 */
@IBOutlet weak var singerLabel: UILabel!
//** 总音乐时间 */
@IBOutlet weak var totalLabel: UILabel!
//** 歌手背景图片 */
@IBOutlet weak var singerImageView: UIImageView!
//** 能让现实歌词的view拖动的scrollView */
@IBOutlet weak var lrcSImageView: UIScrollView!
//** 歌手专辑图片 */
@IBOutlet weak var backImageView: UIImageView!
//** 暂停或者播放 */
@IBOutlet weak var playOrPause: UIButton!
—-> 3.2 经常需要改变的属性(里面包含了上一篇中的部分属性)
//** 当前时间 */
@IBOutlet weak var currentLabel: UILabel!
//** 显示歌词的label */
@IBOutlet weak var lrcLabel: UILabel!
//** 滑块 */
@IBOutlet weak var silderProgress: UISlider!
4 封装工具类(封装调用思想: 三层调用)
—-> 4.1 工具类一 : 对单首音乐的播放状态工具类(往里面添加继续播放;暂停播放的方法)
———-> 4.1.1 该部分代码 :
//** 继续播放音乐 */
func playMusic() {
player?.play()
}
//** 暂停播放 */
func pauseMusic() {
player?.pause()
}
—-> 4.2 工具类二 : 在负责音乐的业务逻辑工具类(获取最新音乐数据;重新播放音乐;暂停播放音乐;上一首和下一首)
———-> 4.2.1 记录播放歌曲的索引
//** 创建音乐播放列表 (XFJQQMusicListItem: 音乐列表的模型)*/
var musicList : [XFJQQMusicListItem]?
//** 提供一个记录歌曲的索引 */
var index = 0 {
didSet {
//判断
if index < 0 {
index = (musicList?.count ?? 0) - 1
}
if index > ((musicList?.count ?? 0) - 1){
index = 0
}
}
}
———-> 4.2.2 创建另外一个模型
———-> 4.2.3 问题 : 怎么才能当前创建的模型具有第一个模型中的属性呢?
———-> 4.2.4 解决 : 才用拥有的方法(让第一个模型作为第二个模型中的属性)
———-> 4.2.5 新创建的模型需要考虑到的属性 : 当前播放时间;歌曲总时间;播放状态
———-> 4.2.6 设计属性采用的方法 : 对当前时间和总歌曲时间提供get方法
———-> 4.2.7 具体代码如下 :
class XFJQQMusicNewMessageModel: NSObject {
//* 让该模型拥有显示歌单的模型 /
var QQMusicItem : XFJQQMusicListItem?
//* 当前播放的时间 /
var costTime : NSTimeInterval = 0
//* 总播放的时间 /
var totalTime : NSTimeInterval = 0
//** 提供当前时间和总时间的get方法 */
var costFormatTime : String {
get{
return XFJQQTimeTool.getTime(costTime)
}
}
//** 提供总时间的get方法 */
var totalFormatTime : String {
get{
return XFJQQTimeTool.getTime(totalTime)
}
}
//** 播放的状态 */
var isPlaying : Bool = false
}
—-> 4.3 在回到负责音乐业务类的工具类中.提供一个方法,负责返回最新的数据
//** 设置一个方法用来提供最新的数据 */
func getQQMusicNewMessage() ->XFJQQMusicNewMessageModel {
//** 给暂时歌词中的模型属性赋值,保持最新的数据 */
QQMusicMessageModel.QQMusicItem = musicList![index]
//** 当前播放的进度 */
QQMusicMessageModel.costTime = tool.player?.currentTime ?? 0
//** 歌曲的总时长 */
QQMusicMessageModel.totalTime = tool.player?.duration ?? 0
//** 播放状态 */
QQMusicMessageModel.isPlaying = tool.player?.playing ?? false
//** 返回最新的数据模型 */
return QQMusicMessageModel
}
—-> 4.4 在该业务中提供一个方法对音乐的处理(判断音乐列表;记录当前播放音乐的索引)
//设置一个方法对音乐的处理
func playMusic(musicM : XFJQQMusicListItem) {
//取出模型中的音乐文件名
let fileName = musicM.filename ?? ""
//传入音乐文件名播放音乐
tool.playMusic(fileName)
//** 判断如果列表中没有音乐就直接返回 */
if musicList == nil {
return
}
//** 并且记录当前的音乐索引 */
index = (musicList?.indexOf(musicM))!
}
—-> 4.5 在该类中在封装一层方法(方法内部分别调用在对单首音乐处理的方法中的继续播放和暂停播放的方法;上一首;下一首的方法)
—-> 4.5.1 重新播放和暂停播放代码 :
//** 重新播放 */
func playCurrentMusic() ->() {
tool.playMusic()
}
//** 暂停播放 */
func pauseCurrentMusic() {
tool.pauseMusic()
}
—-> 4.5.2 上一首和下一首代码 :
//** 上一首 */
func preMusic() {
//索引递减
index -= 1
//判断
if let muiscM = musicList {
//取出模型
let music = muiscM[index]
//播放音乐
playMusic(music)
}
}
//** 下一首 */
func nextMusic() {
//索引增加
index += 1
//判断
if let musicM = musicList {
//取出模型
let music = musicM[index]
//播放
playMusic(music)
}
}
—-> 4.6 工具类三 : 对音乐播放的时间和音乐总时间的处理(转化为具体的分钟和秒钟)
—-> 4.6.1 具体代码详解 :
class XFJQQTimeTool: NSObject {
class func getTime(time : NSTimeInterval) ->String {
let min = Int(time / 60)
let sec = Int(time) % 60
//** 转成字符串 */
let resultStr = String(format: "%02d:%02d", min,sec)
//** 返回结果 */
return resultStr
}
}
—-> 4.7 提供两个方法(分别用来作为该内部调用的 : 对经常执行的和不经常的方法分开存放 )—> 同样采用了分层存放的方法
—-> 4.7.1 在展示歌词的类扩展中设置两个方法(详见代码: 该部分上篇博客已经讲解了)
//** 设置一次 */
private func setUpOnce() {
setUpViewOnce()
setUpSilder()
}
//** 设置多次 */
private func setUpTimes() {
setUpViewFrame()
setUpForeImage()
}
—-> 4.8 根据从storyboard中得到的数据,采用最新创建的模型中的数据来对其赋值(只需要设置一次 : 详见代码)
//** 只需要设置一次的数据 */
private func setUpDataTime() ->() {
let musicMessageModel = XFJQQMusicOperationTool.shareInstance.getQQMusicNewMessage()
//** 歌曲名称 */
nameLabel.text = musicMessageModel.QQMusicItem?.name
//** 歌手背景图片 */
singerImageView.image = UIImage(named: (musicMessageModel.QQMusicItem?.icon ?? ""))
//** 歌手专辑图片 */
backImageView.image = UIImage(named: (musicMessageModel.QQMusicItem?.icon)!)
//** 歌手名称 */
singerLabel.text = musicMessageModel.QQMusicItem?.singer
//** 歌曲总时间 */
totalLabel.text = "\(musicMessageModel.totalFormatTime)"
}
—-> 4.9 根据从storyboard中得到的数据,采用最新创建的模型中的数据来对其赋值(只需要设置多次 : 详见代码)
//** 设置多次的数据 */
func setUpDatasTimes() {
let musicMessageModel = XFJQQMusicOperationTool.shareInstance.getQQMusicNewMessage()
//** 当前播放时间 */
currentLabel.text = "\(musicMessageModel.costFormatTime)"
//** 进度条 */
silderProgress.value = Float(musicMessageModel.costTime / musicMessageModel.totalTime)
//** 播放状态 */
playOrPause.selected = musicMessageModel.isPlaying
}
—-> 4.10 由于用户需要点击具体的按钮来切换相应的业务逻辑,那么我们也采取拖线的方式来达到效果
—-> 4.11 播放和暂停(用单例直接调用工具类中设置的方法)
//** 播放和暂停 */
@IBAction func playAndPause(button : UIButton)
{
//判断
button.selected = !button.selected
if button.selected {
XFJQQMusicOperationTool.shareInstance.playCurrentMusic()
}else{
XFJQQMusicOperationTool.shareInstance.pauseCurrentMusic()
}
}
—-> 4.11 歌曲的切换(直接调用工具类中封装的方法和对属性进行设置的方法)
//** 上一首 */
@IBAction func preButton()
{
let operationTool = XFJQQMusicOperationTool.shareInstance
//调用播放上一首的方法
operationTool.preMusic()
setUpDataTime()
}
//** 下一首 */
@IBAction func nextButton()
{
let operationTool = XFJQQMusicOperationTool.shareInstance
operationTool.nextMusic()
setUpDataTime()
}
—-> 4.12 退出(直接pop就可以)
@IBAction func popVC()
{
navigationController?.popViewControllerAnimated(true)
}
—-> 4.13 当前时间和歌曲总时间的处理(采用定时器)–>里面创建定时器和方法的调用我这里就不写出来了
//** 设置定时器 */
func addTimer() {
//** 设置定时器 */
timer = NSTimer(timeInterval: 1, target: self, selector: "setUpDatasTimes", userInfo: nil, repeats: true)
//** 将定时器添加到主运行循环中 */
NSRunLoop.currentRunLoop().addTimer(timer!, forMode: NSRunLoopCommonModes)
}
//** 移除定时器 */
func removeTimer() ->() {
timer?.invalidate()
timer = nil
}
五 歌手头像动画处理(头像旋转)
1 头像的暂停动画和恢复动画我们采用一个分类(下面的分类中的代码 : 暂时没研究是怎么实现的)
extension CALayer {
// 暂停动画
func pauseAnimate()
{
let pausedTime: CFTimeInterval = convertTime(CACurrentMediaTime(), fromLayer: nil)
speed = 0.0;
timeOffset = pausedTime;
}
// 恢复动画
func resumeAnimate()
{
let pausedTime: CFTimeInterval = timeOffset
speed = 1.0;
timeOffset = 0.0;
beginTime = 0.0;
let timeSincePause: CFTimeInterval = convertTime(CACurrentMediaTime(), fromLayer: nil) - pausedTime
beginTime = timeSincePause;
}
}
2 处理动画
—-> 2.1 通过在类扩展中提供三个方法(恢复动画;暂停动画;添加动画)
—-> 2.2 恢复动画和暂停动画
//恢复动画
private func resumeAnimation() {
foreImageView.layer.resumeAnimate()
}
//暂停动画
private func pauseAnimation() {
foreImageView.layer.pauseAnimate()
}
—-> 2.3 添加动画
private func addAnimation() {
//移除上移个动画
foreImageView.layer.removeAnimationForKey("rotation")
//添加动画
let animation = CABasicAnimation(keyPath: "transform.rotation.z")
//设置相关属性
animation.duration = 30
animation.fromValue = 0
animation.toValue = M_PI * 2
animation.repeatCount = MAXFLOAT
//播放完成后不需要移除动画
animation.removedOnCompletion = false
//添加动画
foreImageView.layer.addAnimation(animation, forKey: "rotation")
}
3 动画调用 :
—-> 3.1 用户点击播放和暂停按钮的时候,需要调用
//** 播放和暂停 */
@IBAction func playAndPause(button : UIButton)
{
button.selected = !button.selected
//判断
if button.selected {
XFJQQMusicOperationTool.shareInstance.pauseCurrentMusic()
pauseAnimation()
}else {
XFJQQMusicOperationTool.shareInstance.playCurrentMusic()
resumeAnimation()
}
}
—-> 3.2 当用户点击显示歌曲的tableView中的cell,页面开始跳转后需要动画效果.(只需要设置一次)
//将歌词的数据源交给展示歌曲的控制器来展示
lrcTVC.dataSource = lrcMs
//切换歌曲开始动画
addAnimation()
//判断如果是选择暂停和开始的属性做相应的动画
if musicMessageModel.isPlaying {
resumeAnimation()
}else {
pauseAnimation()
}
六 歌词的展示(比较有难度)
1 封装处理歌词的工具类.(封装原因 : 展示出来的数据并不符合格式,需要我们做处理)–>下图展示的是需要处理的歌词数据
2 创建歌词展示的模型(模型中需要的属性 : 歌曲开始时间;歌曲结束时间;歌词内容)
class XFJQQLrcModel: NSObject {
//** 歌词演唱的开始时间 */
var beginTime : NSTimeInterval = 0
//** 歌词演唱的结束时间 */
var endTime : NSTimeInterval = 0
//** 歌词的内容 */
var lrcStr : String = ""
}
3 处理歌词工具类的文件
4 在该工具类中创建一个方法能返回具体哪行返回的歌词(采用元组的方法)
//提供一个方法对某一行歌词的返回(需要传入当前播放的时间进度和模型中的开始时间,结束时间)
//采用元组的方式来达到读取歌词的经度
class func getLrcMusicRow(current : NSTimeInterval, lrcModel : [XFJQQLrcModel]) ->(row :Int, lrcM : XFJQQLrcModel){
//取出模型的个数
let count = lrcModel.count
//遍历
for i in 0..<count {
let lrcM = lrcModel[i]
//判断时间区间
if lrcM.beginTime < current && lrcM.endTime > current {
return (i, lrcM)
}
}
return (0, XFJQQLrcModel())
}
5 设计一个方法: 当外界调用的时候传入首歌曲的歌词,然后经过该方法,返回一个解析好的可实现的歌词.
—-> 5.1 处理该部分的歌词 :
[ti:]
[ar:]
[al:]
—-> 5.2 代码(类方法)–> 里面包括 : 异常处理;将歌词转换为一行一行的显示;过滤掉不能处理的符号;装入模型中(该方法的上部分)
//设计一个方法用来处理歌词
class func getLrcMusicData(fileName : String?) ->[XFJQQLrcModel] {
//获取歌词文件的路径
guard let path = NSBundle.mainBundle().pathForResource(fileName, ofType: nil) else{
return [XFJQQLrcModel]()
}
//加载文件的内容
var contentLrc = ""
do {
contentLrc = try String(contentsOfFile: path)
}catch {
print(error)
return [XFJQQLrcModel]()
}
//将歌词转成一行一行组成的数组
let lrcArray = contentLrc.componentsSeparatedByString("\n")
//创建一个装XFJQQLrcModel这种类型的数组
var lrcMs = [XFJQQLrcModel]()
//遍历歌词的组成的数组,然后放入lrcMs数组中
for lrcmStr in lrcArray {
//过滤掉垃圾数据
if lrcmStr.containsString("[ti:") || lrcmStr.containsString("[ar:") || lrcmStr.containsString("[al:") {
continue
}
//装入上面的数组中
let lrcm = XFJQQLrcModel()
lrcMs.append(lrcm)
6 通过调用处理好的歌词的方法来处理过滤好的歌词
[00:00.89]传奇
[00:02.34]作词:刘兵
[00:03.82]作曲:李健
[00:05.48]演唱:王菲
—-> 6.1 代码 :
//拿到的数据才是真正能解析的数据
//替换
let resultStr = lrcmStr.stringByReplacingOccurrencesOfString("[", withString: "")
//开始解析
let timeAndContent = resultStr.componentsSeparatedByString("]")
if timeAndContent.count == 2 {
let time = timeAndContent[0]
lrcm.beginTime = XFJQQTimeTool.getLrcFormatTime(time)
let content = timeAndContent[1]
lrcm.lrcStr = content
}
}
//遍历所有的数组中的时间
let count = lrcMs.count
for i in 0..<count {
if i != count - 1 {
lrcMs[i].endTime = lrcMs[i + 1].beginTime
}
}
//返回处理好的时间和歌词
return lrcMs
}
}
—-> 6.2 对能处理的歌词方法的调用
//** 该方法是对歌词的开唱时间和结束时间的处理(根据传入的字符串,返回一个确切的时间) */
class func getLrcFormatTime(time : String) ->NSTimeInterval {
//根据":"取出时间
let minAndSec = time.componentsSeparatedByString(":")
//判断
if minAndSec.count == 2 {
//分钟
let min = NSTimeInterval(minAndSec[0]) ?? 0
//秒钟
let sec = NSTimeInterval(minAndSec[1]) ?? 0
//返回处理好的时间
return min * 60 + sec
}
//如果走到这里,就直接返回0
return 0
}
7 将处理好的歌词交给显示歌词的tableView来显示
//获取歌词数据源
let lrcMs = XFJQQMusicLrcDataTool.getLrcMusicData(musicMessageModel.musicModel?.lrcname)
//打印出歌词
print(lrcMs)
//将歌词的数据源交给展示歌曲的控制器来展示
lrcTVC.dataSource = lrcMs
8 之间用来显示歌词的是UIView,现在我们使用UITableView来显示,将使用到UIView显示歌词的地方全都用tableView来代替.
9 实现tableView中数据源发方法
///MARK : - 数据源方法
extension XFJQQLrcTVC {
//组
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
//每组的cell
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
//由模型数据决定
return dataSource.count
}
//每行cell的内容
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
//用该方法得到cell
let cell = XFJQQMusicLrcCell.getMusicLrcCell(tableView)
//判断如果当前处在最中间的是正在播放的行数,那么久赋值,否则就赋值为0
if indexPath.row == scrollRow {
cell.progress = progress
}else {
cell.progress = 0.0
}
//取出模型
let lrcStr = dataSource[indexPath.row]
//赋值
cell.lrcStr = lrcStr.lrcStr
return cell
}
}
10 自定义cell和自定义显示歌词的label
—-> 10.1 自定义cell(该部分是对cell中label的显示)
@IBOutlet weak var lrcLabel: XFJQQMusicLrcLabel!
//提供一个属性给展示歌词的view中的歌词状态
var progress : Double = 0.0 {
didSet {
lrcLabel.progress = progress
}
}
var lrcStr : String = "" {
didSet {
lrcLabel.text = lrcStr
}
}
}
—-> 10.2 返回创建好的cell
///MARK : - 再累扩展中提供一个方法,返回cell
extension XFJQQMusicLrcCell {
class func getMusicLrcCell(tableView : UITableView) ->XFJQQMusicLrcCell {
let lrcCellID = "lrcCell"
var cell = tableView.dequeueReusableCellWithIdentifier(lrcCellID) as? XFJQQMusicLrcCell
if cell == nil {
cell = NSBundle.mainBundle().loadNibNamed("XFJQQMusicLrcCell", owner: nil, options: nil).first as? XFJQQMusicLrcCell
}
return cell!
}
}
—-> 10.3 自定义label
class XFJQQMusicLrcLabel: UILabel {
//自定义更新歌词的进度(提供set方法)
var progress : Double = 0.0 {
didSet {
//重绘
setNeedsDisplay()
}
}
override func drawRect(rect: CGRect) {
super.drawRect(rect)
//设置填充颜色
UIColor.greenColor().set()
let progressloat = CGFloat(progress)
let rect = CGRectMake(rect.origin.x, rect.origin.y, rect.size.width * progressloat, rect.size.height)
UIRectFillUsingBlendMode(rect, CGBlendMode.SourceIn)
}
}
11 当有歌词歌曲变化的时候,立刻更新歌词的数据
var dataSource : [XFJQQLrcModel] = [XFJQQLrcModel]() {
didSet {
tableView.reloadData()
}
}
12 在显示歌词的tableView控制器中设置一个属性用来提供正在歌唱的歌词始终处于tableView的中间cell
//设置歌词滚动到中间(set方法)
var scrollRow : Int = 0 {
didSet {
//判断防止重复滚动(用旧值作为判断)
if scrollRow != oldValue {
tableView.reloadRowsAtIndexPaths(tableView.indexPathsForVisibleRows!, withRowAnimation: UITableViewRowAnimation.Fade)
let indexPath = NSIndexPath(forRow: scrollRow, inSection: 0)
//拿到角标滚动到哪个位置
tableView.scrollToRowAtIndexPath(indexPath, atScrollPosition: UITableViewScrollPosition.Middle, animated: true)
//刷新(如果先做动画再刷新的话,动画还没做完就直接刷新了,会出现显示歌词的view跳动,所以需要先刷新再动画)
// tableView.reloadRowsAtIndexPaths(tableView.indexPathsForVisibleRows!, withRowAnimation: UITableViewRowAnimation.Fade)
}
}
}
13 给正在歌唱的cell中的歌词设置进度(set方法)
var progress : Double = 0.0 {
didSet {
//获取当前正在行号
let indexPath = NSIndexPath(forRow: scrollRow, inSection: 0)
//将当前的cell类型转换
let cell = tableView.cellForRowAtIndexPath(indexPath) as? XFJQQMusicLrcCell
//给cell中progress赋值
cell?.progress = progress
}
}
14 处理已进入展示歌词的页面,歌词的头部位置处于tableView的中间部位
//在view将要布局的时候对显示歌词的view布局,让其头部显示在中间
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
tableView.contentInset = UIEdgeInsets(top: tableView.height * 0.5, left: 0, bottom: tableView.height * 0.5, right: 0)
}
15 更新滚动时候的歌词(该方法调用频繁)
//更新滚动的时候歌词(调用频率频繁)
func updateLrc () ->() {
//获取歌曲的最新值
let musicMessageModel = XFJQQMusicOperationTool.shareInstance.getUpNewMusicMessage()
//获取最新歌词的行号
let rowLrm = XFJQQMusicLrcDataTool.getLrcMusicRow(musicMessageModel.costTime, lrcModel:lrcTVC.dataSource)
let row = rowLrm.row
//赋值
lrcTVC.scrollRow = row
//给歌词标签展示歌词
let lrcm = rowLrm.lrcM
lrcLabel.text = lrcm.lrcStr
//歌词进度(当前的进度除以在总进度)
let value = (musicMessageModel.costTime - lrcm.beginTime) / (lrcm.endTime - lrcm.beginTime)
//赋值
lrcLabel.progress = value
//给展示歌词的view赋值
lrcTVC.progress = value
16 怎么才能让歌词滚动呢? 通过系统的一个类CADisplayLink(和定时器作用差不多,这里我代码就不写上了)
七 注意点和性能优化
1 注意 : 退出后台动画不再旋转—> 解决方案 : 动画完成之后,不需要移除
2 怎么样将歌词分割成一行一行,然后展示呢?—>解决方案 : 通过设置一个歌词模型来展示
3 歌词不滚动?—> 解决方案 : CADisplayLink(类似定时器)
4 歌词显示的时候出现了刷新问题?—> 解决方案 : 先刷新,在滚动
5 性能优化 : 图片绘制的次数太多?
—-> 5.1 解决方案 : 通过设置一个属性记录歌词的行值,然后判断是否是同一行
6 当前显示的时间一直在更新,暂停的时候不需要更新,也可以优化(留给你们实现了)
八 功能补充(额外的想法,还未实现)
1 拖动进度条达到快进的目的
2 后台模式(后面会抽时间补上)
3 摇一摇切换歌曲
九 总结
1 该篇博客写的思路还算清晰,唯一的缺点是我无法在后面歌词显示部分更加详细点说明,因为该处真是没办法说明了,只能奉上我写的代码了.
2 本篇博客是基于QQ音乐的swift版本,还有很多的功能没有实现,希望后面我能抽出时间进一步的完善也希望阅读过我博客的程序猿能提供意见.
3 最后,由于时间一直没有抽出来,这使得博客更新的速度有点慢,后续我会慢慢补上.如果大家觉的我写的还可以,麻烦大家关注我的官方博客,谢谢!!!!
时间: 2024-10-12 07:16:01