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(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  {

  List _slivers = [];

  List  _models = [];

  List  _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  items){
    List  _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  _models = [];
  List  get models => _models;

  FMFindMenuModel(List  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  {
  @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 _menuModels,以及创建一个 List _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):我的页面搭建

你可能感兴趣的:(Flutter 仿生微信(3):发现页面搭建)