1.编写标签页结构及样式
1.1结构
在pages/index/index.wxml
<!-- 标签页标题 -->
<view class="tab">
<view class="tab-item">音乐推荐</view>
<view class="tab-item">播放器</view>
<view class="tab-item">播放列表</view>
</view>
<!-- 内容区域 -->
<view class="content"></view>
<!-- 底部播放器 -->
<view class="player"></view>
1.2.样式
整个page样式
page {
display: flex;
flex-direction: column;
background: #17181a;
color: #ccc;
height: 100%;
}
.content {
flex: 1;
}
使音乐推荐、播放器、播放列表平均分配宽度,并设置样式
.tab {
display: flex;
}
.tab-item {
flex: 1;
font-size: 10pt;
text-align: center;
line-height: 72rpx;
border-bottom: 6rpx solid #eee;
}
底部player,由于content是flex为1,将tab和palyer挤到顶部和底部
.player {
background: #222;
border-top: 1px solid #252525;
height: 112rpx;
}
1.3.在页面content中添加3个swiper
<swiper>
<swiper-item>
<include src="info.wxml" />
</swiper-item>
<swiper-item>
<include src="play.wxml" />
</swiper-item>
<swiper-item>
<include src="playlist.wxml" />
</swiper-item>
</swiper>
1.4.新建这三个页面info.wxml、play.wxml、playlist.wxml
info.wxml
<view style="background:#ddd; color:#000; height:100%"> info</view>
play.wxml
<view style="background:#ccc; color:#000; height:100%">play</view>
playlist.wxml
<view style="background:#eee; color:#000; height:100%">playlist</view>
添加样式使swiper高度占满整个content区域
.content > swiper {
height: 100%;
}
2.实现标签页切换
2.1给标签页标题加绑定事件和data-item
bindtap="changeItem" data-item="0"
swiper新增current属性
current="{{item}}"
新增代码实现效果如下:
2.2在index.js中添加函数
// 页面切换
changeItem: function(e) {
this.setData({
item: e.target.dataset.item,
})
},
需要的item数据存入data中,默认第一页,所以设置默认值为0
item: 0
效果如下:
2.3手动滑动执行样式切换事件
index.wxml中添加滑动事件
bindchange="changeTab"
效果如下:
index.js中添加该事件:
// tab切换
changeTab: function(e) {
this.setData({
tab: e.detail.current
})
},
data中新增
tab: 0
需要用tab实现样式激活状态
页面
{{tab==0?'active':''}}
{{tab==1?'active':''}}
{{tab==2?'active':''}}
实现效果:
当前样式:
.tab-item.active {
color: #c25b5b;
border-bottom-color: #c25b5b;
}
当前样式效果如下:
3.音乐推荐
该页面需要上下滑动,需要加scroll-view组件,音乐推荐分为上中下:轮播图、3个按钮、推荐歌曲
3.1 scroll-view组件demo
<scroll-view scroll-x scroll-y style="height:200px" bindscroll="scroll">
<view style="width:200%;height:400px;background:#ccc"></view>
</scroll-view>
scroll: function(e) {
console.log(e.detail)
},
查看控制台看滚动事件的一些参数
scroll-view组件事件对象参数分析:
scrollLeft:横向滚动条左侧到视图左边的距离。
scrollTop:纵向滚动条上端到视图顶部的距离。
scrollHeight:纵向滚动条在Y轴上最大滚动距离。
scrollWidth:横向滚动条在X轴上最大的滚动距离。
deltaX:横向滚动条的滚动状态。
deltaY:纵向滚动条的滚动状态。
3.2 imag组件
显示、裁剪或缩放
image组件属性及说明
image组件缩放模式
image组件9种裁剪模式
image组件缩放模式和裁剪模式测试:
<view>缩放模式测试</view>
<image style="width:200px;height:200px;background-color:#eee;"
mode="scaleToFill" src="/images/test.jpg"></image>
<view>裁剪模式测试</view>
<image style="width:200px;height:200px;background-color:#eee;"
mode="top" src="/images/test.jpg"></image>
效果:
3.3 内容区域滚动
在info.wxml中:
<scroll-view class="content-info" scroll-y>
<view style="background:#eee;height:1000px"></view>
<view>已到达底部</view>
</scroll-view>
.content-info {
height: 100%;
}
效果:
3.4 轮播图
在pages/index/info.wxml页面代码:
<!-- 轮播图 -->
<swiper class="content-info-slide" indicator-color="rgba(255,255,255,.5)" indicator-active-color="#fff" indicator-dots circular autoplay>
<swiper-item>
<image src="/images/banner.jpg" />
</swiper-item>
<swiper-item>
<image src="/images/banner.jpg" />
</swiper-item>
<swiper-item>
<image src="/images/banner.jpg" />
</swiper-item>
</swiper>
在pages/index/index.wxss中新增样式:
/* 轮播图 */
.content-info-slide {
height: 302rpx;
margin-bottom: 20px;
}
.content-info-slide image {
width: 100%;
height: 100%;
}
效果:
3.5功能按钮
在pages/index/info.wxml页面新增功能按钮代码:
<!-- 功能按钮 -->
<view class="content-info-portal">
<view>
<image src="/images/04.png" />
<text>私人FM</text>
</view>
<view>
<image src="/images/05.png" />
<text>每日歌曲推荐</text>
</view>
<view>
<image src="/images/06.png" />
<text>云音乐新歌榜</text>
</view>
</view>
在pages/index/index.wxss中新增样式:
/* 功能按钮 */
.content-info-portal {
display: flex;
margin-bottom: 15px;
}
.content-info-portal > view {
flex: 1;
font-size: 11pt;
text-align: center;
}
.content-info-portal image {
width: 120rpx;
height: 120rpx;
display: block;
margin: 20rpx auto;
}
3.6 热门音乐(推荐歌曲)
在pages/index/info.wxml页面新增功能按钮代码:
<!-- 热门音乐 -->
<view class="content-info-list">
<view class="list-title">推荐歌曲</view>
<view class="list-inner">
<view class="list-item">
<image src="/images/cover.jpg" />
<view>紫罗兰</view>
</view>
<view class="list-item">
<image src="/images/cover.jpg" />
<view>五月之歌</view>
</view>
<view class="list-item">
<image src="/images/cover.jpg" />
<view>菩提树</view>
</view>
<view class="list-item">
<image src="/images/cover.jpg" />
<view>欢乐颂</view>
</view>
<view class="list-item">
<image src="/images/cover.jpg" />
<view>安魂曲</view>
</view>
<view class="list-item">
<image src="/images/cover.jpg" />
<view>摇篮曲</view>
</view>
</view>
</view>
在pages/index/index.wxss中新增样式:
/* 热门音乐 */
.content-info-list {
font-size: 11pt;
margin-bottom: 20rpx;
}
.content-info-list > .list-title {
margin: 20rpx 35rpx;
}
.content-info-list > .list-inner {
display: flex;
flex-wrap: wrap;
margin: 0 20rpx;
}
.content-info-list > .list-inner > .list-item {
flex: 1;
}
.content-info-list > .list-inner > .list-item > image {
display: block;
width: 200rpx;
height: 200rpx;
margin: 0 auto;
border-radius: 10rpx;
border: 1rpx solid #555;
}
.content-info-list > .list-inner > .list-item > view {
width: 200rpx;
margin: 10rpx auto;
font-size: 10pt;
}
4.播放器
4.1播放器的功能分析
从上到下:音乐信息,歌曲封面,进度条;进度条有播放时长和总时长,手动改变播放进度。
4.2 音频API(新知识点测试部分)
audioCtx对象声明的方式:
var audioCtx = wx.createInnerAudioContext();
其属性和方法可以实现音频播放功能
在测试页面pages/test/index.js中加入以下代码:
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady: function () {
// 创建InnerAudioContext实例
var audioCtx = wx.createInnerAudioContext()
// 设置音频资源地址
audioCtx.src = 'http://localhost:3000/1.mp3'
// 当开始播放时,输出调试信息
audioCtx.onPlay(function () {
console.log('开始播放')
})
// 当发生错误时,输出调试信息
audioCtx.onError(function (res) {
console.log(res.errMsg) // 错误信息
console.log(res.errCode) // 错误码
})
// 开始播放
audioCtx.play()
},
4.3 slider组件(新知识点测试部分)
在测试页面pages/test/index.wxml加入以下代码:
<slider bindchanging="sliderChanging" show-value />
在测试页面pages/test/index.js加入以下代码:
sliderChanging: function (e) {
console.log(e.detail.value)
}
4.5 定义基础数据
音乐播放列表和音乐状态数据:
在测试页面pages/indx/index.js的data中加入以下代码:
// 播放列表数据
playlist: [{
id: 1,
title: '钢琴协奏曲',
singer: '肖邦',
src: 'http://localhost:3000/1.mp3',
coverImgUrl: '/images/cover.jpg'
}, {
id: 2,
title: '奏鸣曲',
singer: '莫扎特',
src: 'http://localhost:3000/2.mp3',
coverImgUrl: '/images/cover.jpg'
}, {
id: 3,
title: '欢乐颂',
singer: '贝多芬',
src: 'http://localhost:3000/1.mp3',
coverImgUrl: '/images/cover.jpg'
}, {
id: 4,
title: '爱之梦',
singer: '李斯特',
src: 'http://localhost:3000/2.mp3',
coverImgUrl: '/images/cover.jpg'
}],
state: 'paused',
playIndex: 0,
play: {
currentTime: '00:00',
duration: '00:00',
percent: 0,
title: '',
singer: '',
coverImgUrl: '/images/cover.jpg',
}
4.6 实现音乐播放功能
4.6.1在pages/index/index.js中添加属性audioCtx
并添加事件
onReady: function () {
this.audioCtx = wx.createInnerAudioContext()
this.setMusic(0)
},
setMusic:function(index){
var music = this.data.playlist[index]
this.audioCtx.src = music.src
this.setData({
playIndex: index,
play: {
currentTime: '00:00',
duration: '00:00',
percent: 0,
title: music.title,
singer: music.singer,
coverImgUrl: music.coverImgUrl,
}
});
this.audioCtx.play();
}
重新编译项目,项目刚开始运行有音乐已经播放出,证明播放成功,测试完毕后注释this.audioCtx.play();
4.6.2 底部播放控件
在pages/index/index.wxml页面中添加底部播放控件页面代码
<!-- 底部播放器 -->
<view class="player">
<image class="player-cover" src="{{play.coverImgUrl}}" />
<view class="player-info">
<view class="player-info-title">{{play.title}}</view>
<view class="player-info-singer">{{play.singer}}</view>
</view>
<view class="player-controls">
<!-- 切换到播放列表 -->
<image src="/images/01.png" bindtap="changePage" data-page="2" />
<!-- 播放或暂停 -->
<image wx:if="{{state=='paused'}}" src="/images/02.png" bindtap="play" />
<image wx:else src="/images/02stop.png" bindtap="pause" />
<!-- 下一曲 -->
<image src="/images/03.png" bindtap="next" />
</view>
</view>
在pages/index/index.wxss中添加底部播放控件样式
/* 底部播放控件 */
.player {
display: flex;
align-items: center;
background: #222;
border-top: 1px solid #252525;
height: 112rpx;
}
.player-cover {
width: 80rpx;
height: 80rpx;
margin-left: 15rpx;
border-radius: 8rpx;
border: 1px solid #333;
}
.player-info {
flex: 1;
font-size: 10pt;
line-height: 38rpx;
margin-left: 20rpx;
padding-bottom: 8rpx;
}
.player-info-singer {
color: #888;
}
.player-controls image {
width: 80rpx;
height: 80rpx;
margin-right: 15rpx;
}
pages/index/index.js中添加“播放”、“暂停”、“下一首”3个按钮的事件
//暂停按钮
pause:function(e){
this.audioCtx.pause()
this.setData({state:'paused'})
},
//播放按钮
play:function(e){
this.audioCtx.play()
this.setData({state:'running'})
},
//下一首按钮
next:function(e){
var index = this.data.playIndex>=this.data.playlist.length-1?0:this.data.playIndex+1
this.setMusic(index)
if(this.data.state==='running'){
this.audioCtx.play()
}
}
js代码放置的位置:
4.7 编写播放器页面
在pages/index/play.wxml页面代码:
<!-- 播放器 -->
<view class="content-play">
<!-- 显示音乐信息 -->
<view class="content-play-info">
<text>{{play.title}}</text>
<view>—— {{play.singer}} ——</view>
</view>
<!-- 显示专辑封面 -->
<view class="content-play-cover">
<image src="{{play.coverImgUrl}}" style="animation-play-state:{{state}}" />
</view>
<!-- 显示播放进度和时间 -->
<view class="content-play-progress">
<text>{{play.currentTime}}</text>
<view>
<slider bindchange="sliderChange" activeColor="#d33a31" block-size="12" backgroundColor="#dadada" value="{{play.percent}}" />
</view>
<text>{{play.duration}}</text>
</view>
</view>
在pages/index/index.wxss中新增样式:
/* 播放器 */
.content-play {
display: flex;
justify-content: space-around;
flex-direction: column;
height: 100%;
text-align: center;
}
.content-play-info > view {
color: #888;
font-size: 11pt;
}
/* 显示专辑页面样式 */
.content-play-cover image {
animation: rotateImage 10s linear infinite;
width: 400rpx;
height: 400rpx;
border-radius: 50%;
border: 1px solid #333;
}
@keyframes rotateImage {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
/* 播放进度和时间 */
.content-play-progress {
display: flex;
align-items: center;
margin: 0 35rpx;
font-size: 9pt;
text-align: center;
}
.content-play-progress > view {
flex: 1;
}
4.8 控制播放进度
需要设置播放参数,当前时间,总时间,播放百分比,如下图:
4.8.1 先设置播放失败提示
代码:
this.audioCtx.onError(()=>{
console.log('播放失败:' + this.audioCtx.src)
})
播放完后自动换下一曲
效果:
代码:
// 播放完成自动换下一曲
this.audioCtx.onEnded(()=>{
this.next()
})
自动更新播放进度
// 自动更新播放进度
this.audioCtx.onPlay(function() {})
this.audioCtx.onTimeUpdate(()=> {
this.setData({
'play.duration': formatTime(this.audioCtx.duration),
'play.currentTime': formatTime(this.audioCtx.currentTime),
'play.percent': this.audioCtx.currentTime / this.audioCtx.duration * 100
})
})
格式化时间
// 格式化时间
function formatTime(time) {
var minute = Math.floor(time / 60) % 60;
var second = Math.floor(time) % 60
return (minute < 10 ? '0' + minute : minute) + ':' + (second < 10 ? '0' + second : second)
}
滚动条调节歌曲进度
// 滚动条调节歌曲进度
sliderChange: function(e) {
var second = e.detail.value * this.audioCtx.duration / 100
this.audioCtx.seek(second)
},
- 播放列表
5.1 播放列表分析
测试时多建立几个音乐列表,检查上下滚动效果
5.2 编写播放页列表和样式
pages/index/palylist.wxml
<scroll-view class="content-playlist" scroll-y>
<view class="playlist-item" wx:for="{{playlist}}" wx:key="id" bindtap="change" data-index="{{index}}">
<image class="playlist-cover" src="{{item.coverImgUrl}}" />
<view class="playlist-info">
<view class="playlist-info-title">{{item.title}}</view>
<view class="playlist-info-singer">{{item.singer}}</view>
</view>
<view class="playlist-controls">
<text wx:if="{{index==playIndex}}">正在播放</text>
</view>
</view>
</scroll-view>
样式:
/* 播放列表 */
.playlist-item {
display: flex;
align-items: center;
border-bottom: 1rpx solid #333;
height: 112rpx;
}
.playlist-cover {
width: 80rpx;
height: 80rpx;
margin-left: 15rpx;
border-radius: 8rpx;
border: 1px solid #333;
}
.playlist-info {
flex: 1;
font-size: 10pt;
line-height: 38rpx;
margin-left: 20rpx;
padding-bottom: 8rpx;
}
.playlist-info-singer {
color: #888;
}
.playlist-controls {
font-size: 10pt;
margin-right: 20rpx;
color: #c25b5b;
}
5.3 实现换曲功能
pages/index/index.js
// 播放列表换曲功能
change: function(e) {
this.setMusic(e.currentTarget.dataset.index)
this.play()
}
6.bug修复
6.1 歌曲列表添加到10个以上,页面可以上下滑动
在之前的音乐推荐整个页面上市设计里加入
.content-playlist的高度为100%
以下效果图:
6.2 歌曲进度条bug:点击进度不能实时更新
原因:微信小程序版本库的原因
1.修改方式1:可以改为原来版本库2.4.0
也可以更改函数的实现
2.修改方式2:微信版本库就是原版本2.11.0
index.js修改
完整的index.js代码:
//index.js
//获取应用实例
const app = getApp()
Page({
data: {
item:0,
tab:0,
// 播放列表数据
playlist: [{
id: 1,
title: '钢琴协奏曲',
singer: '肖邦',
src: 'http://localhost:3000/1.mp3',
coverImgUrl: '/images/cover.jpg'
}, {
id: 2,
title: '奏鸣曲',
singer: '莫扎特',
src: 'http://localhost:3000/2.mp3',
coverImgUrl: '/images/cover.jpg'
}, {
id: 3,
title: '欢乐颂',
singer: '贝多芬',
src: 'http://localhost:3000/1.mp3',
coverImgUrl: '/images/cover.jpg'
}, {
id: 4,
title: '爱之梦',
singer: '李斯特',
src: 'http://localhost:3000/2.mp3',
coverImgUrl: '/images/cover.jpg'
}, {
id: 5,
title: '奏鸣曲',
singer: '莫扎特',
src: 'http://localhost:3000/2.mp3',
coverImgUrl: '/images/cover.jpg'
}, {
id: 6,
title: '欢乐颂',
singer: '贝多芬',
src: 'http://localhost:3000/1.mp3',
coverImgUrl: '/images/cover.jpg'
}, {
id: 7,
title: '爱之梦',
singer: '李斯特',
src: 'http://localhost:3000/2.mp3',
coverImgUrl: '/images/cover.jpg'
}, {
id: 8,
title: '奏鸣曲',
singer: '莫扎特',
src: 'http://localhost:3000/2.mp3',
coverImgUrl: '/images/cover.jpg'
}, {
id: 9,
title: '欢乐颂',
singer: '贝多芬',
src: 'http://localhost:3000/1.mp3',
coverImgUrl: '/images/cover.jpg'
}, {
id: 10,
title: '爱之梦10',
singer: '李斯特',
src: 'http://localhost:3000/2.mp3',
coverImgUrl: '/images/cover.jpg'
}],
state: 'paused',
playIndex: 0,
play: {
currentTime: '00:00',
duration: '00:00',
percent: 0,
title: '',
singer: '',
coverImgUrl: '/images/cover.jpg',
}
},
audioCtx:null,
onReady: function () {
this.audioCtx = wx.createInnerAudioContext()
this.setMusic(0)
this.audioCtx.onError(()=>{//es6的箭头函数
console.log('播放失败:' + this.audioCtx.src)
})
// 播放完成自动换下一曲
this.audioCtx.onEnded(()=>{//es6的箭头函数
this.next()
})
// 自动更新播放进度
this.updateTime()
},
// 格式化时间
formatTime:function(time) {
var minute = Math.floor(time / 60) % 60;
var second = Math.floor(time) % 60
return (minute < 10 ? '0' + minute : minute) + ':' + (second < 10 ? '0' + second : second)
},
updateTime: function () {
var that = this
setTimeout(() => {
this.audioCtx.currentTime
this.audioCtx.onPlay(function() {})
this.audioCtx.onTimeUpdate(() => {
that.setData({
'play.duration': that.formatTime(that.audioCtx.duration),
'play.currentTime': that.formatTime(that.audioCtx.currentTime),
'play.percent': that.audioCtx.currentTime / that.audioCtx.duration * 100
})
})
}, 300)
},
setMusic:function(index){
var music = this.data.playlist[index]
this.audioCtx.src = music.src
this.setData({
playIndex: index,
play: {
currentTime: '00:00',
duration: '00:00',
percent: 0,
title: music.title,
singer: music.singer,
coverImgUrl: music.coverImgUrl,
}
});
// this.audioCtx.play();
},
//暂停按钮
pause:function(e){
this.audioCtx.pause()
this.setData({state:'paused'})
},
//播放按钮
play:function(e){
this.audioCtx.play()
this.setData({state:'running'})
},
//下一首按钮
next:function(e){
var index = this.data.playIndex>=this.data.playlist.length-1?0:this.data.playIndex+1
this.setMusic(index)
this.play()
this.updateTime()
},
changeItem:function(e){
this.setData({item:e.target.dataset.item})
},
changeTab:function(e){
this.setData({tab:e.detail.current})
},
// 滚动条调节歌曲进度
sliderChange: function(e) {
var second = e.detail.value * this.audioCtx.duration / 100
if(this.data.state==='paused'){
this.setData({
'play.currentTime': this.formatTime(second),
'play.duration': this.formatTime(this.audioCtx.duration)
})
}
this.audioCtx.seek(second)
this.updateTime();
},
// 播放列表换曲功能
change: function(e) {
this.setMusic(e.currentTarget.dataset.index)
this.play()
this.updateTime()
}
})