上一篇中我记录了Flutter中经常使用的一些布局,本篇开始开发基于Flutter的开源中国客户端了。在本篇博客中,要实现的是一个App的总体框架,包括页面底部的Tab导航菜单、页面的侧滑菜单以及跳转到新的页面这几个功能。但愿本身在记录的同时能温故知新,同时给初学者一些帮助。git
在咱们平常生活中常用的App好比微信、微博、QQ等,基本上都是使用首页底部多个Tab可切换页面,加上可侧滑的菜单这种布局方式来组合。基于Flutter的开源中国客户端也是使用这种布局组合来实现的App。本篇要实现的页面效果以下图所示:github
下面一步步来完成这个布局框架的搭建。数组
在AndroidStudio中,经过File
-> New
-> New Flutter Project...
建立一个新的Flutter工程。bash
在新建立的Flutter工程中,删除lib/main.dart
中的代码,并编写下面的代码:微信
import 'package:flutter/material.dart';
void main() {
runApp(new MyApp());
}
// MyApp是一个有状态的组件,由于页面标题,页面内容和页面底部Tab都会改变
class MyApp extends StatefulWidget {
@override
State<StatefulWidget> createState() => new MyOSCClientState();
}
class MyOSCClientState extends State<MyApp> {
@override
Widget 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
设置了颜色为白色,若是不设置的话,默认为黑色,appBar
的iconTheme
属性也设置为了白色主题,若是不设置的话,AppBar上的图标默认为黑色。网络
在新建的Flutter项目的lib/
目录下,新建一个pages/
目录,该目录用于存放App中的全部页面,而后分别建立四个.dart文件:NewsListPage.dart
TweetsListPage.dart
DiscoveryPage.dart
MyInfoPage.dart
,表明App中首页底部4个Tab切换时分别显示的页面,这四个页面暂时就在页面正中间显示一行文本,下面是资讯列表NewsListPage.dart
代码:app
// pages/NewsListPage.dart
import 'package:flutter/material.dart';
// 资讯列表页面
class NewsListPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new Center(
child: new Text("NewsListPage"),
);
}
}
复制代码
其他三个页面代码跟上面的相似,只是换了类名和Text组件的文本。框架
在上一步中,咱们的Scaffold组件里的body属性只是一个Text组件,为了加载上面的4个页面,须要用一个容器组件将这4个页面装起来,而后在点击Tab时切换页面,这就用到了我以前的博文里说到的IndexedStack组件了。IndexedStack中能够有多个子组件,根据索引值来显示其中某个组件而隐藏其他的组件。less
在第一步中MyOSCClientState
类中定义两个变量:_tabIndex
和_body
,_tabIndex
表示当前页面底部选中的Tab的索引,_body
表示首页Scaffold组件的body属性值,而后给_tabIndex
和_body
变量赋值,以下代码所示:ide
// 页面当前选中的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菜单只须要给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类中定义两个变量tabImages
和appBarTitles
。tabImages
是一个二维数组,表示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文件中已经添加了图片的路径,以下图:
为了达到点击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 {
@override
Widget 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();
},
)
],
)
),
);
}
}
复制代码
上面的代码中有以下几点须要注意:
build
方法中咱们返回的是一个Scaffold组件,而不是像main.dart中那样返回一个MaterialApp组件,这是由于咱们在使用Navigator从资讯列表页跳转到详情页时,会自动为详情页的AppBar左边添加返回按钮,若是你在详情页仍是使用MaterialApp对象,则页面左上角不会自动添加返回按钮。mainAxisAlignment: MainAxisAlignment.center
,则页面上的组件只会在水平方向居中而不会在垂直方向上居中。Navigator.of(context).pop()
来使页面返回到上一级。下面须要修改NewsListPage的代码,加入按钮并完成跳转到详情页的逻辑,代码以下:
import 'package:flutter/material.dart';
import 'NewsDetailPage.dart';
// 资讯列表页面
class NewsListPage extends StatelessWidget {
@override
Widget 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的开源中国客户端各个静态页面的实现。
上一篇 | 下一篇 |
---|---|
从0开始写一个基于Flutter的开源中国客户端(4) ——Flutter布局基础 |
从0开始写一个基于Flutter的开源中国客户端(6)——各个静态页面的实现 |