上图
需要实现的功能:
- 底部tab(可自定义样式)点击切换中间内容
- 不要一般的tab切换时相隔几个页面会缓慢过渡的问题(一般bottomNavigationBar+TabBarView实现会存在的问题)
- 中间内容不可滑动
- 切换页面时页面不重建
实现过程
- 一个Scaffold+bottomNavigationBar+pageView
Tab(child: _nowIndex==0?Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Image.asset("images/nav_home_h.png",width: 25,height: 25,fit: BoxFit.fill,),
Text("首页",style: TextStyle(decoration: TextDecoration.none,color: Colors.blue,fontSize: 14),)
],
):Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Image.asset("images/nav_home.png",width: 25,height: 25,fit: BoxFit.fill,),
Text("首页",style: TextStyle(decoration: TextDecoration.none,color: Colors.grey,fontSize: 14),)
],
),)
可自定义tabbar样式就需要使用tab的child属性,使用_nowIndex记录当前页面切换到那一个页面了,然后使用三目运算符针对不同的页面赋值不同的child,flutter中都是一个个widget,所以tab的样式可以随心所欲,这里我只是实现了与使用tab的text和icon属性一样的效果
2.一般使用bottomNavigationBar+TabBarView假设我们有4个页面,当前我们再第4个页面,然后我们点击tab切换到第一个页面,会出现一个效果就是一次从3切换到2再切换到1,虽然这个过程是很快的,但是也丢失了android原生的fragment切换效果,所以我们这里采用pageView来实现中间页面的展示。
body: PageView(
controller: _pageController,
children: [
HomePageTab(),
ThingsPageTab(),
MePageTab()
],
// 这里很重要就是需要实现的第三个要求
physics: NeverScrollableScrollPhysics(),
),
然后就是tabbar和pageview的控制器了,这一步就是将点击底部,同时也切换page连接起来。
TabController _tabController;
PageController _pageController;
var _nowIndex =0;
@override
void initState() {
// TODO: implement initState
super.initState();
//创建页面的控制器
_pageController = PageController();
//创建底部tab的控制器,并且设置tab改变的监听
_tabController = TabController(length: 3, vsync: this)..addListener((){
// 当tab改变时,使用页面的controller跳转到指定的页面,这样就不会有依次切换的效果
_pageController.jumpToPage(_tabController.index);
// 使用setstate 设置数据记录当前切换到哪一个页面,自动重绘底部tab(响应式)
setState(() {
_nowIndex = _tabController.index;
});
});
}
3.这时候pageview还是能滑动切换的,如果需要在滑动切换后同时切换底部tab可以使用pageView的onChange属性,在onChange时同时响应式设置 _nowIndex的值,就可以达到同步,但是这里我选择直接禁用pageView的滑动,就是使用pageview的''physics: NeverScrollableScrollPhysics(),''这个属性就可以设置不可以滑动切换,请看第2点的第一段代码
4.接下来就是点击底部tab切换时,每一次页面都会新建和销毁,就好像android原生的fragment的replace方法,在android原生中我们使用hide和show的方式来防止重建,那么我们再flutter中如何实现呢,就是在每一个子页面的state类后面混合AutomaticKeepAliveClientMixin
class ThingsPageTab extends StatefulWidget {
@override
_ThingsPageTabState createState() => _ThingsPageTabState();
}
// 1.防止重建加with AutomaticKeepAliveClientMixin
class _ThingsPageTabState extends State with AutomaticKeepAliveClientMixin{
@override
Widget build(BuildContext context) {
return Text("things",style: TextStyle(fontSize: 50),);
}
// 2.防止重建必须要的实现 返回true
@override
// TODO: implement wantKeepAlive
bool get wantKeepAlive => true;
}
完结
- 这样整个功能就实现了,贴主页完整的代码
import 'package:flutter/material.dart';
import 'package:jygonline/ui/fragment/home_page.dart';
import 'package:jygonline/ui/fragment/things_page.dart';
import 'package:jygonline/ui/fragment/me_page.dart';
class MainPage extends StatefulWidget {
@override
_MainPageState createState() => _MainPageState();
}
class _MainPageState extends State with SingleTickerProviderStateMixin{
TabController _tabController;
PageController _pageController;
var _nowIndex =0;
@override
void initState() {
// TODO: implement initState
super.initState();
_pageController = PageController();
_tabController = TabController(length: 3, vsync: this)..addListener((){
_pageController.jumpToPage(_tabController.index);
setState(() {
_nowIndex = _tabController.index;
});
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
bottomNavigationBar: Container(
height: 60,
color: Colors.white,
child: TabBar(tabs: [
Tab(child: _nowIndex==0?Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Image.asset("images/nav_home_h.png",width: 25,height: 25,fit: BoxFit.fill,),
Text("首页",style: TextStyle(decoration: TextDecoration.none,color: Colors.blue,fontSize: 14),)
],
):Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Image.asset("images/nav_home.png",width: 25,height: 25,fit: BoxFit.fill,),
Text("首页",style: TextStyle(decoration: TextDecoration.none,color: Colors.grey,fontSize: 14),)
],
),),
Tab(child: _nowIndex==1?Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Image.asset("images/nav_bs_h.png",width: 25,height: 25,fit: BoxFit.fill,),
Text("办事",style: TextStyle(decoration: TextDecoration.none,color: Colors.blue,fontSize: 14),)
],
):Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Image.asset("images/nav_bs.png",width: 25,height: 25,fit: BoxFit.fill,),
Text("办事",style: TextStyle(decoration: TextDecoration.none,color: Colors.grey,fontSize: 14),)
],
),),
Tab(child: _nowIndex==2?Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Image.asset("images/nav_my_h.png",width: 25,height: 25,fit: BoxFit.fill,),
Text("个人中心",style: TextStyle(decoration: TextDecoration.none,color: Colors.blue,fontSize: 14),)
],
):Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Image.asset("images/nav_my.png",width: 25,height: 25,fit: BoxFit.fill,),
Text("个人中心",style: TextStyle(decoration: TextDecoration.none,color: Colors.grey,fontSize: 14),)
],
),),
],
controller: _tabController,
indicatorColor: Colors.white,
isScrollable: false,
indicatorWeight: 0.01,
),
),
body: PageView(
controller: _pageController,
children: [
HomePageTab(),
ThingsPageTab(),
MePageTab()
],
physics: NeverScrollableScrollPhysics(),
),
);
}
}