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 这种操作,暂时就先这样吧,马上开工。
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 {
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 {
var selectedIndex = 0;
@override
Widget build(BuildContext context) {
// TODO: implement build
return Consumer(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 图片使用
这里可以看到,我是在工程目录下创建了 assets/images/tab 文件夹,后续的其他页面的路径图片为 assets/images/xxx 这样。
这里的空格有点讲究,不懂的看这里 Flutter入门(9):Flutter 组件之 Image、AssetImage 详解。
4.2 页面刷新
在之前的很多简单页面里,很多同学应该都是简单的用 setState 来刷新页面了,但是在工程里,如果这样做,联动这方面就会做得很乱。
我们这里思路首先已经明确了,页面的所有交互,都只是更改 FMHomeManager 的属性,然后通过 FMHomeManager 去通知所有需要更新的地方来做数据更新。
在这里,我们使用的是 Provider,这个 Provider 真的非常好用,但是上手较难,初学者建议耐下性子。我作为一个多年的 App 开发,这里也是用了几个小时才搞定。
我们先来更改 pubspec.yaml 文件,引入 provider,我这里查了当下的最新版本为 4.3.2。
接下来执行 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(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 完成页面刷新。