上一篇中我记录了Flutter中常用的一些布局,本篇开始开发基于Flutter的开源中国客户端了。在本篇博客中,要实现的是一个App的整体框架,包括页面底部的Tab导航菜单、页面的侧滑菜单以及跳转到新的页面这几个功能。希望自己在记录的同时能温故知新,同时给初学者一些帮助。

App整体布局框架搭建

在我们日常生活中经常使用的App比如微信、微博、QQ等,基本上都是使用首页底部多个Tab可切换页面,加上可侧滑的菜单这种布局方式来组合。基于Flutter的开源中国客户端也是使用这种布局组合来实现的App。本篇要实现的页面效果如下图所示:

下面一步步来完成这个布局框架的搭建。

新建项目

在AndroidStudio中,通过File -> New -> New Flutter Project...创建一个新的Flutter工程。

使用MaterialApp和Scaffold组件构建首页

在新创建的Flutter工程中,删除lib/main.dart中的代码,并编写下面的代码:

import 'package:flutter/material.dart';void main() {runApp(new MyApp());
}// MyApp是一个有状态的组件,因为页面标题,页面内容和页面底部Tab都会改变
class MyApp extends StatefulWidget {@overrideState<StatefulWidget> createState() => new MyOSCClientState();
}class MyOSCClientState extends State<MyApp> {@overrideWidget build(BuildContext context) {return new MaterialApp(theme: new ThemeData(// 设置页面的主题色primaryColor: const Color(0xFF63CA6C)),home: new Scaffold(appBar: new AppBar(// 设置AppBar标题title: new Text("My OSC",// 设置AppBar上文本的样式style: new TextStyle(color: Colors.white)),// 设置AppBar上图标的样式iconTheme: new IconThemeData(color: Colors.white)),body: new Text("MyOSC Client")),);}
}

上面的代码中,为MaterialApp设置了theme参数,主要是为了改变页面主题颜色为绿色,在Scaffold的appBar属性中,为title设置了颜色为白色,如果不设置的话,默认为黑色,appBariconTheme属性也设置为了白色主题,如果不设置的话,AppBar上的图标默认为黑色。

编写4个页面用于切换显示

在新建的Flutter项目的lib/目录下,新建一个pages/目录,该目录用于存放App中的所有页面,然后分别创建四个.dart文件:NewsListPage.dart TweetsListPage.dart DiscoveryPage.dart MyInfoPage.dart,代表App中首页底部4个Tab切换时分别显示的页面,这四个页面暂时就在页面正中间显示一行文本,下面是资讯列表NewsListPage.dart代码:

// pages/NewsListPage.dart
import 'package:flutter/material.dart';// 资讯列表页面
class NewsListPage extends StatelessWidget {@overrideWidget build(BuildContext context) {return new Center(child: new Text("NewsListPage"),);}
}

其余三个页面代码跟上面的类似,只是换了类名和Text组件的文本。

在上一步中,我们的Scaffold组件里的body属性只是一个Text组件,为了加载上面的4个页面,需要用一个容器组件将这4个页面装起来,然后在点击Tab时切换页面,这就用到了我之前的博文里说到的IndexedStack组件了。IndexedStack中可以有多个子组件,根据索引值来显示其中某个组件而隐藏其余的组件。

在第一步中MyOSCClientState类中定义两个变量:_tabIndex_body_tabIndex表示当前页面底部选中的Tab的索引,_body表示首页Scaffold组件的body属性值,然后给_tabIndex_body变量赋值,如下代码所示:

// 页面当前选中的Tab的索引
int _tabIndex = 0;// 页面body部分组件
var _body = new IndexedStack(children: <Widget>[new NewsListPage(),new TweetsListPage(),new DiscoveryPage(),new MyInfoPage()],index: _tabIndex,
);

上面用IndexedStack加载了4个页面用于切换Tab时显示,但是Tab我们还没有做出来,Flutter中为页面添加底部导航Tab菜单很简单,已经有很多组件可以用了。

编写页面底部导航Tab菜单

给页面添加底部导航Tab菜单只需要给Scaffold组件添加一个bottomNavigationBar属性即可,这里的bottomNavigationBar我们用Flutter提供的CupertinoTabBar组件。

CupertinoTabBar是Flutter内置的iOS风格的选项卡,用于在页面底部显示几个Tab,要使用Cupertino风格的组件,必须先导入头文件,如下代码:

import 'package:flutter/cupertino.dart';

CupertinoTabBar组件的用法也比较简单,代码如下:

new CupertinoTabBar(items: getBottomNavItems(),currentIndex: _tabIndex,onTap: (index) {// 底部TabItem的点击事件处理,点击时改变当前选择的Tab的索引值,则页面会自动刷新setState((){_tabIndex = index;});},
)

其中items是一个List<BottomNavigationBarItem>对象,currentIndex表示当前选中的Tab的索引值,onTap是TabItem点击事件,上面的代码中,getBottomNavItems()方法代码如下:

List<BottomNavigationBarItem> getBottomNavItems() {List<BottomNavigationBarItem> list = new List();for (int i = 0; i < 4; i++) {list.add(new BottomNavigationBarItem(icon: getTabIcon(i),title: getTabTitle(i)));}return list;
}// 根据索引值确定Tab是选中状态的样式还是非选中状态的样式
TextStyle getTabTextStyle(int curIndex) {if (curIndex == _tabIndex) {return tabTextStyleSelected;}return tabTextStyleNormal;
}// 根据索引值确定TabItem的icon是选中还是非选中
Image getTabIcon(int curIndex) {if (curIndex == _tabIndex) {return tabImages[curIndex][1];}return tabImages[curIndex][0];
}// 根据索引值返回页面顶部标题
Text getTabTitle(int curIndex) {return new Text(appBarTitles[curIndex],style: getTabTextStyle(curIndex));
}

由于TabItem是由一个图标和一个文本组件构成,所以这里还需要在MyOSCClientState类中定义两个变量tabImagesappBarTitlestabImages是一个二维数组,表示TabItem中的图标(包括选中和未选中状态的图标),appBarTitles是一个字符串数组,表示每个TabItem对应的页面标题,这两个变量的赋值代码如下:

// 页面底部TabItem上的图标数组
var tabImages;// 页面顶部的大标题(也是TabItem上的文本)
var appBarTitles = ['资讯', '动弹', '发现', '我的'];// 数据初始化,包括TabIcon数据和页面内容数据
void initData() {if (tabImages == null) {tabImages = [[getTabImage('images/ic_nav_news_normal.png'),getTabImage('images/ic_nav_news_actived.png')],[getTabImage('images/ic_nav_tweet_normal.png'),getTabImage('images/ic_nav_tweet_actived.png')],[getTabImage('images/ic_nav_discover_normal.png'),getTabImage('images/ic_nav_discover_actived.png')],[getTabImage('images/ic_nav_my_normal.png'),getTabImage('images/ic_nav_my_pressed.png')]];}
}// 传入图片路径,返回一个Image组件
Image getTabImage(path) {return new Image.asset(path, width: 20.0, height: 20.0);
}

上面的代码中需要注意的是Image组件,要使用image/目录下的图片,必须确保项目根目录下的pubspec.yaml文件中已经添加了图片的路径,如下图:

如果没有上面的assets配置,直接加载图片是会报错的。

为了达到点击Tab切换不同的页面的功能,我们需要给CupertinoTabBar组件的onTap参数配置一个方法,该方法有一个index参数,我们将这个index赋值给前面定义的_tabIndex即可,并将这个赋值操作放到setState中执行,如下代码:

onTap: (index) {// 底部TabItem的点击事件处理,点击时改变当前选择的Tab的索引值,则页面会自动刷新setState((){_tabIndex = index;});
},

最后放上MyOSCClientState类的build方法代码:

@override
Widget build(BuildContext context) {initData();return new MaterialApp(theme: new ThemeData(// 设置页面的主题色primaryColor: const Color(0xFF63CA6C)),home: new Scaffold(appBar: new AppBar(// 设置AppBar标题title: new Text(appBarTitles[_tabIndex],// 设置AppBar上文本的样式style: new TextStyle(color: Colors.white)),// 设置AppBar上图标的样式iconTheme: new IconThemeData(color: Colors.white)),body: _body,// bottomNavigationBar属性为页面底部添加导航的Tab,CupertinoTabBar是Flutter提供的一个iOS风格的底部导航栏组件bottomNavigationBar: new CupertinoTabBar(items: getBottomNavItems(),currentIndex: _tabIndex,onTap: (index) {// 底部TabItem的点击事件处理,点击时改变当前选择的Tab的索引值,则页面会自动刷新setState((){_tabIndex = index;});},)),);
}

在上面的代码中,body属性是_body变量,而_body变量是个IndexedStack对象,IndexedStack对象的index值是_tabIndex,所以当我们在setState中改变了_tabIndex后,IndexedStack就会自动切换显示子组件了,也就达到了切换页面的目的。

上面的代码运行在模拟器中如下图所示:

给首页加上侧滑菜单

侧滑菜单在Flutter中已有相关组件,所以为首页加上侧滑菜单的方法很简单:给Scaffold组件传个drawer参数即可,代码如下:

new Scaffold(appBar: new AppBar(// 设置AppBar标题title: new Text(appBarTitles[_tabIndex],// 设置AppBar上文本的样式style: new TextStyle(color: Colors.white)),// 设置AppBar上图标的样式iconTheme: new IconThemeData(color: Colors.white)),body: _body,// bottomNavigationBar属性为页面底部添加导航的Tab,CupertinoTabBar是Flutter提供的一个iOS风格的底部导航栏组件bottomNavigationBar: new CupertinoTabBar(items: getBottomNavItems(),currentIndex: _tabIndex,onTap: (index) {// 底部TabItem的点击事件处理,点击时改变当前选择的Tab的索引值,则页面会自动刷新setState((){_tabIndex = index;});},),// drawer属性用于为当前页面添加一个侧滑菜单drawer: new Drawer(child: new Center(child: new Text("this is a drawer")),),
)

有了drawer之后的app运行效果如下图:

实现页面跳转逻辑

在Flutter中实现页面的跳转非常简单,使用Navigator的相关API即可,下面我们改造一下NewsListPage页面,在页面中间加入一个按钮,点击按钮跳转到详情页。

首先我们在pages/目录下新建一个NewsDetailPage代表资讯详情页,并添加如下代码:

import 'package:flutter/material.dart';class NewsDetailPage extends StatelessWidget {@overrideWidget build(BuildContext context) {return new Scaffold(appBar: new AppBar(title: new Text("资讯详情", style: new TextStyle(color: Colors.white)),iconTheme: new IconThemeData(color: Colors.white)),body: new Center(child: new Column(mainAxisAlignment: MainAxisAlignment.center,children: <Widget>[new Text("News Detail Page."),new RaisedButton(child: new Text("Back"),onPressed: () {Navigator.of(context).pop();},)],)),);}
}

上面的代码中有如下几点需要注意:

  1. build方法中我们返回的是一个Scaffold组件,而不是像main.dart中那样返回一个MaterialApp组件,这是因为我们在使用Navigator从资讯列表页跳转到详情页时,会自动为详情页的AppBar左边添加返回按钮,如果你在详情页还是使用MaterialApp对象,则页面左上角不会自动添加返回按钮。
  2. 上面代码中的body部分返回的是一个Center组件,Center中装的是Column组件,如果你不为Column组件设置mainAxisAlignment: MainAxisAlignment.center,则页面上的组件只会在水平方向居中而不会在垂直方向上居中。
  3. 使用Navigator.of(context).pop()来使页面返回到上一级。

下面需要修改NewsListPage的代码,加入按钮并完成跳转到详情页的逻辑,代码如下:

import 'package:flutter/material.dart';
import 'NewsDetailPage.dart';// 资讯列表页面
class NewsListPage extends StatelessWidget {@overrideWidget build(BuildContext context) {return new Center(child: new RaisedButton(child: new Text("to detail page"),onPressed: () {Navigator.of(context).push(new MaterialPageRoute(builder: (ctx) {return new NewsDetailPage();}));}));}
}

使用Navigator.of(context).push()方法来完成页面的跳转,push的参数是一个Route对象,这里使用了Flutter提供的MaterialPageRoute对象,builder参数是一个方法,返回的就是详情页对象。

除了使用上面的方式做页面的跳转外,还可以给MaterialApp配置一个route参数,该route参数类似于一个全局的路由表,根据一个name值导航到对应的页面,这种方式需要定义一个类型为Map<String, WidgetBuilder>的变量_route变量,并在initDate()方法中为这个变量赋值,如下代码:

_routes['newsDetail'] = (BuildContext) {return new NewsDetailPage();
};

在需要跳转页面的时候,调用如下方法完成页面跳转:

Navigator.of(context).pushNamed("newsDetail");

如果在页面跳转时需要给下一个页面传值,可以在下一个页面的构造方法中接收传入的值,然后在Navigator调用push方法的时候new下一个页面时,在组件的构造方法中设置传入的值,具体用法可以参考这里

源码

本篇相关的所有源码都在GitHub上demo-flutter-osc项目的v0.1分支。

后记

本篇主要记录的是基于Flutter的开源中国客户端整体布局框架的搭建过程,通过使用Flutter内置的各种Widget,可以很容易的实现这个布局框架,下一篇准备记录的是基于Flutter的开源中国客户端各个静态页面的实现。

我的开源项目

  1. 基于Google Flutter的开源中国客户端,希望大家给个Star支持一下,源码:

    • GitHub

    • 码云

  2. 基于Flutter的俄罗斯方块小游戏,希望大家给个Star支持一下,源码:

  • GitHub
  • 码云

转载于:https://my.oschina.net/u/815261/blog/1925491

从0开始写一个基于Flutter的开源中国客户端(5)——App整体布局框架搭建相关推荐

  1. 手写一个基于NIO的迷你版Tomcat

    笔者也建立的自己的公众号啦,平时会分享一些编程知识,欢迎各位大佬支持~ 扫码或微信搜索北风IT之路关注 本文公众号地址:手写一个基于NIO的迷你版Tomcat 在很久之前看到了一篇文章写一个迷你版的T ...

  2. [从 0 开始写一个操作系统] 一、准备知识

    从 0 开始写一个操作系统 作者:解琛 时间:2020 年 8 月 29 日 从 0 开始写一个操作系统 一.准备知识 1.1 实现方案 1.2 gcc 1.2.1 AT&T 汇编基本语法 1 ...

  3. 给微软的日志框架写一个基于委托的日志提供者

    动手造轮子:给微软的日志框架写一个基于委托的日志提供者 Intro 微软的日志框架现在已经比较通用,有时候我们不想使用外部的日志提供者,但又希望提供一个比较简单的委托就可以实现日志记录,于是就有了后面 ...

  4. java编写一个框架_手把手教你写一个基于 RxJava 的扩展框架

    背景 现在 RxJava 在 Android 开发中可谓时炽手可热,其受欢迎程度不言而喻,也因此在 github 上出现了一系列的基于 RxJava 的框架,如 RxBinding.RxPermiss ...

  5. 从0开始写一个小程序

    项目简介 从0开始写一个小程序,本来想写一个新闻类的程序,后来发现调用的聚合数据api每天只能访问100次,就换成豆瓣的了,直接用豆瓣的接口又访问不了,在网上查了一下,要把豆瓣的地址换成"h ...

  6. 从0开始写一个多线程爬虫(2)

    上一篇文章: 从0开始写一个多线程爬虫(1) 我们用继承Thread类的方式来改造多线程爬虫,其实主要就是把上一篇文章的代码写到线程类的run方法中,代码如下: import re import re ...

  7. 详细介绍如何从0开始写一个数据通信,将数据从单片机发送到上位机(或者虚拟示波器)进行数据或图像显示,以及常见问题或注意事项解答,本文主要以匿名上位机为例,适合新手和小白

      本文主要内容:详细介绍如何从0开始写一个数据通信,将数据从单片机发送到上位机(或者虚拟示波器)进行数据或图像显示,帮助我们调节一些参数,比如电机PID的调节.波形融合等,以及在我们写通信协议的时候 ...

  8. 从0开始写一个播放器系列-开篇

    从0开始写一个播放器系列-开篇 阅读本系列博客所需要具备的知识: js, ts , canvas Api , DOM, webpack, 不懂也没有太大的关系, 我会捎带着写出来, 剩下的自行百度 第 ...

  9. 一款基于flutter的仿微博客户端(仿微博首页,视频页,聊天页面等)

    基于flutter的仿微博客户端 在学习了flutter之后,写了一个仿微博最新的10.4.0版本, 还原微博80%的界面 总共涉及到了几十个界面和接口,用到了flutter中的大部分组件 该项目分为 ...

最新文章

  1. 【Egret】WebSocket 的使用说明
  2. cisco 路由器访问权限的设置
  3. MyBatis(二)——多对一、一对多
  4. Strut2和FreeMarker整合时的一些问题
  5. 11.1 JavaScript介绍
  6. C++测试与调试知识
  7. mysql 关联查询_mysql数据库调优(二)
  8. 一个值得收藏的小工具
  9. 如何量化考核技术人的KPI?
  10. 2021-09-09 Hadoop Hive Spark概览
  11. Netty之Channel、NioEventLoopGroup、客户端connect方法总结
  12. Bean的装配方式之xml装配--(超详细,适合小白入门)
  13. vyos配置dns迭代查询
  14. 千万别说你会Python!如果不知道这10个Python包!
  15. 【iccv2021】Vision-Language Transformer and Query Generation for Referring Segmentation
  16. 怎样开发自己的Telegram Bot
  17. jdbc连接字符集为us7ascii的oracle数据库乱码解决办法
  18. 量子通信利用量子力学原理产生密钥对信息进行加密和解密,并采用量子纠缠效应进行密钥分发,被认为是当今最安全的通信系统.有两项特性,一个是不可分割,一个是不可复制...
  19. 自动化测试——回顾与展望
  20. 费率与利率的差别_费率是什么(利率和费率有啥区别?)

热门文章

  1. anime 动画的使用方法
  2. Stable Diffusion - Prompts 提示词工程框架
  3. 油画篇—颜色和空间关系直接影响作品效果~
  4. 兴趣小实验-小灯泡闪烁
  5. 三星浏览器vr_通过WebVR在浏览器中带来VR体验
  6. 每一个值从a到z的顺序排序,若遇到相同首字母,则看第二个字母,以此类推
  7. Android_003_android应用程序安装后图标不显示
  8. Uncaught Error: only one instance of babel-polyfill is allowed
  9. Linux字体-Linux中文字体(mkfontscale mkfontdir fc-cache -fv命令)
  10. 神经网络的激活函数为什么必须使用非线性函数