Flutter 仿生微信(1):TabBar 创建

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 这种操作,暂时就先这样吧,马上开工。

FM Weixin TabBar.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 图片使用

Images path.png

这里可以看到,我是在工程目录下创建了 assets/images/tab 文件夹,后续的其他页面的路径图片为 assets/images/xxx 这样。

Images yaml.png

这里的空格有点讲究,不懂的看这里 Flutter入门(9):Flutter 组件之 Image、AssetImage 详解

4.2 页面刷新

在之前的很多简单页面里,很多同学应该都是简单的用 setState 来刷新页面了,但是在工程里,如果这样做,联动这方面就会做得很乱。

我们这里思路首先已经明确了,页面的所有交互,都只是更改 FMHomeManager 的属性,然后通过 FMHomeManager 去通知所有需要更新的地方来做数据更新。

在这里,我们使用的是 Provider,这个 Provider 真的非常好用,但是上手较难,初学者建议耐下性子。我作为一个多年的 App 开发,这里也是用了几个小时才搞定。

我们先来更改 pubspec.yaml 文件,引入 provider,我这里查了当下的最新版本为 4.3.2。

provider yaml.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 完成页面刷新。

FM Weixin TabBar.gif
目录
下一篇: Flutter 仿生微信(2):Pages 创建
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容