本系列可能會伴隨大家很長時間,這裡我會從0開始搭建一個「網易雲音樂」的APP出來。
下面是該APP 功能的思維導圖:
前期回顧:
本篇為第八篇,在這裡我們會搭建「我的」頁面。
我的 | 新建歌單 | 歌單操作 |
---|---|---|
0. 確認需求
還是老套路,先確認一下需求。
「我的」頁面,我這裡做的比較簡單,上面的UI(本地音樂等)目前只是用來展示用,真正的功能有如下幾點:
- 展示歌單(建立的歌單、收藏的歌單)
- 建立新歌單
- 對歌單進行操作
下面就開始吧。
1. 展示歌單
首先我們先想一下,整個 APP 中對於歌單操作的位置其實是非常多的(搜尋後新增歌單、推薦歌單裡新增歌單、給歌單新增歌曲等等),那麼對於這種需求,我所考慮的就是把歌單的邏輯放入頂層 Provider
中,這樣方便操作。
理清楚邏輯後,來看頁面如何展示:
一共分為兩塊:「建立的歌單」、「收藏的歌單」。
兩個模組的 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
欄位,可以用來控制外觀,這裡我們加上圓角就可以了。
剩下的還有一點就是「提交」按鈕的顏色問題,當我們沒有寫歌單標題的時候,「提交」按鈕要置灰,
這裡有一個小竅門就是 如果 FlatButton
的 onPressed
為 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」來入群。