紧接上一篇的有侧边栏APP,此次咱们向APP中加入上下Tab页,使之跟趋近主流大部分APP的信息布局套路,等不及看源码的同窗能够点击进入个人git仓库下载代码。html
这是Tab
页的控制器,用于定义Tab
标签和内容页的坐标,还可配置标签页的切换动画效果等。android
TabController通常放入有状态控件中使用,以适应标签页数量和内容有动态变化的场景,若是标签页在APP中是静态固定的格局,则能够在无状态控件中加入简易版的 DefaultTabController以提升运行效率,毕竟无状态控件要比有状态控件更省资源,运行效率更快。
Tab
页的Title
控件,切换Tab
页的入口,通常放到AppBar
控件下使用,内部有*Title属性。其子元素按水平横向排列布局,若是须要纵向排列,请使用Column
或ListView
控件包装一下。子元素为Tab
类型的数组。git
Tab
页的内容容器,其内放置Tab
页的主体内容。子元素能够是多个各类类型的控件。github
TabController
Tab
页的切换搭配了动画,所以到State
类上附加一个SingleTickerProviderStateMixin
:数组
//定义有状态控件 class HomePage extends StatefulWidget { @override _HomePageState createState() => new _HomePageState(); } //用于使用到了一点点的动画效果,所以加入了SingleTickerProviderStateMixin class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin{ ... }
而后到有状态控件的子类State
中初始化控制器TabController
:app
@override void initState() { super.initState(); _tabController = new TabController( vsync: this, //动画效果的异步处理,默认格式,背下来便可 length: 3 //须要控制的Tab页数量 ); } //当整个页面dispose时,记得把控制器也dispose掉,释放内存 @override void dispose() { _tabController .dispose(); super.dispose(); }
而后到TabBar
和TabBarView
中的controller属性中调用控制器_tabController
框架
//标签页标题 new TabBar( tabs: [ //注意TabBar的子元素为Tab类型的数组 new Tab(icon: new Icon(Icons.directions_car)), new Tab(icon: new Icon(Icons.directions_transit)), new Tab(icon: new Icon(Icons.directions_bike)), ] //标签页内容区域 new TabBarView( children: [ new Icon(Icons.directions_car), new Icon(Icons.directions_transit), new Icon(Icons.directions_bike), ]
最后,咱们把定义好的TabBar
和TabBarView
安放到须要的地方去,好比:less
new Scaffold( appBar: new AppBar( backgroundColor: Colors.deepOrange, title: new Text('title'), ), .... body: new TabBarView( //TabBarView呈现内容,所以放到Scaffold的body中 controller: _bottomNavigation, //配置控制器 children: [ //注意顺序与TabBar保持一直 new News(data: '参数值'), //上一篇定义好的子页面 new TabPage2(), new TabPage3(), ] ), bottomNavigationBar: new Material( //为了适配主题风格,包一层Material实现风格套用 color: Colors.deepOrange, //底部导航栏主题颜色 child: new TabBar( //TabBar导航标签,底部导航放到Scaffold的bottomNavigationBar中 controller: _bottomNavigation, //配置控制器 tabs: _bottomTabs, indicatorColor: Colors.white, //tab标签的下划线颜色 ), ) );
DefaultTabController
DefaultTabController
要简单不少,因为应用在无状态控件中,使用DefaultTabController
包裹须要用到Tab
的页面便可:异步
class TabPage3 extends StatelessWidget { @override Widget build(BuildContext context) { return new DefaultTabController( length: 3, child: new Scaffold( appBar: new AppBar( backgroundColor: Colors.orangeAccent, title: new TabBar( tabs: [ new Tab(icon: new Icon(Icons.directions_car)), new Tab(icon: new Icon(Icons.directions_transit)), new Tab(icon: new Icon(Icons.directions_bike)), ], indicatorColor: Colors.white, ), ), body: new TabBarView( children: [ new Icon(Icons.directions_car), new Icon(Icons.directions_transit), new Icon(Icons.directions_bike), ], ), ), ); } }
DefaultTabController
和TabController
的用法差别主要在控制器的定义上,TabBar
和TabBarView
的使用方法彻底相同,一般上下Tab
页的标签分别安放在Scaffold
控件的appBar和bottomNavigationBar属性上,而后咱们把APP填充成下面这个样式:ide
如上图所示,APP以底部Tab
导航栏为主入口,底部每一个Tab
中又各自有本身的顶部次级Tab
页,所以咱们把代码结构整理一下:
_HomePageState
是APP的主页面HomePage
的子类State
Scaffold
底部的bottomNavigationBar属性摆放TabBar
,搭建Tab
页的标签栏Scaffold
的body属性放入TabBarView
,TabBarView
的children是三个外部dart文件定义的控件页面Tab
标签页Tab
页的通用属性能够提早定义到数组List
中,在TabBar
和TabBarView
经过遍历提取List
的值建立子元素能够提升代码的维护效率://在`StatefulWidget`控件中,可经过修改下面的数组,实现Tab页的动态加载 final List<Tab> myTabs = <Tab>[ new Tab(text: 'Tab1'), new Tab(text: 'Tab2'), new Tab(text: 'Tab3'), new Tab(text: 'Tab4'), new Tab(text: 'Tab5'), new Tab(text: 'Tab6'), new Tab(text: 'Tab7'), new Tab(text: 'Tab8'), new Tab(text: 'Tab9'), new Tab(text: 'Tab10'), new Tab(text: 'Tab11'), ]; Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar( backgroundColor: Colors.orangeAccent, title: new TabBar( controller: _tabController, tabs: myTabs, //使用Tab类型的数组呈现Tab标签 indicatorColor: Colors.white, isScrollable: true, ), ), body: new TabBarView( controller: _tabController, children: myTabs.map((Tab tab) { //遍历List<Tab>类型的对象myTabs并提取其属性值做为子控件的内容 return new Center(child: new Text(tab.text+' '+widget.data)); //使用参数值 }).toList(), ), ); }
因为StatelessWidget
和StatefulWidget
的页面构建不一样,使用从外部获取到的参数的方式也略有差别,在这里简单总结下。
StatelessWidget
的获参和用参方式定义StatelessWidget
控件时,添加一个final
类型的变量如pageText
,用于为参数值预留空间,并在构造函数中加入参数值:
class SidebarPage extends StatelessWidget { final String pageText; //定义一个常量,用于保存跳转进来获取到的参数 SidebarPage(this.pageText); //构造函数,获取参数 ... }
使用参数时直接引用便可:
Widget build(BuildContext context) { return new Scaffold( appBar: new AppBar(title: new Text(pageText),), //将参数看成页面标题 body: new Center( child: new Text('pageText'), ), ); }
从外部传入参数时,直接向构造函数中填入参数值便可:
Navigator.of(context).push(new MaterialPageRoute(builder: (BuildContext context) => new SidebarPage('First Page'))); //在new方法时调用控件的构造函数传入参数值
StatefulWidget
的获参和用参方式相比StatelessWidget
略复杂。定义构造函数时须要默认声明key:
class TabPage1 extends StatefulWidget { const TabPage1({ Key key , this.data}) : super(key: key); //构造函数中增长参数 final String data; //为参数分配空间 @override _MyTabbedPageState createState() => new _MyTabbedPageState(); }
使用时,因为在State
子类中实现具体的页面内容,所以State
子类使用父类TabPage1
的参数时须要在参数名前增长一个widget关键字:
class _MyTabbedPageState extends State<TabPage1> { ... new Center(child: new Text(tab.text+' '+widget.data)); //使用参数值,需在参数名前增长widget前缀 ... }
从外部传入参数时,须要声明参数名:
new TabBarView controller: _bottomNavigation, children: [ new TabPage1(data: '参数值'), //new方法调用构造函数时,还须要声明参数名称 new TabPage2(), new TabPage3(), ] )
好勒,今天就讲到这里,你们去下载个人git源码试试效果吧,代码中有附加的注释,对一些控件属性的特性也有单独的描述,相信看完源码以后,你们也能够自行实现效果了。
顺便分享一个雷区,因为当初建立这个项目时,我使用命令flutter create [APPname1]的方式建立了这个项目,但我发现这个APPname1(代称,并不是真实名称)很差听,想把项目更名为APPname2,因而参考以前写的安卓怎么打包?,把项目文件夹更名为APPname2,并不是常任性的把项目目录下的_androidappsrcmainAndroidManifest.xml_文件,把package和android:label都改为了APPname2,因而不出意料的悲催了,命令flutter fun报错,没法启动APP,还原配置以后,没法启动APP,即使尝试经过全文搜索APPname1,都按规定格式替换成APPname2,或者逆向改回去,都没法启动APP,此时已经是凌晨1点。。。妥妥的血泪史,因此郑重的告诫你们:
不要在项目的各类配置文件中轻易改动项目名称!不要在项目的各类配置文件中轻易改动项目名称!不要在项目的各类配置文件中轻易改动项目名称! 不然你就是下一个在电脑面前捶胸顿足的鱼丸。什么?问我怎么恢复的?固然是托git的福。
感谢你们的支持,请关注个人Flutter圈子,多多投稿,也能够加入flutter 中文社区(官方QQ群:338252156)共同成长,谢谢你们~