Flutter實戰 | 從 0 搭建「網易雲音樂」APP(3、每日推薦、推薦歌單)

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

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

前期回顧:微信

每日推薦 推薦歌單
每日推薦
推薦歌單

本篇爲第三篇,在這裏咱們會搭建每日推薦、推薦歌單。網絡

UI 分析

首先仍是再來看一下「每日推薦」的UI效果:less

看到這個效果,有經驗的同窗可能直接就會喊出:CustomScrollView!!ide

沒錯,當前頁一共分爲三部分:flex

  1. SliverAppBar
  2. SliverAppBar 的 bottom
  3. SliverList

整個頁面就是用 CustomScrollView 來作的,可是有一點不一樣:ui

平時咱們在使用 SliverAppBar 作這種摺疊效果的時候,摺疊起來是會變成主題色的,this

因此這裏我找了別人寫好的一個組件:FlexibleDetailBar,用它之後的效果就是上面圖片那樣。spa

滑上去的時候「播放所有」那一行還停留在上方,是使用了 SliverAppBar 的 bottom參數。

這樣一個頁面的UI其實就分析完了。

然而!咱們回過頭看一下兩個頁面的UI,是否是感受很是類似!咱們來捋一下。

  1. 標題,不用多說,是同樣的
  2. SliverAppBar 展開狀態時的內容,是否是能夠由外部傳入
  3. 播放所有,也是同樣的,後面有個「共多少首」,也能夠由調用者傳入
  4. 最下面的歌單,是否是也能夠封裝出一個組件來
  5. 忘記標了,還有一個是SliverAppBar展開時的模糊背景,也能夠由調用者傳入

so,咱們從上往下來封裝。

先封裝SliverAppBar 的 bottom

肯定一下需求,看看須要傳入哪些參數:

  1. count:共多少首歌
  2. tail:尾部控件
  3. onTap:點擊播放所有時的回調

bottom 須要的是一個 PreferredSizeWidget,因此咱們的代碼是這樣:

class MusicListHeader extends StatelessWidget implements PreferredSizeWidget {
  MusicListHeader({this.count, this.tail, this.onTap});
  final int count;
  final Widget tail;
  final VoidCallback onTap;

  @override
  Widget build(BuildContext context) {
    return ClipRRect(
      borderRadius: BorderRadius.vertical(
          top: Radius.circular(ScreenUtil().setWidth(30))),
      child: Container(
        color: Colors.white,
        child: InkWell(
          onTap: onTap,
          child: SizedBox.fromSize(
            size: preferredSize,
            child: Row(
              children: <Widget>[
                HEmptyView(20),
                Icon(
                  Icons.play_circle_outline,
                  size: ScreenUtil().setWidth(50),
                ),
                HEmptyView(10),
                Padding(
                  padding: const EdgeInsets.only(top: 3.0),
                  child: Text(
                    "播放所有",
                    style: mCommonTextStyle,
                  ),
                ),
                HEmptyView(5),
                Padding(
                  padding: const EdgeInsets.only(top: 3.0),
                  child: count == null
                      ? Container()
                      : Text(
                    "(共$count首)",
                    style: smallGrayTextStyle,
                  ),
                ),
                Spacer(),
                tail ?? Container(),
              ],
            ),
          ),
        ),
      ),
    );
  }

  @override
  Size get preferredSize => Size.fromHeight(ScreenUtil().setWidth(100));
}
複製代碼

而後封裝 SliverAppBar

仍是先肯定一下需求,看看須要傳入什麼:

  1. 要傳入一個背景還模糊
  2. 傳入title
  3. 傳入展開時的高度
  4. 播放次數
  5. 播放所有的點擊回調

肯定好就以後,代碼以下:

class PlayListAppBarWidget extends StatelessWidget {
  final double expandedHeight;
  final Widget content;
  final String backgroundImg;
  final String title;
  final double sigma;
  final VoidCallback playOnTap;
  final int count;

  PlayListAppBarWidget({
    @required this.expandedHeight,
    @required this.content,
    @required this.title,
    @required this.backgroundImg,
    this.sigma = 5,
    this.playOnTap,
    this.count,
  });

  @override
  Widget build(BuildContext context) {
    return SliverAppBar(
      centerTitle: true,
      expandedHeight: expandedHeight,
      pinned: true,
      elevation: 0,
      brightness: Brightness.dark,
      iconTheme: IconThemeData(color: Colors.white),
      title: Text(
        title,
        style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
      ),
      bottom: MusicListHeader(
        onTap: playOnTap,
        count: count,
      ),
      flexibleSpace: FlexibleDetailBar(
        content: content,
        background: Stack(
          children: <Widget>[
            backgroundImg.startsWith('http')
                ? Image.network(
                    backgroundImg,
                    width: double.infinity,
                    height: double.infinity,
                    fit: BoxFit.cover,
                  )
                : Image.asset(backgroundImg),
            BackdropFilter(
              filter: ImageFilter.blur(
                sigmaY: sigma,
                sigmaX: sigma,
              ),
              child: Container(
                color: Colors.black38,
                width: double.infinity,
                height: double.infinity,
              ),
            ),
          ],
        ),
      ),
    );
  }
}
複製代碼

這裏有兩個地方須要注意一下:

  1. 外部傳入背景圖片時,有多是本地文件,也有多是網絡圖片,因此咱們直接在這裏判斷 startsWith('http')
  2. 模糊背景圖片時,加一個 Colors.black38,這樣省的後續有白色圖片所致使文字看不清。

最後封裝歌曲列表的item

這個item就比較簡單了,傳入一個實體類,根據參數來填值就行了,大體代碼以下:

class WidgetMusicListItem extends StatelessWidget {
  final MusicData _data;

  WidgetMusicListItem(this._data);

  @override
  Widget build(BuildContext context) {
    return Container(
      width: Application.screenWidth,
      height: ScreenUtil().setWidth(120),
      child: Row(
        mainAxisSize: MainAxisSize.min,
        crossAxisAlignment: CrossAxisAlignment.center,
        children: <Widget>[
          // xxx
        ],
      ),
    );
  }
}

複製代碼

總結

通過前兩次基礎頁面的搭建,咱們後續再來寫頁面的時候能夠說是簡單了百倍不止。

並且根本不用管網絡請求之類的邏輯,只需管好咱們的頁面就行了。

而在寫UI時,也必定要多看,多想,這個能不能封裝出來?那個能不能提取?

這樣之後再開發的話,真的是很是簡單。

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

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

相關文章
相關標籤/搜索