iOS-Flutter TabBarView

79 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 31 天,点击查看活动详情

TabBarView

TabBarView 封装了PageView。

TabBarView({
  Key? key,
  required this.children, // tab 页
  this.controller, // TabController
  this.physics,
  this.dragStartBehavior = DragStartBehavior.start,
}) 

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

TabBar

TabBar为TbaBarView的导航标题,类似于新闻头条的标题导航栏。

const TabBar({
  Key? key,
  required this.tabs, // 具体的 Tabs,需要我们创建
  this.controller,
  this.isScrollable = false, // 是否可以滑动
  this.padding,
  this.indicatorColor,// 指示器颜色,默认是高度为2的一条下划线
  this.automaticIndicatorColorAdjustment = true,
  this.indicatorWeight = 2.0,// 指示器高度
  this.indicatorPadding = EdgeInsets.zero, //指示器padding
  this.indicator, // 指示器
  this.indicatorSize, // 指示器长度,有两个可选值,一个tab的长度,一个是label长度
  this.labelColor, 
  this.labelStyle,
  this.labelPadding,
  this.unselectedLabelColor,
  this.unselectedLabelStyle,
  this.mouseCursor,
  this.onTap,
  ...
}) 

TabBar也有一个TabController,如果需要和TabBarView联动,可以使它们共用同一个TanController即可。需要注意的是联动时TabBar和TabBarView的子组件数量需保持一致。此外需要创建的tab并通过tabs传递给TabBar,tab可以是任意的Widget,不过Material组件库中已经实现了Tab组件:

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

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

实例:

class TabViewRoute1 extends StatefulWidget {
  @override
  _TabViewRoute1State createState() => _TabViewRoute1State();
}

class _TabViewRoute1State extends State<TabViewRoute1>
    with SingleTickerProviderStateMixin {
  late TabController _tabController;
  List tabs = ["新闻", "历史", "图片"];

  @override
  void initState() {
    super.initState();
    _tabController = TabController(length: tabs.length, vsync: this);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("App Name"),
        bottom: TabBar(
          controller: _tabController,
          tabs: tabs.map((e) => Tab(text: e)).toList(),
        ),
      ),
      body: TabBarView( //构建
        controller: _tabController,
        children: tabs.map((e) {
          return KeepAliveWrapper(
            child: Container(
              alignment: Alignment.center,
              child: Text(e, textScaleFactor: 5),
            ),
          );
        }).toList(),
      ),
    );
  }
  
  @override
  void dispose() {
    // 释放资源
    _tabController.dispose();
    super.dispose();
  }
}

滑动页面时,顶部的Tab也会跟着动,点击顶部Tab时页面也会跟着切换。为了实现TabBar和TabBarView的联动,显式创建一个TabController,TabController需要一个Ticker Provider(vsync参数),又混入了SingleTickerProviderStateMixin;由于TabController中会执行动画,持有一些资源,在页面销毁时需要释放资源(dispose),也就是上面的代码思路。实际开发中,通常会创建一个DefaultTabController作为它们共同的父级组件,这样在执行时就会从组件树向上查找,都会使用指定的这个DefaultTabController。

class TabViewRoute2 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    List tabs = ["新闻", "历史", "图片"];
    return DefaultTabController(
      length: tabs.length,
      child: Scaffold(
        appBar: AppBar(
          title: Text("App Name"),
          bottom: TabBar(
            tabs: tabs.map((e) => Tab(text: e)).toList(),
          ),
        ),
        body: TabBarView( //构建
          children: tabs.map((e) {
            return KeepAliveWrapper(
              child: Container(
                alignment: Alignment.center,
                child: Text(e, textScaleFactor: 5),
              ),
            );
          }).toList(),
        ),
      ),
    );
  }
}

这样还无需手动的管理Controller的生命周期,也不需要提供SingleTickerPrividerStateMixin,同时也没有其他的状态需要管理,也不需要额外的创建StatefulWidget。从这可以看出Scaffold的强大之处。