Flutter實戰 | 從 0 搭建「網易雲音樂」APP(8、個人頁面)

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

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

前期回顧:微信

  1. Flutter實戰 | 從 0 搭建「網易雲音樂」APP(1、建立項目、添加插件、通用代碼)
  2. Flutter實戰 | 從 0 搭建「網易雲音樂」APP(2、Splash Page、登陸頁、發現頁)
  3. Flutter實戰 | 從 0 搭建「網易雲音樂」APP(3、每日推薦、推薦歌單)
  4. Flutter實戰 | 從 0 搭建「網易雲音樂」APP(4、排行榜、播放頁面)
  5. Flutter實戰 | 從 0 搭建「網易雲音樂」APP(5、播放功能邏輯)
  6. Flutter實戰 | 從 0 搭建「網易雲音樂」APP(6、歌詞(一))
  7. Flutter實戰 | 從 0 搭建「網易雲音樂」APP(7、歌詞(二))

本篇爲第八篇,在這裏咱們會搭建「個人」頁面。ide

個人 新建歌單 歌單操做

0. 確認需求

仍是老套路,先確認一下需求。動畫

「個人」頁面,我這裏作的比較簡單,上面的UI(本地音樂等)目前只是用來展現用,真正的功能有以下幾點:ui

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

下面就開始吧。spa

1. 展現歌單

首先咱們先想一下,整個 APP 中對於歌單操做的位置實際上是很是多的(搜索後添加歌單、推薦歌單裏添加歌單、給歌單添加歌曲等等),那麼對於這種需求,我所考慮的就是把歌單的邏輯放入頂層 Provider 中,這樣方便操做。插件

理清楚邏輯後,來看頁面如何展現:3d

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

兩個模塊的 UI 實際上是同樣的,只不過度在了不一樣的列表中。

那麼先來看一下返回的數據是什麼樣的:

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

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

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

2. 新建歌單

新建歌單相對來講就簡單不少了。

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

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. 歌單操做

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

這裏也有區分,若是是「建立的歌單」,那麼會有「編輯歌單信息」這一欄,若是是收藏的話,則沒有。

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

在點擊更改歌單信息的時候彈出:

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

在點刪除的時候,調用 PlayListModel 裏的刪除方法而且通知刷新就行了。

這樣整個「個人」頁面大體就完成了。

4. 總結

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

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


該項目是我本人本身在工做之餘寫的,因此進度不會很快,可是會一直寫下去。

你們若是有好的建議的話,歡迎提 issue,我會在第一時間回覆。


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

另我我的建立了一個「Flutter 交流羣」,能夠添加我我的微信 「17610912320」來入羣。

相關文章
相關標籤/搜索