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阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容