1. 程式人生 > >微信小程式實戰教程:模仿—網易雲音樂(一)

微信小程式實戰教程:模仿—網易雲音樂(一)

初窺
音樂截圖

todo:

  • 新增音樂到收藏(最近)列表
  • 歌詞滾動

從一個hello world開始

微信開發者工具生成 目錄如下:

.
|-- app.js
|-- app.json
|-- app.wxss
|-- pages     
|   |-- index   # 主頁
|   |   |-- index.js
|   |   |-- index.json
|   |   |-- index.wxml
|   |   `-- index.wxss
|   `-- log # 日誌頁面
|   |   |-- log.js
|   |   |-- log.json
|
| |-- log.wxml | | `-- log.wxss `-- utils # 工具 `-- util.js

大體為:
每一個page即是一個頁面檔案 ,每個頁面有一個js/wxml/wxss/json檔案 規定:描述頁面的這四個檔案必須具有相同的路徑與檔名。

全域性下同路,為公共的邏輯,樣式,配置

與html不同:用view text navigator 代替 div span a

開發者文件走馬觀花

app.json: 註冊pages window tabBar networkTimeout

元件說明

*.js: 作為邏輯層 與wxml互動 有著豐富的
網路,
媒體,
檔案,
資料快取,
位置,
裝置,
介面…的api

官方文件

*.wxml: 資料驅動的檢視層 + 微信提供了大量的元件 表單 導航 媒體 …

官方元件不夠,weui來湊

weui為小程式提供了 weui.wxcss 但大多是造官方元件的輪子

這裡精選,也算是補充兩個常用元件

對於小程式沒有DOM操作 不熟悉mvvm思想的同學 是個很好的入門

navbar
Navbar

<!-- wxml -->
<view class="weui-tab">
            <view class="weui-navbar">
                <block wx:for="{{tabs
}}
" wx:key="*this"> <view id="{{index}}" class="weui-navbar__item {{activeIndex == index ? 'weui-bar__item_on' : ''}}" bindtap="tabClick"> <view class="weui-navbar__title">{{item}}</view> </view> </block> <view class="weui-navbar__slider" style="left: {{sliderLeft}}px; transform: translateX({{sliderOffset}}px); -webkit-transform: translateX({{sliderOffset}}px);"></view> </view> <view class="weui-tab__panel"> <view class="weui-tab__content" hidden="{{activeIndex != 0}}">選項一的內容</view> <view class="weui-tab__content" hidden="{{activeIndex != 1}}">選項二的內容</view> <view class="weui-tab__content" hidden="{{activeIndex != 2}}">選項三的內容</view> </view> </view>

block渲染data裡面的四個tabs,slider為啟用tab選項時候的表現,panel為內容面板

//js
var sliderWidth = 96; // 需要設定slider的寬度,用於計算中間位置
Page({
    data: {
        tabs: ["選項一", "選項二", "選項三"],
        activeIndex: 1,
        sliderOffset: 0,
        sliderLeft: 0
    },
    onLoad: function () {
        var that = this;
        wx.getSystemInfo({
            success: function(res) {
                that.setData({
                    sliderLeft: (res.windowWidth / that.data.tabs.length - sliderWidth) / 2,
                    sliderOffset: res.windowWidth / that.data.tabs.length * that.data.activeIndex
                });
            }
        });
    },
    tabClick: function (e) {
        this.setData({
            sliderOffset: e.currentTarget.offsetLeft,
            activeIndex: e.currentTarget.id
        });
    }
});

瞭解mvvm思想的同學不難看出 通過tabs陣列渲染出來選項後每次點選獲取id 然後通過設定hidden顯示或隱藏

searchbar
Searchbar

        <view class="weui-search-bar">
            <view class="weui-search-bar__form">
                <view class="weui-search-bar__box">
                    <icon class="weui-icon-search_in-box" type="search" size="14"></icon>
                    <input type="text" class="weui-search-bar__input" placeholder="搜尋" value="{{inputVal}}" focus="{{inputShowed}}" bindinput="inputTyping" />
                    <view class="weui-icon-clear" wx:if="{{inputVal.length > 0}}" bindtap="clearInput">
                        <icon type="clear" size="14"></icon>
                    </view>
                </view>
                <label class="weui-search-bar__label" hidden="{{inputShowed}}" bindtap="showInput">
                    <icon class="weui-icon-search" type="search" size="14"></icon>
                    <view class="weui-search-bar__text">搜尋</view>
                </label>
            </view>
            <view class="weui-search-bar__cancel-btn" hidden="{{!inputShowed}}" bindtap="hideInput">取消</view>
        </view>
        <view class="weui-cells searchbar-result" wx:if="{{inputVal.length > 0}}">
            <navigator url="" class="weui-cell" hover-class="weui-cell_active">
                <view class="weui-cell__bd">
                    <view>實時搜尋文字</view>
                </view>
            </navigator>
        </view>

一個input輸入框+一個搜尋label+一個清楚內容的icon + 取消按鈕

Page({
    data: {
        inputShowed: false,
        inputVal: ""
    },
    showInput: function () {
        this.setData({
            inputShowed: true
        });
    },
    hideInput: function () {
        this.setData({
            inputVal: "",
            inputShowed: false
        });
    },
    clearInput: function () {
        this.setData({
            inputVal: ""
        });
    },
    inputTyping: function (e) {
        this.setData({
            inputVal: e.detail.value
        });
    }
});

input上面有一層label 通過Page裡面狀態的改變而操作其wxml狀態的改變

不難體會到:小程式和Vue的思想還是挺接近的

站在巨人的肩膀上–雲音樂api

在此我將他部署到leancloud上

即可線上訪問,免去煩人的本地localhost啟動,線上url

呼叫例子:

具體參考api

詳細文件

一切具備 只欠東風

生成目錄本文講解核心內容音樂的播放,讀者可自己實現其餘頁面。

.
|-- app.js
|-- app.json
|-- app.wxss
|-- common.js #公用js
|-- images #存放專案圖片
|-- style
|   |-- weui.wxss   # 引入weui樣式  萬一你自己不想寫css樣式呢
|-- pages
|   |-- find   # 發現音樂
|   |   |-- index.js
|   |   |-- index.json
|   |   |-- index.wxml
|   |   `-- index.wxss
|   |--my   # 我的音樂
|   |   |-- index.js
|   |   |-- index.json
|   |   |-- index.wxml
|   |   `-- index.wxss
|   |--now  # 正在播放
|   |   |-- index.js
|   |   |-- index.json
|   |   |-- index.wxml
|   |   `-- index.wxss
|   |--account   # 賬號
|   |   |-- index.js
|   |   |-- index.json
|   |   |-- index.wxml
|   |   `-- index.wxss
|   |-- index   # 主頁
|   |   |-- index.js
|   |   |-- index.json
|   |   |-- index.wxml
|   |   `-- index.wxss
|   `-- log # 日誌頁面
`-- utils       # 工具
    `-- util.js

請先在在app.json中註冊頁面,設定navigation,配置tabbar

{
  "pages":[
    "pages/find/index",
    "pages/my/index",
    "pages/now/index",
    "pages/account/index",
    "pages/index/index"
  ],
  "window":{
    "backgroundTextStyle":"light",
    "navigationBarBackgroundColor": "#D43C33",
    "navigationBarTitleText": "網易雲音樂",
    "navigationBarTextStyle":"white",
    "backgroundColor": "#FBFCFD"
  },
  "tabBar": {
    "backgroundColor":"#2A2C2E",
    "color": "#a7a7a7",
     "selectedColor": "#ffffff",
    "list": [{
      "iconPath":"./images/find.png",
      "selectedIconPath":"./images/find1.png",
      "pagePath":"pages/find/index",
      "text": "發現音樂"
    }, {
      "iconPath":"./images/my.png",
      "selectedIconPath":"./images/my1.png",
      "pagePath": "pages/my/index",
      "text": "我的音樂"
    }, {
      "iconPath":"./images/now.png",
      "selectedIconPath":"./images/now1.png",
      "pagePath": "pages/now/index",
      "text": "正在播放"
    }, {
      "iconPath":"./images/account.png",
      "selectedIconPath":"./images/account1.png",
      "pagePath": "pages/account/index",
      "text": "賬號"
    }]
  }
}

發現音樂
這裡寫圖片描述

佈局分為搜尋框,navbar,swiper滑動,三列,以及兩行三列構成

tips:小程式中flex佈局基本無相容性問題 ,可大膽使用

前三個可用上文提到的元件和小程式swiper元件快速完成,

對於搜尋功能

我們在搜尋input上繫結一個inputTyping事件,這樣每次鍵入完畢都可以得到結果,然後我們直接請求api

    //index.js
//獲取應用例項
// 個人網易雲音樂 ID  66919655
var app = getApp()
Page({
    data: {
        searchReault: []
    },
    //繫結事件
    inputTyping: function (e) {
        let that = this
        console.log(e.detail)
        this.setData({
            inputVal: e.detail.value
        });
        wx.request({
            url: 'http://neteasemusic.leanapp.cn/search',
            data: {
                keywords: e.detail.value
            },
            method: 'GET',
            success: function (res) {
                let temp = []
                if(!res.data.result.songs){
                    return ;
                }
                //遍歷資料
                res.data.result.songs.forEach((song, index) => {
                    temp.push({
                        id: song.id,
                        name: song.name,
                        mp3Url: song.mp3Url,
                        picUrl: song.album.picUrl,
                        singer: song.artists[0].name
                    })
                    //設定資料
                   that.setData({
                        searchReault: temp
                    })
                })
                // 存入搜尋的結果進快取
                wx.setStorage({
                    key:"searchReault",
                    data:temp
                })
            }
        })
    }
});

data裡面的searchReault陣列存入搜尋結果,發起一個wx.request,用GET方式傳入引數,組織好json後設置data,然後將搜尋結果存入本地快取

wxml渲染searchReault:
這裡寫圖片描述

並且自定義data屬性,navigator的開啟方式為tab切換open-type=”switchTab” ,繫結一個tonow事件bindtap=”tonow”

<block wx:for="{{searchReault}}" wx:key="item" style="overflow-y: scroll;">
    <navigator url="../now/index" class="weui-cell" hover-class="weui-cell_active"
       data-id="{{item.id}}" data-name="{{item.name}}" data-songUrl="{{item.mp3Url}}" data-picUrl="{{item.picUrl}}" 
       data-singer="{{item.singer}}"
       open-type="switchTab" bindtap="tonow">
       <view class="weui-cell__bd">
          <view class="song-name">{{item.name}}
               <text class="song-singer">{{item.singer}}</text>
            </view>
         </view>
       </navigator>
</block>

在tonow事件中,獲取當前的歌曲

    tonow: function (event) {
        let songData = {
            id: event.currentTarget.dataset.id,
            name: event.currentTarget.dataset.name,
            mp3Url: event.currentTarget.dataset.songurl,
            picUrl: event.currentTarget.dataset.picurl,
            singer: event.currentTarget.dataset.singer
        }
        // 將當前點選的歌曲儲存在快取中
        wx.setStorageSync('clickdata', songData)
        wx.switchTab({
            url: '../now/index'
        })
    }

正在播放
這裡寫圖片描述

佈局:歌曲封面,滑動條上下為操作按鈕,
封面在採用圓角,rotate,transition既可以
滑動快進:在滑動條上繫結事件 slider3change

//滑動 歌曲快進
function sliderToseek(e, cb) {
  wx.getBackgroundAudioPlayerState({
    success: function (res) {
      var dataUrl = res.dataUrl
      var duration = res.duration
      let val = e.detail.value
      let cal = val * duration / 100
      cb && cb(dataUrl, cal);
    }
  })
}
//分隔 在page中呼叫
  slider3change: function (e) {
    sliderToseek(e, function (dataUrl, cal) {
      wx.playBackgroundAudio({
        dataUrl: dataUrl
      })
      wx.seekBackgroundAudio({
        position: cal
      })
    })
  },

一個自定義的sliderToseek函式:

引數e 可以獲取滑動的值,獲取正在播放的音樂資訊成功後執行回撥函式1->播放 回撥函式2->跳到指定位置;
拆分歌詞:
在api中得到的歌詞:”[00:00.00] 作曲 : 黃家駒 [00:01.00] 作詞 : 黃家駒 [00:18.580]今天我 寒夜裡看雪飄過 [00:25.050]懷著冷卻了的心窩漂遠方 [00:30.990]風雨裡追趕 ”
在page外定義函式:

以]劃分陣列 第二部分就是歌詞內容:item.split(‘]’)[1] 第一部分即為對應的時間:item.split(‘]’)[0]

// 獲取歌詞
function getlyric(id,cb) {
  console.log('id:',id)
  let url = `http://neteasemusic.leanapp.cn/lyric`
  wx.request({
    url: url,
    data: {
      id: id
    },
    method: 'GET', // OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, CONNECT
    // header: {}, // 設定請求的 header
    success: function (res) {
      // success

      if (!res.data.lrc.lyric) return false;

      let lyric = res.data.lrc.lyric

      let timearr = lyric.split('[')
      let obj = {}
      let lyricArr=[]
      // seek 為鍵  歌詞為value
      timearr.forEach((item) => {
        let key = parseInt(item.split(']')[0].split(':')[0]) * 60 + parseInt(item.split(']')[0].split(':')[1])
        let val = item.split(']')[1]

        obj[key] = val
      })
      for(let key in obj){
        // obj[key] = obj[key].split('\n')[0]
        lyricArr.push(obj[key])
      }
      cb&&cb(obj,lyricArr)
    },
    fail: function (res) {
      // fail
    },
    complete: function (res) {
      // complete
    }
  })
}

在page中呼叫:傳入歌曲ID(上文我們已經存入快取,在快取中取出即可),和將其設定在data的回撥

  getlyric(id,function(data, lyricArr){
           that.setData({
             lyricobj:data,
             lyricArr:lyricArr
           })
         })