在公司开发的第一个 Flutter 项目告一段落,准备将一些值得分享的整理到博客,今天是 TabBarView
。它相当一个单独的 View Page,可以和底部导航栏 BottomNavigationBar 结合使用,也可以和顶部导航栏 TabBar 关联使用,它们的默认用法虽然简单,但不好看,如果想满足 UI 要求,肯定要对各种属性了解。其次涉及到项目数据接口请求,数据初始化、懒加载等等,如何实现比较好,这是我今天要分享的。
TabBar
TabBar ` 就是顶部导航栏,包括导航栏标题和指示器,里面属性很多,这里列出我使用到的
TabBar(
tabs: _myTabs, //标题,使用 Tab 构造
isScrollable: false, //是否可以滑动,标题左右滑动
padding: EdgeInsets.only(top: 32,left: 65,right: 65),
indicatorWeight: 1, //指示器高度
indicator: UnderlineTabIndicator(
borderSide: BorderSide(width: 2,color: Colors.white),
insets: EdgeInsets.only(left: 42,right: 42)
),
labelColor: Colors.white, //标题选择时颜色
unselectedLabelColor: Color(0xFFABABAB), //未被选择时颜色
labelStyle: TextStyle(fontSize: 15), //被选择时label风格样式
unselectedLabelStyle: //未被选择时风格,比如文本变大变小
),
tabs
是标题,我这里只构造纯文本 Tab,它还能搭配图标。
final List<Tab> _myTabs = <Tab>[
Tab(text: "动态",),
Tab(text: "推荐",),
];
padding
,如果不加左右 Padding ,这俩标题显示如下。
指示器高度 indicatorWeight
,但使用自定义的指示器后,这个设置不会生效。与它对应的还有一个指示器长度,但只提供了两种固定好的 tab、label 常量。如果我们想设置具体长度就不行。
indicatorSize: TabBarIndicatorSize.tab,
所以我们最好要自定义指示器 indicator
的长度/宽度和高度,这就不能用默认指示器,使用 UnderlineTabIndicator
。
indicator: UnderlineTabIndicator(
borderSide: BorderSide(width: 2,color: Colors.white), //高度和颜色
insets: EdgeInsets.only(left: 42,right: 42) //左右间距反向设置长度
),
TabBarView
它是整一个页面 View,是一个 Page,可以放任何东西。TabBarView
要注意的属性就是俩,children
和 controller
。我的页面放了一张图做演示。
TabBarView(
children: [MomentPage(),RecommendedPage()],
),
class MomentPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.only(top: 10),
width: double.infinity,
decoration: BoxDecoration(
image: DecorationImage(
alignment: Alignment.topCenter,
image: AssetImage(HomePage.assetsImage1),
),
),
);
}
}
其实,TabBarView
和 TabBar
都有 controller
属性,需要填入 TabController
,它是关联 TabBarView
和 TabBar
的控制器。我们可以不实现 controller
,只要最外层使用了 DefaultTabController
。
class HomePage extends StatelessWidget {
final List<Tab> _myTabs = <Tab>[
Tab(text: "动态",),
Tab(text: "推荐",),
];
@override
Widget build(BuildContext context) {
return DefaultTabController(
length: 2,
child: Scaffold(
backgroundColor: Color(0xFF16181F),
appBar: TabBar(
tabs: _myTabs,
isScrollable: false,
padding: EdgeInsets.only(top: 32,left: 65,right: 65),
indicatorWeight: 1,
indicatorSize: TabBarIndicatorSize.label,
indicator: UnderlineTabIndicator(
borderSide: BorderSide(width: 2,color: Colors.white),
insets: EdgeInsets.only(left: 42,right: 42)
),
labelColor: Colors.white,
unselectedLabelColor: Color(0xFFABABAB),
labelStyle: TextStyle(fontSize: 15),
),
body: TabBarView(
children: [MomentPage(),RecommendedPage()],
),
),
);
}
}
懒加载数据
像这种 TabPage
,基本都会请求数据,但又分为很多种情况,我们不可能构造出 TabView 后,一次性将所有页面的接口都请求完成,这种体验就不好了。最好还是希望能做到,当点击 TabBar
时,再请求数据。并且只是第一次点击时请求数据,后面再次点击不再请求接口。
class _MomentPageState extends State<MomentPage> {
@override
void initState() {
print('请求数据1');
super.initState();
}
}
直接在 initState
中使用应该会报错,而且每次切换都会重新请求,拉取数据,甚至会销毁一些状态。所以我们需要 TabBar 每次切换页面不重新加载。这里需要用到 AutomaticKeepAliveClientMixin
。
class _MomentPageState extends State<MomentPage> with AutomaticKeepAliveClientMixin{
@override
void initState() {
print('请求数据1');
super.initState();
}
@override
bool get wantKeepAlive => true;
}
将你不需要重新加载的 TabBarView 页面实现 AutomaticKeepAliveClientMixin
,如果两个页面都配置,则每次切换打印如下
flutter: 请求数据1
flutter: 请求数据2
如果只配置第一个页面,第二个不配置,每次点击第二个 Tab 都会重新请求,加载状态
flutter: 请求数据1
flutter: 请求数据2
flutter: 请求数据2
但这样还不行,因为如果你是请求接口,获取数据,刷新界面的流程,可能会报错。我们可以使用 addPostFrameCallback
,它是指界面 build 完成后再执行回调。
WidgetsBinding.instance.addPostFrameCallback((_) {
print('请求数据1');
});
TabController
上面的代码都没使用自定义 TabController
,直接将默认的 DefaultTabController
放到最外层,包裹 TabBar
和 TabBarView
,也能实现它们的联动。但这还不够,如果想监听 TabBar
的滑动,监听 TabBarView
的变化。还是要用到自定义 TabController
。
这里涉及状态变化,将 HomePage 从 StatelessWidget
修改成 StatefulWidget
。然后混入TickerProviderStateMixin
。
class _HomePageState extends State<HomePage> with TickerProviderStateMixin{}
@override
void initState() {
///初始化构造
_tabController = TabController(vsync: this, length: 2,initialIndex: 0);
///添加监听
///_tabController(() => _handleTabChange());
_tabController.addListener((){
if(_tabController.indexIsChanging){
_handleTabChange();
}
});
super.initState();
}
void _handleTabChange() {
if (_tabController.index == 0) {
print('请求数据1');
} else if (_tabController.index == 1) {
print('请求数据2');
}
}
这里貌似有一个 bug,如果直接使用 _tabController(() => _handleTabChange());
添加监听,切换时会执行两次。如果你也碰到这种情况,可以用我上面提供的代码。
给 TabBar
和 TabBarView
添加自定义的 TabController
。
appBar: TabBar(
...
controller: _tabController,
),
body: TabBarView(
...
controller: _tabController,
),
OK,以上就是要分享,希望能帮到各位。
本文由老郭种树原创,转载请注明:https://guozh.net/flutter-tabbarview/