Flutter實戰 | 從 0 搭建「網易雲音樂」APP(八、我的頁面)

Flutter筆記發表於2019-11-11

本系列可能會伴隨大家很長時間,這裡我會從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(七、歌詞(二))

本篇為第八篇,在這裡我們會搭建「我的」頁面。

我的 新建歌單 歌單操作
Flutter實戰 | 從 0 搭建「網易雲音樂」APP(八、我的頁面)
Flutter實戰 | 從 0 搭建「網易雲音樂」APP(八、我的頁面)
Flutter實戰 | 從 0 搭建「網易雲音樂」APP(八、我的頁面)

0. 確認需求

還是老套路,先確認一下需求。

「我的」頁面,我這裡做的比較簡單,上面的UI(本地音樂等)目前只是用來展示用,真正的功能有如下幾點:

  1. 展示歌單(建立的歌單、收藏的歌單)
  2. 建立新歌單
  3. 對歌單進行操作

下面就開始吧。

1. 展示歌單

首先我們先想一下,整個 APP 中對於歌單操作的位置其實是非常多的(搜尋後新增歌單、推薦歌單裡新增歌單、給歌單新增歌曲等等),那麼對於這種需求,我所考慮的就是把歌單的邏輯放入頂層 Provider 中,這樣方便操作。

理清楚邏輯後,來看頁面如何展示:

Flutter實戰 | 從 0 搭建「網易雲音樂」APP(八、我的頁面)

一共分為兩塊:「建立的歌單」、「收藏的歌單」。

兩個模組的 UI 其實是一樣的,只不過分在了不同的列表中。

那麼先來看一下返回的資料是什麼樣的:

Flutter實戰 | 從 0 搭建「網易雲音樂」APP(八、我的頁面)

emmm,只返回了一個 playlist,那就說明要讓我們自己來找這兩個的區別了。

經過我一番查詢後發現,不同型別的 Creator 值是不一樣的,「我建立的歌單」裡的資料 Creator.userId 是等於我登入後個人 id 的, 所以區分的程式碼如下:

_selfCreatePlayList =
  _allPlayList.where((p) => p.creator.userId == user.account.id).toList();
_collectPlayList =
  _allPlayList.where((p) => p.creator.userId != user.account.id).toList();
複製程式碼

ok,資料有了,畫頁面就簡單多了,從圖上我們也可以看得出來,是可以展開和收回的。

這個功能首先我想到的是 ExpansionPanelList,但是他和我們的需求不太搭,包括樣式和邏輯。

那我們就自定義一個,怎麼來做到展開和收回?其實就是控制歌單列表的顯示和不顯示,所以我們應該能想到一個元件:Offstage

而且在展開/收回的時候箭頭要來回的變化,我在前面也寫過一篇文章:Flutter | 求求你們了,切換 Widget 的時候加上動畫吧,這個時候就派上用場了。

頭部元件大致程式碼如下:

Widget build(BuildContext context) {
  return Container(
    height: ScreenUtil().setWidth(80),
    child: GestureDetector(
      behavior: HitTestBehavior.translucent,
      onTap: () {
        setState(() {
          if (arrow == arrows[0])
            arrow = arrows[1];
          else
            arrow = arrows[0];
          widget.onSwitchTap();
        });
      },
      child: Row(
        children: <Widget>[
          AnimatedSwitcher(
            transitionBuilder: (child, anim) {
              return ScaleTransition(child: child, scale: anim);
            },
            duration: Duration(milliseconds: 300),
            child: Image.asset(
              arrow,
              key: ValueKey(arrow),
              width: ScreenUtil().setWidth(30),
            ),
          ),
        ],
      ),
    ),
  );
}
複製程式碼

給整行套上 GestureDetector,點選的時候切換箭頭,並且呼叫 widget.onSwitchTap() 方法來觸發回撥。

整個歌單的程式碼大致如下:

Widget _realBuildPlayList() {

  return Column(
    mainAxisSize: MainAxisSize.min,
    crossAxisAlignment: CrossAxisAlignment.start,
    children: <Widget>[
      PlaylistTitle("建立的歌單", _playListModel.selfCreatePlayList.length, () {
        setState(() {
          selfPlayListOffstage = !selfPlayListOffstage;
        });
      }, () {},xxx,
      Offstage(
        offstage: selfPlayListOffstage,
        child: _buildPlayListItem(_playListModel.selfCreatePlayList),
      ),
      PlaylistTitle(
        "收藏的歌單",
        _playListModel.collectPlayList.length,
        () {
          setState(() {
            collectPlayListOffstage = !collectPlayListOffstage;
          });
        },
        () {},
      ),
      Offstage(
        offstage: collectPlayListOffstage,
        child: _buildPlayListItem(_playListModel.collectPlayList),
      ),
    ],
  );
}
複製程式碼

在每一個頭部下面都是一個 Offstage 元件,來控制歌單列表的顯示與否,並且通過點選回撥來觸發 setState

還有一點是:「建立的歌單」中是可以新建歌單的,所以要多處理一下,控制「+」的顯示與否。

這樣就完成了整個歌單列表的分拆與顯示。

Flutter實戰 | 從 0 搭建「網易雲音樂」APP(八、我的頁面)

2. 新建歌單

新建歌單相對來說就簡單很多了。

Flutter實戰 | 從 0 搭建「網易雲音樂」APP(八、我的頁面)

就是一個彈出框,來看一下是怎麼寫的:

Widget build(BuildContext context) {
  return AlertDialog(
    title: Text(
      '新建歌單',
      style: bold16TextStyle,
    ),
    shape: RoundedRectangleBorder(
      borderRadius:
      BorderRadius.all(Radius.circular(ScreenUtil().setWidth(20)))),
    content: Theme(
      data: ThemeData(primaryColor: Colors.red),
      child: Column(
        mainAxisSize: MainAxisSize.min,
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          xxx,
        ],
      ),
    ),
    actions: <Widget>[
      FlatButton(
        onPressed: () => Navigator.of(context).pop(),
        child: Text('取消'),
        textColor: Colors.red,
      ),
      FlatButton(
        onPressed: submitCallback == null
        ? null
        : () {
          submitCallback(_editingController.text, isPrivatePlayList);
        },
        child: Text('提交'),
        textColor: Colors.red,
      ),
    ],
  );
}
複製程式碼

直接呼叫 showDialog() 方法,返回一個 AlertDialog

AlertDialog 本身就有一個 shape 欄位,可以用來控制外觀,這裡我們加上圓角就可以了。

剩下的還有一點就是「提交」按鈕的顏色問題,當我們沒有寫歌單標題的時候,「提交」按鈕要置灰,

這裡有一個小竅門就是 如果 FlatButtononPressed 為 null,那麼這個按鈕的顏色就是灰色的

所以我們使用 TextEditingController 來判斷就好了:

_editingController.addListener(() {
  if (_editingController.text.isEmpty) {
    setState(() {
      submitCallback = null;
    });
  } else {
    setState(() {
      if (submitCallback == null) {
        submitCallback = widget.submitCallback;
      }
    });
  }
});
複製程式碼

最後在呼叫介面成功之後,給歌單列表中插入一條資料就行了,但是這裡返回的時候是沒有 Creator 資訊的,我們自己新增上就ok了:

NetUtils.createPlaylist(context,
                        params: {'name': name, 'privacy': isPrivate ? '10' : null})
  .catchError((e) {
    Utils.showToast('建立失敗');
  }).then((result) {
  Utils.showToast('建立成功');
  Navigator.of(context).pop();
  _playListModel.addPlayList(result.playlist..creator = _playListModel.selfCreatePlayList[0].creator);
});
複製程式碼

3. 歌單操作

對於歌單的操作,如圖所示:

Flutter實戰 | 從 0 搭建「網易雲音樂」APP(八、我的頁面)

這裡也有區分,如果是「建立的歌單」,那麼會有「編輯歌單資訊」這一欄,如果是收藏的話,則沒有。

這裡也是簡單的使用了 showModalBottomSheet來顯示。

在點選更改歌單資訊的時候彈出:

Flutter實戰 | 從 0 搭建「網易雲音樂」APP(八、我的頁面)

這裡其實和上面新建歌單是一樣的,只不過就是改了一點樣式。

在點刪除的時候,呼叫 PlayListModel 裡的刪除方法並且通知重新整理就好了。

這樣整個「我的」頁面大致就完成了。

4. 總結

其實這一篇沒什麼好總結的,把前面寫好的東西拿來用就好了,非常簡單。

畢竟知識就是一個積累的過程,慢慢學就完了。


該專案是我本人自己在工作之餘寫的,所以進度不會很快,但是會一直寫下去。

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


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

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

Flutter實戰 | 從 0 搭建「網易雲音樂」APP(八、我的頁面)

相關文章