Flutter實戰 | 從 0 搭建「網易雲音樂」APP(九、搜尋頁面、底部播放控制欄)

Flutter筆記發表於2019-11-25

本系列可能會伴隨大家很長時間,這裡我會從0開始搭建一個「網易雲音樂」的APP出來。

下面是該APP 功能的思維導圖:

Flutter實戰 | 從 0 搭建「網易雲音樂」APP(九、搜尋頁面、底部播放控制欄)

前期回顧:

  1. Flutter實戰 | 從 0 搭建「網易雲音樂」APP(一、建立專案、新增外掛、通用程式碼)
  2. Flutter實戰 | 從 0 搭建「網易雲音樂」APP(二、Splash Page、登入頁、發現頁)
  3. Flutter實戰 | 從 0 搭建「網易雲音樂」APP(三、每日推薦、推薦歌單)
  4. Flutter實戰 | 從 0 搭建「網易雲音樂」APP(四、排行榜、播放頁面)
  5. Flutter實戰 | 從 0 搭建「網易雲音樂」APP(五、播放功能邏輯)
  6. Flutter實戰 | 從 0 搭建「網易雲音樂」APP(六、歌詞(一))
  7. Flutter實戰 | 從 0 搭建「網易雲音樂」APP(七、歌詞(二))
  8. Flutter實戰 | 從 0 搭建「網易雲音樂」APP(八、我的頁面)

本篇為第九篇,在這裡我們會搭建「搜尋頁面、底部播放控制欄」。

搜尋頁 搜尋結果頁
Flutter實戰 | 從 0 搭建「網易雲音樂」APP(九、搜尋頁面、底部播放控制欄)
Flutter實戰 | 從 0 搭建「網易雲音樂」APP(九、搜尋頁面、底部播放控制欄)

0. 寫在前面

上一週一直沒更新程式碼與文章,是因為公司公費去廈門旅遊來著,所以好好放鬆了一週。

這周開始恢復程式碼與文章的更新,最近收到很多童鞋反饋說比較卡,

我建議加我個人微信「17610912320」,來探討一下是哪個地方,具體在哪裡卡。

也歡迎 PR,讓我們一起為這個專案添磚加瓦!

1. 搜尋頁

話不多說,接著就來我們的搜尋頁,先看一下圖,然後梳理一下需求:

  1. 歷史記錄(無歷史記錄的時候不顯示)
  2. 熱搜榜

1. 歷史記錄

先來搞歷史記錄,歷史記錄肯定是要存在我們本地的,那就需要用到 shared_preferences了。

這方面的就不多說了,看看文件,都懂的。

然後是 UI,不知道有沒有童鞋記得我以前寫過一篇文章:Flutter Wrap & Chip。

Flutter實戰 | 從 0 搭建「網易雲音樂」APP(九、搜尋頁面、底部播放控制欄)

在這裡完全就能用得上,而且不需要那麼多花裡胡哨的,只有一個文字就行了。

來看一下如何定義:

Wrap(
  spacing: ScreenUtil().setWidth(20),
  children: historySearchList
  .map((v) => GestureDetector(
    onTap: () {
      searchText = v;
      _search();
    },
    child: Chip(
      label: Text(
        v,
        style: common14TextStyle,
      ),
      backgroundColor: Color(0xFFf2f2f2),
    ),
  ))
  .toList(),
),
複製程式碼

邏輯如下:

  1. Wrap 包裹住 chip
  2. 並設定每個 chip 的間隔為 20
  3. 然後根據 historySearchList 的資料來返回 chip
  4. 最後改變一下 chip 的背景顏色即可

但是,不要忘了我們還有「清空歷史記錄」的功能,頁面如下:

Flutter實戰 | 從 0 搭建「網易雲音樂」APP(九、搜尋頁面、底部播放控制欄)

在點選 小垃圾桶的時候彈出,這個也很簡單:

IconButton(
  icon: Icon(
    Icons.delete_outline,
    color: Colors.grey,
  ),
  onPressed: () {
    showDialog(
      context: context,
      builder: (context) {
        return AlertDialog(
          content: Text(
            "確定清空全部歷史記錄?",
            style: common14GrayTextStyle,
          ),
          actions: <Widget>[
            FlatButton(
              onPressed: () {
                Navigator.of(context).pop();
              },
              child: Text('取消'),
              textColor: Colors.red,
            ),
            FlatButton(
              onPressed: () {
                setState(() {
                  historySearchList.clear();
                  Application.sp.remove("search_history");
                });
                Navigator.of(context).pop();
              },
              child: Text('清空'),
              textColor: Colors.red,
            ),
          ],
        );
      });
  },
)
複製程式碼

在點選這個 IconButton 的時候呼叫 showDialog 方法,然後根據點選的按鈕做相應的操作即可。

2. 熱搜榜

熱搜榜這個就更簡單了,直接就是一個 ListView

剛開始看到這個佈局的時候想到的是 ListTile,但是間距什麼的不好控制,所以只能自己寫了。

Text(
  curData.searchWord,
  style: index < 3
  ? w500_16TextStyle
  : common16TextStyle,
),
複製程式碼

最前面排名字型顏色的控制直接用 index 就ok,後面的也沒什麼好說的,直接擼就完了。

2. 搜尋結果頁

搜尋結果頁其實是和「搜尋頁」在一起的,由搜尋狀態控制:

_isSearching ? _buildSearchingPage() : _buildUnSearchingPage();
複製程式碼

搜尋結果頁分 7 頁:

  1. 綜合
  2. 單曲
  3. 專輯
  4. 歌手
  5. 歌單
  6. 使用者
  7. 視訊

其中「綜合」頁面包含了剩下的每一頁的UI,所以我們在寫「綜合」頁面的時候每一個控制元件都封裝一下。

具體UI上面就不說了,有一個需要注意的地方就是:

在綜合頁面需要跳轉別的頁面,這裡我使用的是在建立「綜合」頁面的時候傳入點選事件,然後在點選的時候呼叫:

SearchMultipleResultPage(this.keywords,
                         {@required this.onTapMore, @required this.onTapSimText});
複製程式碼

最後在主頁面用 controller 來控制就好了。

3. 底部播放控制欄

接到很多人反饋說找不到當前聽的是哪首歌?,

當時覺得這個東西比較簡單,就沒有寫,昨天花了一點時間給寫完了。

我為什麼說他簡單呢。。。不是我裝x,是真的簡單,聽我說!

我們在編寫播放頁面的時候就已經把關於歌曲播放功能的 model:PlaySongsModel 給寫好了,所有的功能都在這裡,所以我們想要寫一個「播放控制欄」真的是分分鐘搞定。

so,控制欄邏輯如下:

  1. 在播放的時候儲存當前歌曲列表和當前 index 到本地
  2. 在重新開啟 APP 的時候點選播放可以播放上次播放的歌曲

第一個儲存,很簡單了,使用 shared_preferences

// 儲存當前歌曲到本地
void saveCurSong(){
  Application.sp.remove('playing_songs');
  Application.sp.setStringList('playing_songs', _songs.map((s) => FluroConvertUtils.object2string(s)).toList());
  Application.sp.setInt('playing_index', curIndex);
}
複製程式碼

第二個取出資料:

// 判斷是否有儲存的歌曲列表
if(Application.sp.containsKey('playing_songs')){
  List<String> songs = Application.sp.getStringList('playing_songs');
  playSongsModel.addSongs(songs.map((s) => Song.fromJson(FluroConvertUtils.string2map(s))).toList());
  int index = Application.sp.getInt('playing_index');
  playSongsModel.curIndex = index;
}
複製程式碼

關於UI更新什麼的根本不需要考慮,Provider 直接搞定了,什麼邏輯都不用寫。

暫停播放之類的,點選事件如下:

GestureDetector(
  onTap: (){
    if(model.curState == null){
      model.play();
    }else {
      model.togglePlay();
    }
  }
)
複製程式碼

當我們重新開啟APP的時候,這個時候 curState 是 null,這個時候我們呼叫 恢復/暫停 方法是沒有效果的,所以我們要先呼叫 play() 方法。

還有一個地方需要注意,在 iPhone 上有些是有「控制條」的,所以我們要加上這個高度:

height: ScreenUtil().setWidth(110) + Application.bottomBarHeight
複製程式碼

寫好以後在需要使用的頁面加上就行了。

4. 總結

感覺大部分功能已經完成,但是看了一下思維導圖。。。慢慢來吧!

大家如果有好的建議的話,歡迎提 issue,我會在第一時間回覆。

該系列文章程式碼已傳至 GitHub:github.com/wanglu1209/…

另我個人建立了一個「Flutter 交流群」,可以新增我個人微信 「17610912320」來入群。

Flutter實戰 | 從 0 搭建「網易雲音樂」APP(九、搜尋頁面、底部播放控制欄)

相關文章