底Tab
Flutter已经为底Tab提供了基础类,分别是BottomNavigationBar表示整个底Tab控件和BottomNavigationBarItem表示每个tab按钮,其二者和Scaffold结合使用。Scaffold有个bottomNavigationBar属性,value使用一个BottomNavigationBar对象,而body则可用任意一个Widget作为content区。BottomNavigationBar的type是BottomNavigationBarType枚举类型的,有fixed和shifting两种取值。onTap属性表示的是bar的点击监听函数,入参index表示点击的是哪个tab。currentIndex表示当前哪个tab是active状态。selectedItemColor和unselectedItemColor分别表示active和非active状态的item的icon使用什么颜色。最后是最重要的items属性,是一个BottomNavigationBarItem的数组,BottomNavigationBarItem有4个属性,分别是必传的icon表示tab item的图标,Text类型的title,active状态下的图标activeIcon以及背景色backgroundColor。
为了实现点击tab切换的目的,可在BottomNavigationBar的onTap函数中使用setState来修改BottomNavigationBar的currentIndex属性和Caffold的body属性。
var _bottomNavigationIndex = 0;
var _homePage = Container(color: Colors.red, child: Center(child: Text("home")),);
var _videoPage = Container(color: Colors.green, child: Center(child: Text("video")),);
var _hotPage = Container(color: Colors.blue, child: Center(child: Text("hot")),);
var _myPage = Container(color: Colors.grey, child: Center(child: Text("my")),);
var _pages = <Widget>[];
var _curPage = null;
Widget bottomTabDemo(BuildContext context) {
if (_curPage == null) {
_curPage = _homePage;
}
if (_pages.length == 0) {
_pages.addAll([_homePage, _videoPage, _hotPage, _myPage]);
}
return Scaffold(bottomNavigationBar: BottomNavigationBar(
onTap: (index) {
_curPage = _pages[index];
setState(() {
_bottomNavigationIndex = index;
});
},
type: BottomNavigationBarType.fixed,
currentIndex: _bottomNavigationIndex,
selectedItemColor: Theme.of(context).primaryColor,
unselectedItemColor: Colors.black,
items: [
BottomNavigationBarItem(title: Text("首页"), icon: Icon(Icons.home)),
BottomNavigationBarItem(title: Text("视频"), icon: Icon(Icons.video_call)),
BottomNavigationBarItem(title: Text("热点"), icon: Icon(Icons.bookmark)),
BottomNavigationBarItem(title: Text("我的"), icon: Icon(Icons.person))
]),
body: _curPage,
);
}
Tab控件
Tab控件主要应用于底Tab或者频道栏,对于Android原生来说,底Tab一般用TabHost实现,而频道栏一般因为数量较多并可滑动,采用RecyclerView定制一个横向列表更为方便。Flutter的底Tab已经为我们提供了BottomNavigationBar,而像频道栏那种场景也为我们提供了TabBar控件, TabBar需要跟TabBarView一起使用,TabBar定义tab的标题样式,相当于RecycleView,而TabBarView则是Tab的content,用来在切换tab时更新view的。TabBar和TabBarView使用TabController来连接,TabBar和TabBarView都有一个controller属性,填同一个TabBarController对象后当TabBar选中的Tab变化时,TabBarView显示的child也会跟着变化,同样的,在TabBarView区域内手势滑动导致的child变化,也会导致TabBar的tab变化。
var _tabController;
var _tabs = const ["推荐", "视频", "科技", "财经", "热点", "动漫", "小视频", "文化", "房产", "电视剧", "电影"];
Widget tabDemo(BuildContext context) {
if (_tabController == null) {
_tabController = TabController(length: _tabs.length, vsync: ScrollableState());
}
return Container(padding: EdgeInsets.only(top: 30), child: Column(children: <Widget>[
TabBar(tabs: _tabs.map((tab) {
return Text("$tab");
}).toList(),
controller: _tabController,
indicatorColor: Colors.redAccent,
indicatorSize: TabBarIndicatorSize.label,
isScrollable: true,
labelColor: Colors.redAccent,
unselectedLabelColor: Colors.black,
indicatorWeight: 5.0,
labelStyle: TextStyle(height: 2),
),
Expanded(
child: TabBarView(
controller: _tabController,
children: _tabs.map((tab) {
return Center(child: Text("$tab"),);
}).toList(),))
],),);
}
这里还有一个问题,如果把TabBarView直接作为Column的child,会运行异常,因为TabBarView无法确定其大小,就算再包一层Container也一样会抛异常,除非设置height和width。这里用Expanded之所有能解决抛异常,是因为Expanded可以占据Column的剩余空间,相当于Expanded能够自动确认自身大小,这样TabBarView的大小也就确认了。
吸顶效果控件SliverAppBar
所谓吸顶,就是一个大图+一个列表,列表上推时大图收起,列表下拉时大图展开,充分利用有限的手机屏幕空间。 Flutter的吸顶效果可用CustomScrollView+SliverAppBar实现,CustomScrollView有一个slivers属性,是个Widget的数组,可放入一个SliverAppBar+一个ListView,当ListView滑动时,SliverAppBar就可作出相应的吸顶收放效果。吸顶效果主要是SliverAppBar的属性来定制,最主要的就是flexibleSpace和expandedHeight,分别表示吸顶的布局和展开区域的高度,也就是大图的布局在flexibleSpace中实现,而大图的高度由expandedHeight决定,还有一些其他的额外属性可定制一些特殊的效果,比如floating可决定是否在下拉时直接下拉出顶部。
sliverBarDemo() {
return CustomScrollView(
slivers: <Widget>[
SliverAppBar(
expandedHeight: 200,
flexibleSpace: FlexibleSpaceBar(
title: Text("Sliver Demo"),
background: Image.asset("imgs/girl.jpg", fit: BoxFit.fitHeight)
),
floating: true,
snap: true,
),
SliverFixedExtentList(delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return Card(
child: Container(color: Colors.primaries[index % 18], child: Text("$index"), alignment: Alignment.center,),
);
}
), itemExtent: 80.0)
],
);
}
轮播图
Flutter的轮播图是PageView,由于PageView也具有items,因此被设计了builder方法,可以使用PageView.builder函数来构建一个PageView对象。对于轮播图需要设计一个指示小点,可以通过监听轮播图的页面切换,并用Stack控件将PageView和一个Row控件叠加起来,当页面切换时,改变Row中的小点颜色即可。
var pageList = [Center(child: Text("page1")), Center(child: Text("page2")), Center(child: Text("page3"))];
var _pageIndex = 0;
pageViewDemo() {
return Container(
color: Colors.grey,
height: 100,
child: Stack(children: <Widget>[
PageView.builder(
itemCount: 100,
controller: PageController(viewportFraction: 0.9),
onPageChanged: (index) {
setState(() {
_pageIndex = index % pageList.length;
});
},
itemBuilder: (context, index) {
return pageList[index % pageList.length];
}),
Positioned(bottom: 10, left: 0, right: 0, child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children:
List.generate(pageList.length, (index) {
return Container(
width: 10,
height: 10,
margin: EdgeInsets.symmetric(horizontal: 5),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: _pageIndex == index? Colors.deepOrangeAccent: Colors.blueGrey),
);
}).toList()
))
],)
);
}