跳到正文
Willh的个人博客 记录 · 思考 · 分享
《Flutter实战·第二版》

6.9 TabBarView

TabBarView 是 Material 组件库中提供了 Tab 布局组件,通常和 TabBar 配合使用。

6.9.1 TabBarView

TabBarView 封装了 PageView,它的构造方法很简单

1 TabBarView({
2  Key? key,
3  required this.children, // tab 页
4  this.controller, // TabController
5  this.physics,
6  this.dragStartBehavior = DragStartBehavior.start,
7}) 

TabController 用于监听和控制 TabBarView 的页面切换,通常和 TabBar 联动。如果没有指定,则会在组件树中向上查找并使用最近的一个 DefaultTabController 。

6.9.2 TabBar

TabBar 为 TabBarView 的导航标题,如下图所示:

image-20210822144239879

TabBar 有很多配置参数,通过这些参数我们可以定义 TabBar 的样式,很多属性都是在配置 indicator 和 label,拿上图来举例,Label 是每个Tab 的文本,indicator 指 “历史” 下面的白色下划线。

1const TabBar({
2  Key? key,
3  required this.tabs, // 具体的 Tabs,需要我们创建
4  this.controller,
5  this.isScrollable = false, // 是否可以滑动
6  this.padding,
7  this.indicatorColor,// 指示器颜色,默认是高度为2的一条下划线
8  this.automaticIndicatorColorAdjustment = true,
9  this.indicatorWeight = 2.0,// 指示器高度
10  this.indicatorPadding = EdgeInsets.zero, //指示器padding
11  this.indicator, // 指示器
12  this.indicatorSize, // 指示器长度,有两个可选值,一个tab的长度,一个是label长度
13  this.labelColor, 
14  this.labelStyle,
15  this.labelPadding,
16  this.unselectedLabelColor,
17  this.unselectedLabelStyle,
18  this.mouseCursor,
19  this.onTap,
20  ...
21}) 

TabBar 通常位于 AppBar 的底部,它也可以接收一个 TabController ,如果需要和 TabBarView 联动, TabBar 和 TabBarView 使用同一个 TabController 即可,注意,联动时 TabBar 和 TabBarView 的孩子数量需要一致。如果没有指定 controller,则会在组件树中向上查找并使用最近的一个 DefaultTabController 。另外我们需要创建需要的 tab 并通过 tabs 传给 TabBar, tab 可以是任何 Widget,不过Material 组件库中已经实现了一个 Tab 组件,我们一般都会直接使用它:

1const Tab({
2  Key? key,
3  this.text, //文本
4  this.icon, // 图标
5  this.iconMargin = const EdgeInsets.only(bottom: 10.0),
6  this.height,
7  this.child, // 自定义 widget
8})

注意,text 和 child 是互斥的,不能同时制定。

6.9.3 实例

下面我们看一个例子:

1class TabViewRoute1 extends StatefulWidget {
2  
3  _TabViewRoute1State createState() => _TabViewRoute1State();
4}
5
6class _TabViewRoute1State extends State<TabViewRoute1>
7    with SingleTickerProviderStateMixin {
8  late TabController _tabController;
9  List tabs = ["新闻", "历史", "图片"];
10
11  
12  void initState() {
13    super.initState();
14    _tabController = TabController(length: tabs.length, vsync: this);
15  }
16
17  
18  Widget build(BuildContext context) {
19    return Scaffold(
20      appBar: AppBar(
21        title: Text("App Name"),
22        bottom: TabBar(
23          controller: _tabController,
24          tabs: tabs.map((e) => Tab(text: e)).toList(),
25        ),
26      ),
27      body: TabBarView( //构建
28        controller: _tabController,
29        children: tabs.map((e) {
30          return KeepAliveWrapper(
31            child: Container(
32              alignment: Alignment.center,
33              child: Text(e, textScaleFactor: 5),
34            ),
35          );
36        }).toList(),
37      ),
38    );
39  }
40  
41  
42  void dispose() {
43    // 释放资源
44    _tabController.dispose();
45    super.dispose();
46  }
47}

运行后效果如下:

TabBarView

滑动页面时顶部的 Tab 也会跟着动,点击顶部 Tab 时页面也会跟着切换。为了实现 TabBar 和 TabBarView 的联动,我们显式创建了一个 TabController,由于 TabController 又需要一个 TickerProvider (vsync 参数), 我们又混入了 SingleTickerProviderStateMixin;由于 TabController 中会执行动画,持有一些资源,所以我们在页面销毁时必须得释放资源(dispose)。综上,我们发现创建 TabController 的过程还是比较复杂,实战中,如果需要 TabBar 和 TabBarView 联动,通常会创建一个 DefaultTabController 作为它们共同的父级组件,这样它们在执行时就会从组件树向上查找,都会使用我们指定的这个 DefaultTabController。我们修改后的实现如下:

1class TabViewRoute2 extends StatelessWidget {
2  
3  Widget build(BuildContext context) {
4    List tabs = ["新闻", "历史", "图片"];
5    return DefaultTabController(
6      length: tabs.length,
7      child: Scaffold(
8        appBar: AppBar(
9          title: Text("App Name"),
10          bottom: TabBar(
11            tabs: tabs.map((e) => Tab(text: e)).toList(),
12          ),
13        ),
14        body: TabBarView( //构建
15          children: tabs.map((e) {
16            return KeepAliveWrapper(
17              child: Container(
18                alignment: Alignment.center,
19                child: Text(e, textScaleFactor: 5),
20              ),
21            );
22          }).toList(),
23        ),
24      ),
25    );
26  }
27}

可以看到我们无需去手动管理 Controller 的生命周期,也不需要提供 SingleTickerProviderStateMixin,同时也没有其它的状态需要管理,也就不需要用 StatefulWidget 了,这样简单很多。

页面缓存

因为TabBarView 内部封装了 PageView,如果要缓存页面,可以参考 PageView 一节中关于页面缓存的介绍。