1. 源码下载
喜欢的话,别忘了点个关注,还有给个 Github 右上角的小星星吧。
源码下载地址,代码会根据不断更新,建议使用 git clone xxx,有发现好的架构或者好的封装方式,代码可能会更新,文章可能会比较慢。
目录
下一篇: Flutter 仿生微信(2):Pages 创建
2. 思路
我一直认为写代码前,思路一定要清晰,不然一定是越写越乱的。微信的功能逻辑从开发的角度肯定是比较复杂的,但是从仿生的角度,我们更多的只是在意一下 UI 效果,会简单很多。
- 文件分类
微信页面整体分为4块,’微信‘,’通讯录‘,’发现‘,’我的‘,以及一些通用的业务块。按照这个逻辑创建不同文件夹,以及业务文件夹。
- TabBar 结构
由于现在只是刚开始写,先不考虑太多,脚踏实地的写。
首先,我们需要一个 home 文件夹,其中第一层包含 FMHome.dart,以及 FMHomeManger.dart 两个文件。FMHome.dart 用来加载主页,FMHomeManager 则是用来控制页面切换等功能。
第二,我们在 home 文件夹下新增 TabBar 文件夹,在其中新建 FMTabBar.dart,将 TabBar 封装出去。这个页面只做 UI,点击的交互事件,以及刷新页面,交给 FMHomeManager 来完成,因为需要同步通知 body 切换内容。
最后,home 文件夹下预留别的文件夹,用来处理离线,他端在线,以及全屏 Dialog 这种操作,暂时就先这样吧,马上开工。
![](https://img.haomeiwen.com/i19650943/1285ebf0336f36c5.gif)
3. 示例代码
FMHome.dart
import 'package:FMWeixinApp/home/FMHomeManager.dart';
import 'package:FMWeixinApp/home/tabbar/FMTabBar.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class FMHome extends StatefulWidget {
@override
FMHomeState createState()=> FMHomeState();
}
class FMHomeState extends State <FMHome> {
FMHomeManager manager = FMHomeManager();
@override
void initState() {
// TODO: implement initState
super.initState();
}
@override
Widget build(BuildContext context) {
// TODO: implement build
return _scaffold();
}
ChangeNotifierProvider _provider(){
return ChangeNotifierProvider(
create: (context) => FMHomeManager(),
child: _scaffold(),
);
}
Scaffold _scaffold(){
return Scaffold(
// TabBar
bottomNavigationBar: ChangeNotifierProvider(
create: (context)=> manager,
child: FMTabBar(),
),
);
}
}
FMHomeManager.dart
import 'package:flutter/material.dart';
class FMHomeManager with ChangeNotifier {
// 下标
int _selectedIndex = 0;
int get selectedIndex => _selectedIndex;
set selectedIndex(int index){
_selectedIndex = index;
notifyListeners();
}
}
FMTabBar.dart
import 'package:FMWeixinApp/home/FMHomeManager.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class FMTabBar extends StatefulWidget {
@override
FMTabBarState createState()=> FMTabBarState();
}
class FMTabBarState extends State <FMTabBar> {
var selectedIndex = 0;
@override
Widget build(BuildContext context) {
// TODO: implement build
return Consumer<FMHomeManager>(builder: (context, manager, child){
print('index = ${manager.selectedIndex}');
return _bottomNavigationBar(manager);
});
}
BottomNavigationBar _bottomNavigationBar(FMHomeManager manager){
return BottomNavigationBar(
// items
items: [
_createItem(AssetImage('assets/images/tab/tab_weixin.png'), AssetImage('assets/images/tab/tab_weixin_green.png'), '微信'),
_createItem(AssetImage('assets/images/tab/tab_mail_list.png'), AssetImage('assets/images/tab/tab_mail_list_green.png'), '通讯录'),
_createItem(AssetImage('assets/images/tab/tab_find.png'), AssetImage('assets/images/tab/tab_find_green.png'), '发现'),
_createItem(AssetImage('assets/images/tab/tab_mine.png'), AssetImage('assets/images/tab/tab_mine_green.png'), '我的'),
],
// 选中 index
currentIndex: manager.selectedIndex,
// 点击事件
onTap: (index){
manager.selectedIndex = index;
},
// 固定大小,取消自适应偏移
type: BottomNavigationBarType.fixed,
// 字体颜色
unselectedItemColor: Color.fromRGBO(51, 51, 51, 1),
selectedItemColor: Color.fromRGBO(0, 186, 85, 1),
// 字体大小
selectedFontSize: 15,
unselectedFontSize: 15,
// 未选中时显示 title
showUnselectedLabels: true,
);
}
BottomNavigationBarItem _createItem(AssetImage image, AssetImage selectedImage, String title){
return BottomNavigationBarItem(
icon: SizedBox(
width: 30,
height: 30,
child: Image(image: image),
),
activeIcon: SizedBox(
width: 30,
height: 30,
child: Image(image: selectedImage),
),
title: Text('$title'),
);
}
}
4. 源码分析
4.1 TabBar 图片使用
![](https://img.haomeiwen.com/i19650943/a85a9504a92f643e.png)
这里可以看到,我是在工程目录下创建了 assets/images/tab 文件夹,后续的其他页面的路径图片为 assets/images/xxx 这样。
![](https://img.haomeiwen.com/i19650943/13de8057ee69abc2.png)
这里的空格有点讲究,不懂的看这里 Flutter入门(9):Flutter 组件之 Image、AssetImage 详解。
4.2 页面刷新
在之前的很多简单页面里,很多同学应该都是简单的用 setState 来刷新页面了,但是在工程里,如果这样做,联动这方面就会做得很乱。
我们这里思路首先已经明确了,页面的所有交互,都只是更改 FMHomeManager 的属性,然后通过 FMHomeManager 去通知所有需要更新的地方来做数据更新。
在这里,我们使用的是 Provider,这个 Provider 真的非常好用,但是上手较难,初学者建议耐下性子。我作为一个多年的 App 开发,这里也是用了几个小时才搞定。
我们先来更改 pubspec.yaml 文件,引入 provider,我这里查了当下的最新版本为 4.3.2。
![](https://img.haomeiwen.com/i19650943/8c8a983cf4d19fc7.png)
接下来执行 flutter pub get
flutter pub get
完成后,重新运行工程,加载 package 是很基础的了,就不多讲了。Flutter入门(1):SDK下载与环境变量
- 构建 ChangeNotifier
ChangeNotifier 可以理解为 ViewModel,他负责保存各种相关属性,并且可以通知所有监听者进行刷新操作。
- 创建 Provider
FMHomeManager manager = FMHomeManager();
Scaffold _scaffold(){
return Scaffold(
// TabBar
bottomNavigationBar: ChangeNotifierProvider(
create: (context)=> manager,
child: FMTabBar(),
),
);
}
这里,我们创建 ChangeNotifierProvider,并将 manager 作为 ViewModel 传递给 FMTabBar。
- 监听者
Widget build(BuildContext context) {
// TODO: implement build
return Consumer<FMHomeManager>(builder: (context, manager, child){
print('index = ${manager.selectedIndex}');
return _bottomNavigationBar(manager);
});
}
BottomNavigationBar _bottomNavigationBar(FMHomeManager manager){
return BottomNavigationBar(
// items
items: [
_createItem(AssetImage('assets/images/tab/tab_weixin.png'), AssetImage('assets/images/tab/tab_weixin_green.png'), '微信'),
_createItem(AssetImage('assets/images/tab/tab_mail_list.png'), AssetImage('assets/images/tab/tab_mail_list_green.png'), '通讯录'),
_createItem(AssetImage('assets/images/tab/tab_find.png'), AssetImage('assets/images/tab/tab_find_green.png'), '发现'),
_createItem(AssetImage('assets/images/tab/tab_mine.png'), AssetImage('assets/images/tab/tab_mine_green.png'), '我的'),
],
// 选中 index
currentIndex: manager.selectedIndex,
// 点击事件
onTap: (index){
manager.selectedIndex = index;
},
// 固定大小,取消自适应偏移
type: BottomNavigationBarType.fixed,
// 字体颜色
unselectedItemColor: Color.fromRGBO(51, 51, 51, 1),
selectedItemColor: Color.fromRGBO(0, 186, 85, 1),
// 字体大小
selectedFontSize: 15,
unselectedFontSize: 15,
// 未选中时显示 title
showUnselectedLabels: true,
);
}
我们使用 Consumer<>(builder) 来监听我们之前创建的 manager,如果收到 manager 通知,就会刷新页面。同时,我们这个页面的 currentIndex = manager.selectedIndex,并且在 onTap 时,我们也只是更改了 manager.selectedIndex = index。
在 FMTabBar 使用 FMHomeManager 的 selectedIndex 作为下标,并且在点击下方 item 时,也是更改 FMHomeManager 的 selectedIndex。
- 通知页面刷新
这里就是最后一环了,状态更新完毕,如何进行页面刷新。这里就再次回到 FMHomeManager 了,我们所有的刷新操作都是通过 FMHomeManager 发送出去的,监听者接收到通知,就刷新页面。
class FMHomeManager with ChangeNotifier {
// 下标
int _selectedIndex = 0;
int get selectedIndex => _selectedIndex;
set selectedIndex(int index){
_selectedIndex = index;
notifyListeners();
}
}
这里可以看到,我们重写 _selectedIndex 的 getter,setter 方法,并且在 setter 方法里增加了一行 notifyListeners();
FMHomeManager 继承于 ChangeNotifier,notifyListeners() 就是通知监听者的 api,调用这个 api 完成页面刷新。
![](https://img.haomeiwen.com/i19650943/1285ebf0336f36c5.gif)
网友评论