效果图
2.gif场景
使用小程序的时候经常会遇到一些分页加载展示的功能,都是list列表展示,只不过是里面的内容不同;然后每次有新的list都要把item遍历,然后又得加上底部的加载更多,加载完成等copy一份,操作十分繁琐;由此笔者进行了简单的封装,具体内容如下。
编写一个common-list组件
common-list.js:主要向外部提供两个属性list和loadMore;list用来存放需要展示的列表数组;loadMore用来控制页面加载更多的展示和关闭,通过给loadMore的enableLoadMore和haseMore来控制;然后向外部提供一个回调方法onClickListItem,通过该方法把item的所有点击事件回调到调用该common-list组件的组件或者页面,具体回调的值待会讲解item组件的时候再来分析;
"use strict";
Component({
properties: {
list: {
type: Array,
value: [],
observer: "onListChange"
},
loadMore: {
type: Object,
value: {
enableLoadMore: false,
hasMore: true,
},
observer: "onLoadMoreChange"
},
},
data: {
enableLoadMore: false,
hasMore: true,
},
methods: {
onListChange: function onListChange() {
this.setData({
list: this.data.list,
});
},
/**
* list里面item内部点击
* 可通过e.detail.clickEvent.target知道是Item内部哪个子控件自身点击事件
* @param {*} e
*/
onClickItem: function onClickItem(e) {
// console.log(e);
this.triggerEvent('onClickListItem', e.detail);
},
/**
* 加载更多控件更新
*/
onLoadMoreChange: function onLoadMoreChange() {
this.setData({
enableLoadMore: this.data.loadMore.enableLoadMore,
hasMore: this.data.loadMore.hasMore,
});
},
}
});
common-list.json:componentGenerics用来存在抽象节点的声明,比如我们这里声明了一个抽象节点list-item,list-item在common-list.wxml中代替外部传进来的item,有些类似slot;外部应用common-list的时候通过 generic:list-item="record-item" 给list-item赋值,record-item这里是自定义的item组件;
该快理解模糊的话可以查看微信官方对抽象节点的描写文档:https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/generics.html
{
"component": true,
"componentGenerics": {
"list-item": true
}
}
//引用
<common-list list="{{recordList}}" loadMore="{{loadMore}}" generic:list-item="record-item" bindonClickListItem="onClickListItem" />
common-list.wxml:主要就是遍历我们的item和添加加载更多样式,这里遍历的item就是我们的抽象节点list-item,然后把相应的值和索引传进去,再绑定一个队对外部的点击事件
<template name="list-footer-view">
<view wx:if="{{ enableLoadMore }}" class="loadmore_parent">
<view class="loadmore" wx:if="{{ hasMore ? hasMore : false }}">
<image class="loading" src="../../images/loading.svg"></image>
</view>
<view wx:else class="loadmore-end">
<view class="loadmore-tip">
<view class="loadmore-end-line" />
<view class="loadmore-end-tip">
{{(listNoMoreDataTip ? listNoMoreDataTip : '没有更多内容了')}}
</view>
<view class="loadmore-end-line" />
</view>
</view>
</view>
</template>
<view class="container">
<block wx:if="{{list !== null && list.length !== 0}}">
<block wx:for="{{list}}" wx:for-item="item" wx:key="{{index}}">
<list-item item="{{item}}" index="{{index}}" bindonClickItem="onClickItem" />
</block>
<template is="list-footer-view" data="{{enableLoadMore:enableLoadMore,hasMore:hasMore}}" />
</block>
</view>
编写一个record-item组件
该组件就是我们的list里面的item;这里我这个组件内容可能相对复杂,因为包含了音频的使用;大家使用的时候只需要根据自己需要抽取就好;该组件提供了两个属性item和index,item主要用来存放item需要展示的数据,index主要用来几张item在list的索引;对外部提供一个onClickItem回调方法,该方法会回调以下基本数据clickEvent里面的target标识该点击事件是item哪个控件的点击事件,index代表item的索引。
{
clickEvent:{target: "play"},
index:0
}
其他数据大家根据自己需要拓展
record-item.js
'use strict';
Component({
properties: {
item: {
type: Object,
// value: {
// isPlay:false,
// name:'王先生',
// recordSrc:'http://ws.stream.qqmusic.qq.com/M500001VfvsJ21xFqb.mp3?guid=ffffffff82def4af4b12b3cd9337d5e7&uin=346897220&vkey=6292F51E1E384E06DCBDC9AB7C49FD713D632D313AC4858BACB8DDD29067D3C601481D36E62053BF8DFEAF74C0A5CCFADD6471160CAF3E6A&fromtag=46',
// phone:'13168849257',
// type:'安居客来电',
// rPersonnel:'万兴',
// duration:'30:22',
// createTime:'2019-11-11 22:22:12',
// curTime:'30:22',
// }
observer: 'onDataChange'
},
index: {
type: Number,
value: "",
}
},
data: {
curTime: '00:00',
sliderValue: '',
pause:false,
},
methods: {
onDataChange: function onDataChange() {
this.setData({
item: this.data.item,
});
},
/**
* 点击播放
* @param {*} e
*/
onClickPlay: function onClickPlay(e) {
let data = e.currentTarget.dataset.item;
if (data.isPlay) {
getApp().getAudioContext().pause();
this.setData({
pause: true,
})
}else{
let _this = this;
getApp().getAudioContext().src = data.recordSrc;
getApp().getAudioContext().loop = true;
getApp().getAudioContext().onPlay(function () {});
getApp().getAudioContext().onTimeUpdate(function () {
_this.setData({
sliderValue: (getApp().getAudioContext().currentTime / getApp().getAudioContext().duration) * 100,
curTime: _this.formatTime(getApp().getAudioContext().currentTime),
})
})
if(!_this.data.pause){
getApp().getAudioContext().stop();
}
_this.setData({
pause: false,
})
getApp().getAudioContext().play()
}
data.index = this.data.index;
let clickEvent = {};
clickEvent.target = 'play';
data.clickEvent = clickEvent;
this.triggerEvent('onClickItem', data);
},
/**
* 后退
* @param {*} e
*/
onClickPre: function onClickPre(e) {
let _this = this;
let currentTime = getApp().getAudioContext().currentTime - 10;
if (currentTime < 0) {
currentTime = 0;
}
_this.setData({
sliderValue: (currentTime / getApp().getAudioContext().duration) * 100,
curTime: _this.formatTime(currentTime)
})
getApp().getAudioContext().seek(currentTime);//通过滑块控制音频进度
},
/**
* 前进
* @param {*} e
*/
onClickNext: function onClickNext(e) {
let _this = this;
let currentTime = getApp().getAudioContext().currentTime + 10;
let duration = getApp().getAudioContext().duration;
if (currentTime > duration) {
currentTime = duration;
}
_this.setData({
sliderValue: (currentTime / duration) * 100,
curTime: _this.formatTime(currentTime)
})
getApp().getAudioContext().seek(currentTime);//通过滑块控制音频进度
},
/**
* 监听slider
*/
onRecordSliderListener: function onRecordSliderListener(e) {
let _this = this;
var per = e.detail.value / 100;
var long = per * getApp().getAudioContext().duration;
this.setData({
curTime: _this.formatTime(long)
})
getApp().getAudioContext().seek(long);//通过滑块控制音频进度
},
/**
* 时间秒数格式化
* @param s 时间戳(单位:秒)
* @returns {*} 格式化后的时分秒
*/
formatTime: function formatTime(s) {
var t = '';
s = parseInt(s);
if (s > -1) {
var hour = Math.floor(s / 3600);
var min = Math.floor(s / 60) % 60;
var sec = s % 60;
if (hour < 10) {
// t = '0' + hour + ":";
} else {
t = hour + ":";
}
if (min < 10) { t += "0"; }
t += min + ":";
if (sec < 10) { t += "0"; }
t += sec.toFixed(0);
}
return t;
}
}
});
record-item.wxml:跟我们平时定义的item差不多,唯一有差异就是,需要回调到外部的点击事件需要绑定onClickItem方法
<view class="container">
<view class="content_container">
<view class="content">
<view class="title">{{item.phone+'('+item.name+')'}}</view>
<view class="des">{{item.type + ' | 接电员 : ' + item.rPersonnel + ' | 通话时间:' + item.duration}} </view>
<view class="time">{{item.creatTime}}</view>
</view>
<image class="play_icon" data-item="{{item}}" src="{{item.isPlay?'/images/ic_record_pause.png':'/images/ic_record_play.png'}}" bindtap="onClickPlay" />
</view>
<view class="play_container" wx:if="{{item.isPlay}}">
<view class="cur_time">{{curTime}}</view>
<slider class="record_slider" bindchange="onRecordSliderListener" value="{{sliderValue}}" block-size="12" block-color="#00000000" activeColor="#DDDDDDFF" backgroundColor="#EEEEEEFF"/>
<image class="pre" src="{{'/images/ic_record_pre.png'}}" bindtap="onClickPre" />
<image class="next" src="{{'/images/ic_record_next.png'}}" bindtap="onClickNext" />
</view>
<view class="line"></view>
</view>
音频API使用
微信官方文档:https://developers.weixin.qq.com/miniprogram/dev/api/InnerAudioContext.html
需要注意的是使用onTimeUpdate监听进度之前,要设置onPlay播放监听,不然可能监听不到进度。
由于这里我的音频播放是整个列表任何一个item都可以播放,所以我在app.js里面声明了一个音频上下文,然后在item内部直接调用该音频上下文;
/**
* 音频播放上下文
*/
audioContext: null,
/**
* 获取当前音频上下文
*/
getAudioContext: function getAudioContext() {
if(this.audioContext==null){
this.audioContext = wx.createInnerAudioContext();
}
return this.audioContext;
},
/**
* 摧毁当前音频上下文
*/
destroyAudioContext: function destroyAudioContext() {
if(this.audioContext!=null){
this.audioContext.stop();
this.audioContext.destroy
}
},
整个列表音频播放,暂停,快进,后退流程思路如下:
1,首先item外部参数要含有一个播放和暂停参数isPlay,用来标识item当前是播放还是停止状态;
2,当点击播放的时候,通过 getApp().getAudioContext()获得音频上下文;为音频上下文设置src播放地址,设置loop为循环播放,设置onPlay和onTimeUpdate回调;然后判断该音频是否点击过暂停,如果点击过需要先调用stop方法,为了避免列表存在含有相同src的时候,点击播放音频不能从头开始播放;然后再调用播放方法play;最后通过回调onClickItem方法,更新列表样式;
3,当点击暂停的时候,直接调用pause方法就好;
4,当点击快进和后退的时候使用seek方法就好;
详细使用可以看record-item.js
网友评论