[Flutter 基础] - Tab 组件-玩转导航栏

1,502 阅读4分钟

在 Flutter 中,Tab(标签页) 是实现多页面切换的常见方式,通常与 AppBarTabBarTabBarView 结合使用。Tab的提供了丰富的属性,通过灵活使用这些属性基本上可以满足我们日常开发中的大部分需求。 以下是关于 Tab 的详细用法,包括基础配置、复杂用法和 底部Tab的实现。


一、基础用法

1. 核心组件

  • DefaultTabController: 管理 Tab 的状态和切换(适用于简单场景)。
  • TabBar: 显示一组水平排列的标签(通常放在 AppBarbottom 属性中)。
  • TabBarView: 显示与 TabBar 对应的内容区域。
  • Tab: 单个标签项,可以自定义图标和文字。

2. 基本步骤

顶部展示一个tab分页,这种场景常见于订单或者是新闻等分类业务。

top_tab.gif

import 'package:flutter/material.dart';

class TabDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return DefaultTabController(
      length: 3, // Tab 的数量
      child: Scaffold(
        appBar: AppBar(
          title: Text('Tab 示例'),
          bottom: TabBar(
            tabs: [
              Tab(text: '首页', icon: Icon(Icons.home)),
              Tab(text: '消息', icon: Icon(Icons.message)),
              Tab(text: '设置', icon: Icon(Icons.settings)),
            ],
          ),
        ),
        body: TabBarView(
          children: [
            Center(child: Text('首页内容')),
            Center(child: Text('消息内容')),
            Center(child: Text('设置内容')),
          ],
        ),
      ),
    );
  }
}

二、自定义 Tab 样式

1. 修改标签样式

tab_indicator-ezgif.com-video-to-gif-converter.gif

TabBar(
  tabs: [...],
  indicatorColor: Colors.red, // 指示器颜色
  indicatorWeight: 4,         // 指示器厚度
  indicatorSize: TabBarIndicatorSize.label, // 指示器长度(label: 与文本同宽)
  labelColor: Colors.black,   // 选中标签颜色
  unselectedLabelColor: Colors.grey, // 未选中标签颜色
  labelStyle: TextStyle(fontWeight: FontWeight.bold), // 标签文本样式
)

2. 自定义 Tab 内容

支持任意 Widget(不仅仅是文字和图标),所以可以通过这个child自定义自己想要的tab样式:

image.png

bottom: TabBar(
  tabs: [
    Tab(
      child: Row(
        children: [
          Icon(Icons.check),
          SizedBox(width: 4),
          Text('全部'),
        ],
      ),
    ),
    Tab(text: '未完成'),
    Tab(text: '进行中'),
    Tab(text: '已完成'),
  ],
  indicatorColor: Colors.red, // 指示器颜色
  indicatorWeight: 4,         // 指示器厚度
  indicatorSize: TabBarIndicatorSize.label, // 指示器长度(label: 与文本同宽, tab: 占整个tab的宽度)
  labelColor: Colors.black,   // 选中标签颜色
  unselectedLabelColor: Colors.grey, // 未选中标签颜色
  labelStyle: TextStyle(fontWeight: FontWeight.bold), // 标签文本样式
),

三、复杂场景

Tabbar的使用场景非常丰富,经常伴随复杂的业务场景需要灵活的通过代码去控制tab的选中状态,比如通过某个订单的状态索引到不同的tab下面,这个时候我们就可以通过TabController来达到这个效果。

1. 手动控制 TabController

点击查看已完成订单会自动跳转至已完成 页面

12.gif

class _TabDemoState extends State<TabDemo> with SingleTickerProviderStateMixin {
    late TabController _tabController;

    @override
    void initState() {
      super.initState();
      _tabController = TabController(length: 4, vsync: this);
      _tabController.addListener(() {
        print('当前 Tab 索引: ${_tabController.index}');
      });
    }

    void setSelectedTab(int index) {
      _tabController.animateTo(index); //跳转至对应下表的页面
    }

    @override
    Widget build(BuildContext context) {
      return Scaffold(
        appBar: AppBar(
          title: Text('手动控制 Tab'),
          bottom: TabBar(
            controller: _tabController,
            tabs: [
              Tab(text: '全部'),
              Tab(text: '未完成'),
              Tab(text: '进行中'),
              Tab(text: '已完成'),
            ],
            indicatorColor: Colors.red,
            // 指示器颜色
            indicatorWeight: 4,
            // 指示器厚度
            indicatorSize: TabBarIndicatorSize.tab,
            // 指示器长度(label: 与文本同宽)
            labelColor: Colors.black,
            // 选中标签颜色
            unselectedLabelColor: Colors.grey,
            // 未选中标签颜色
            labelStyle: TextStyle(fontWeight: FontWeight.bold), // 标签文本样式
          ),
        ),
        body: TabBarView(
          controller: _tabController,
          children: [
            Center(child: Text('全部订单')),
            Center(child: GestureDetector(
              onTap: (){
                setSelectedTab(3); //跳转至下标为3的页面
              },
              child: Text('查看已完成订单'),
            )),
            Center(child: Text('进行中的订单')),
            Center(child: Text('已完成订单')),
          ],
        ),
      );
    }

    @override
    void dispose() {
      _tabController.dispose();
      super.dispose();
    }
}

2. 滑动切换与动画

通过 TabBarViewphysics 属性控制滑动行为:

TabBarView(
  physics: NeverScrollableScrollPhysics(), // 禁止滑动切换,也可以通过继承ScrollPhysics来自定义动画
  children: [...],
)

3. 保持页面状态

默认情况下,切换 Tab 时会重建页面。若需保持状态(如保留表单输入),有两种方式:

  • 使用 AutomaticKeepAliveClientMixin(推荐):
    class _HomePageState extends State<HomePage> with AutomaticKeepAliveClientMixin {
      @override
      bool get wantKeepAlive => true; // 保持页面状态
    
      @override
      Widget build(BuildContext context) {
        super.build(context);
        return ...;
      }
    }
    
  • 使用 PageStorageKey
    TabBarView(
      children: [
        HomePage(key: PageStorageKey('home')),
        SettingsPage(key: PageStorageKey('settings')),
      ],
    )
    

四、底部Tab

在 Scaffold使用 BottomNavigationBar实现我们常见的底部Tab:

bottomNavigationBar-ezgif.com-video-to-gif-converter.gif

主要思路
  • 创建一个tabs数组,数组包含3个页面
 List<Widget> tabs = [HomePage(), SearchPage(), SettingPage()]; //定义好一个包含三个页面的数组
  • 通过BottomNavigationBar设置底部tab
 bottomNavigationBar: BottomNavigationBar(
        items: [
          BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
          BottomNavigationBarItem(icon: Icon(Icons.search), label: 'Search'),
          BottomNavigationBarItem(icon: Icon(Icons.message), label: 'Setting'),
        ],
 ),
  • 自定义底部tab的样式
currentIndex: _currentIndex,
unselectedItemColor: Colors.grey,
selectedItemColor: Colors.amber[800],
onTap: _onItemTapped,
type: BottomNavigationBarType.shifting,
  • 添加消息提示数字

由于dart基础库不支持,所以需要添加额外的依赖

dependencies:
  badges: ^3.0.0
BottomNavigationBarItem(
  icon: Badge(label: Text('3'), child: Icon(Icons.message)),
  label: 'Setting',
),
  • 完整代码
class _MyHomePageState extends State<MyHomePage>
    with SingleTickerProviderStateMixin {
  late TabController _tabController;
  int _currentIndex = 0; //记录当前页面的下标
  List<Widget> tabs = [HomePage(), SearchPage(), SettingPage()]; //定义好一个包含三个页面的数组

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

  void _handleTabSelection() {
    if (_tabController.indexIsChanging) {
      setState(() {
        // 可以在这里处理Tab切换事件
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      bottomNavigationBar: BottomNavigationBar(
        items: [
          BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
          BottomNavigationBarItem(icon: Icon(Icons.search), label: 'Search'),
          BottomNavigationBarItem(icon: Badge(
            label: Text('3'),
            child: Icon(Icons.message),
          ), label: 'Setting'),
        ],
        currentIndex: _currentIndex,
        unselectedItemColor: Colors.grey,
        selectedItemColor: Colors.amber[800],
        onTap: _onItemTapped,
        //底部按钮的样式,[fixed: 固定底部样式,shifting:未选中的时候,只展示icon,选中的时候展示完整的tab]
        type: BottomNavigationBarType.shifting, 
      ),
      body:
          tabs[_currentIndex], //根据下标展示对应的页面
    );
  }

  void _onItemTapped(int index) {
    setState(() {
      _currentIndex = index;
    });
    print(_currentIndex);
  }

  @override
  void dispose() {
    _tabController.dispose();
    super.dispose();
  }
}

通过灵活使用 Tab 组件,可以实现复杂的多页面布局和交互,不仅适用于新闻分类、电商商品详情等场景,底部tab也同样拥有丰富的属性来完成样式的定制。