Flutter 仿生微信(3):發現頁面搭建

1. 源碼下載

喜歡的話,別忘了點個關注,還有給個 Github 右上角的小星星吧。

源碼下載地址,代碼會根據不斷更新。

Flutter 仿生微信(目錄)
上一篇:Flutter 仿生微信(2):Pages 創建
下一篇:Flutter 仿生微信(4):我的頁面搭建

2. 思路

發現頁是幾個頁面中最簡單的,靜態頁面,所以先從這里入手。

2.1 使用 ListView 完成

本來是準備使用 Scaffold 的 appBar 來實現導航條,然后 FMFind.dart 中使用最方便的 ListView 即可完成。但是考慮到不同頁面導航條不同,狀態管理比較麻煩,所以放棄這個方案。

2.2 使用 CustomScrollView 完成

  1. CustomScrollView 可以自定義導航條,看了一下和預期比較接近。
  2. 然后就是結構了,頁面布局可以很清晰的看到,是一個常用的功能條和分割條,并且可以編輯展示在這里的功能條數量。
  3. 使用 Models 管理,只需要針對 Models 進行順序排列即可,直觀便捷。比如我們先放個 Model(朋友圈),在放一個 Model(分隔行),再放一個 Model(掃一掃),就完成了 朋友圈,分隔,掃一掃這種功能。

Models -> [model1, model2..], [model_divider], [model3, model4..].. -> 分類布局完成后 -> <Widget>[Widget(model1), Widget(model2), Widget(model_divider), Widget(model3)....] -> 刷新頁面。

FM Weixin Find.png
FM Weixin Find.gif

3. 示例代碼

FMFind.dart

import 'package:FMWeixinApp/find/item/FMFindItem.dart';
import 'package:FMWeixinApp/find/model/FMFindModel.dart';
import 'package:FMWeixinApp/tools/FMColor.dart';
import 'package:flutter/material.dart';

class FMFind extends StatefulWidget {
  @override
  FMFindState createState()=> FMFindState();
}

class FMFindState extends State <FMFind> {

  List<Widget> _slivers = [];

  List <FMFindModel> _models = [];

  List <FMFindMenuModel> _menuModels = [];

  @override
  void initState() {
    // TODO: implement initState
    _models.clear();

    _initModels();
    _initMenuModels(_models);
    _initSliversWithModels(_models);
  }

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return CustomScrollView(
      slivers: _slivers,
    );
  }

  SliverAppBar _sliverAppBar(){
    return SliverAppBar(
      title: Text('發現',
        style: TextStyle(fontSize: 20),
      ),
      backgroundColor: FMColors.wx_gray,
      floating: true,
      pinned: true,
      elevation: 0.0,
    );
  }

  // 功能 Items
  SliverFixedExtentList _sliverFixedExtentList(FMFindMenuModel menuModel){
    return SliverFixedExtentList(
      delegate: SliverChildBuilderDelegate(
            (context, index) => FMFindItem(
              model: menuModel.models[index],
              onTap: (model){

              },
            ),
        childCount: menuModel.models.length,
      ),
      itemExtent: 60.0,
    );
  }

  // 空白 blank
  SliverFixedExtentList _sliverDividList(FMFindMenuModel menuModel){
    return SliverFixedExtentList(
      delegate: SliverChildBuilderDelegate(
            (context, index) => Padding(padding: EdgeInsets.zero),
        childCount: menuModel.models.length,
      ),
      itemExtent: 10.0,
    );
  }

  void _initModels(){
    _models.add(FMFindModel('assets/images/find/find_friend.png', '朋友圈', 'function'));
    _models.add(FMFindModel('', '', 'divid'));
    _models.add(FMFindModel('assets/images/find/find_scan.png', '掃一掃', ''));
    _models.add(FMFindModel('', '', 'divid'));
    _models.add(FMFindModel('assets/images/find/find_look.png', '看一看', ''));
    _models.add(FMFindModel('assets/images/find/find_search.png', '搜一搜', ''));
    _models.add(FMFindModel('', '', 'divid'));
    _models.add(FMFindModel('assets/images/find/find_game.png', '游戲', ''));
    _models.add(FMFindModel('', '', 'divid'));
    _models.add(FMFindModel('assets/images/find/find_small_project.png', '小程序', ''));
  }

  void _initSliversWithModels(models){
    _slivers.add(_sliverAppBar());

    _menuModels.forEach((menuModel) {
      if (menuModel.dividModel) {
        _slivers.add(_sliverDividList(menuModel));
      } else {
        _slivers.add(_sliverFixedExtentList(menuModel));
      }
    });
  }

  void _initMenuModels(List <FMFindModel> items){
    List <FMFindModel> _tempModels = [];
    items.forEach((model) {
      if (model.type == 'divid') {
        _menuModels.add(new FMFindMenuModel(_tempModels));
        _tempModels.clear();
        _tempModels.add(model);
        FMFindMenuModel menuModel = new FMFindMenuModel(_tempModels);
        menuModel.dividModel = true;
        _menuModels.add(menuModel);
        _tempModels.clear();
      } else {
        _tempModels.add(model);
      }
    });

    if (_tempModels.length > 0) {
      _menuModels.add(new FMFindMenuModel(_tempModels));
      _tempModels.clear();
    }
  }
}

FMFindModel.dart

class FMFindModel {
  String imageName;
  String title;
  // 分割線
  String hasDivid;
  // type
  String type;

  FMFindModel(this.imageName, this.title, this.type);
}

class FMFindMenuModel {
  // 是否為分隔單位
  bool dividModel = false;
  //
  List <FMFindModel> _models = [];
  List <FMFindModel> get models => _models;

  FMFindMenuModel(List <FMFindModel> models) {
    _models.clear();
    models.forEach((model) {
      _models.add(model);
    });
  }
}

FMFindItem.dart

import 'package:FMWeixinApp/find/model/FMFindModel.dart';
import 'package:FMWeixinApp/tools/FMColor.dart';
import 'package:flutter/material.dart';

class FMFindItem extends StatefulWidget {
  final FMFindModel model;
  final void Function(FMFindModel model) onTap;
  const FMFindItem({
    Key key,
    this.model,
    this.onTap,
  }):super(key: key);

  @override
  FMFindItemState createState()=> FMFindItemState();
}

class FMFindItemState extends State <FMFindItem> {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return SizedBox(
      child: GestureDetector(
        onTap: (){
          if (widget.onTap != null) widget.onTap(widget.model);
        },
        child: _stack(),
      ),
    );
  }

  Stack _stack(){
    return Stack(
      children: [
        Positioned(
          left: 0,
          right: 0,
          top: 0,
          bottom: 0,
          child: _container(),
        ),
        Positioned(
          bottom: 0,
          height: 1,
          left: 60,
          right: 0,
          child: Divider(color: FMColors.wx_gray, thickness: 1,),
        ),
      ],
    );
  }

  Container _container(){
    return Container(
      child: Padding(
        padding: EdgeInsets.only(left: 15, right: 15),
        child: _row(),
      ),
      color: Colors.white,
    );
  }

  Row _row(){
    return Row(
      mainAxisAlignment: MainAxisAlignment.spaceBetween,
      children: [
        Row(
          children: [
            SizedBox(
              width: 30,
              height: 30,
              child: Image(image: AssetImage('${widget.model.imageName}')),
            ),
            Padding(padding: EdgeInsets.all(8)),
            Text('${widget.model.title}',
              style: TextStyle(fontSize: 17),
            ),
          ],
        ),
        Expanded(child: Padding(padding: EdgeInsets.zero)),
        SizedBox(
          width: 12,
          height: 30,
          child: Image(image: AssetImage('assets/images/find/find_arrow_right.png')),
        ),
      ],
    );
  }
}

4. 源碼分析

4.1 導航條實現

CustomScrollView.silvers 的第一個元素使用 SliverAppBar,自定義導航條。

4.2 Models 管理

  • 為什么要使用 Models 進行管理?
  1. 后續這里可能是網絡讀取數據,json -> Models -> 刷新 View。
  2. 使用 Models 管理,只需要針對 Models 進行順序排列即可,直觀便捷。比如我們先放個 Model(朋友圈),在放一個 Model(分隔行),再放一個 Model(掃一掃),就完成了 朋友圈,分隔,掃一掃這種功能。
  • Models 管理的實現
  1. 首先,我們創建一個分組 List <FMFindMenuModel> _menuModels,以及創建一個 List <FMFindModel> _models。
  2. _models 中我們就放 朋友圈、分隔、掃一掃、空格、、、等 FMFindModel 的集合,順序按照我們需要的順序排版
  3. 遍歷 _models,按照 分隔 Model 來對 _models 進行分組,每一組都創建一個 FMFindMenuModel,并將拆分出來的 FMFindModel 分組,保存到 FMFindMenuModel.models 中。
  4. 遍歷 _menuModels,按照對應分組開始創建 SliverFixedExtentList,并區分是功能性 Model 還是 分隔性 Model,生成兩種不同的 SliverFixedExtentList。
  5. 將 SliverAppBar,SliverFixedExtentList 整合到一起,提供給 CustomScrollView.silvers 完成我們想要的頁面。
FM Weixin Find.png
FM Weixin Find.gif
Flutter 仿生微信(目錄)
上一篇:Flutter 仿生微信(2):Pages 創建
下一篇:Flutter 仿生微信(4):我的頁面搭建
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,333評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,491評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,263評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,946評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,708評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,409評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,939評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,774評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,641評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,872評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,650評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,958評論 2 373

推薦閱讀更多精彩內容