1. 源码下载
喜欢的话,别忘了点个关注,还有给个 Github 右上角的小星星吧。
源码下载地址,代码会根据不断更新。
Flutter 仿生微信(目录)
上一篇:Flutter 仿生微信(2):Pages 创建
下一篇:Flutter 仿生微信(4):我的页面搭建
2. 思路
发现页是几个页面中最简单的,静态页面,所以先从这里入手。
2.1 使用 ListView 完成
本来是准备使用 Scaffold 的 appBar 来实现导航条,然后 FMFind.dart 中使用最方便的 ListView 即可完成。但是考虑到不同页面导航条不同,状态管理比较麻烦,所以放弃这个方案。
2.2 使用 CustomScrollView 完成
- CustomScrollView 可以自定义导航条,看了一下和预期比较接近。
- 然后就是结构了,页面布局可以很清晰的看到,是一个常用的功能条和分割条,并且可以编辑展示在这里的功能条数量。
- 使用 Models 管理,只需要针对 Models 进行顺序排列即可,直观便捷。比如我们先放个 Model(朋友圈),在放一个 Model(分隔行),再放一个 Model(扫一扫),就完成了 朋友圈,分隔,扫一扫这种功能。
Models -> [model1, model2..], [model_divider], [model3, model4..].. -> 分类布局完成后 ->
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 进行管理?
- 后续这里可能是网络读取数据,json -> Models -> 刷新 View。
- 使用 Models 管理,只需要针对 Models 进行顺序排列即可,直观便捷。比如我们先放个 Model(朋友圈),在放一个 Model(分隔行),再放一个 Model(扫一扫),就完成了 朋友圈,分隔,扫一扫这种功能。
- Models 管理的实现
- 首先,我们创建一个分组 List
_menuModels,以及创建一个 List _models。 - _models 中我们就放 朋友圈、分隔、扫一扫、空格、、、等 FMFindModel 的集合,顺序按照我们需要的顺序排版
- 遍历 _models,按照 分隔 Model 来对 _models 进行分组,每一组都创建一个 FMFindMenuModel,并将拆分出来的 FMFindModel 分组,保存到 FMFindMenuModel.models 中。
- 遍历 _menuModels,按照对应分组开始创建 SliverFixedExtentList,并区分是功能性 Model 还是 分隔性 Model,生成两种不同的 SliverFixedExtentList。
- 将 SliverAppBar,SliverFixedExtentList 整合到一起,提供给 CustomScrollView.silvers 完成我们想要的页面。