小程序list分页加载组件和音频API使用

效果图
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

代码详细地址:https://github.com/fuxingkai/frankui-weapp

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,753评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,668评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,090评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,010评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,054评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,806评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,484评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,380评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,873评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,021评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,158评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,838评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,499评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,044评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,159评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,449评论 3 374
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,136评论 2 356

推荐阅读更多精彩内容