CupertinoTabController

Flutter cupertino库中用于管理CupertinoTabScaffold或CupertinoTabBar的选择状态的控制器

CupertinoTabController是Flutter cupertino库中用于管理CupertinoTabScaffoldCupertinoTabBar的选择状态的控制器。它允许开发者程序化地控制当前选中的标签页索引,而不仅仅依赖用户点击交互。

  • 主要用途: 用于在多个标签页(Tabs)之间切换时,管理和监听当前活跃的标签页索引。它通常与CupertinoTabScaffold结合使用,后者提供了一个底部标签栏和与之对应的页面内容区域。
  • 核心逻辑: 它是一个ChangeNotifier,当index属性改变时,会通知所有监听器(如CupertinoTabScaffold),从而更新UI显示。

使用场景

  • 程序化切换标签页: 例如,当用户完成某个操作后,自动切换到另一个相关的标签页。
  • 共享标签页状态: 在应用的多个部分之间共享或同步当前激活的标签页状态。
  • 深度链接处理: 从外部链接(如通用链接或应用内通知)导航到特定的标签页。
  • 状态恢复: 在应用重启后恢复之前选中的标签页状态。

示例

基本使用与手动切换

import 'package:flutter/cupertino.dart';

class BasicTabControlledApp extends StatefulWidget {
  const BasicTabControlledApp({super.key});

  
  State<BasicTabControlledApp> createState() => _BasicTabControlledAppState();
}

class _BasicTabControlledAppState extends State<BasicTabControlledApp> {
  late CupertinoTabController _tabController;

  
  void initState() {
    super.initState();
    _tabController = CupertinoTabController(initialIndex: 0); // 初始选中第一个标签页
  }

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

  
  Widget build(BuildContext context) {
    return CupertinoApp(
      theme: const CupertinoThemeData(brightness: Brightness.light),
      home: CupertinoTabScaffold(
        controller: _tabController,
        tabBar: CupertinoTabBar(
          items: const <BottomNavigationBarItem>[
            BottomNavigationBarItem(
              icon: Icon(CupertinoIcons.home),
              label: '主页',
            ),
            BottomNavigationBarItem(
              icon: Icon(CupertinoIcons.settings),
              label: '设置',
            ),
          ],
          onTap: (index) {
            // 用户点击标签栏时更新UI,控制器也会随之更新
            print('用户点击了索引 $index');
          },
        ),
        tabBuilder: (BuildContext context, int index) {
          return CupertinoTabView(
            builder: (BuildContext context) {
              return CupertinoPageScaffold(
                navigationBar: CupertinoNavigationBar(
                  middle: Text('Tab ${index + 1}'),
                  trailing: (index == 0)
                      ? CupertinoButton(
                          padding: EdgeInsets.zero,
                          onPressed: () {
                            // 手动切换到第二个标签页
                            _tabController.index = 1;
                            print('程序化切换到设置页');
                          },
                          child: const Text('去设置'),
                        )
                      : null,
                ),
                child: Center(
                  child: Text('这是Tab ${index + 1} 的内容'),
                ),
              );
            },
          );
        },
      ),
    );
  }
}

void main() {
  runApp(const BasicTabControlledApp());
}

监听标签页切换事件

import 'package:flutter/cupertino.dart';

class TabListenerApp extends StatefulWidget {
  const TabListenerApp({super.key});

  
  State<TabListenerApp> createState() => _TabListenerAppState();
}

class _TabListenerAppState extends State<TabListenerApp> {
  late CupertinoTabController _tabController;

  
  void initState() {
    super.initState();
    _tabController = CupertinoTabController(initialIndex: 0);
    _tabController.addListener(_handleTabChange); // 添加监听器
  }

  void _handleTabChange() {
    print('当前选中的标签页索引发生变化: ${_tabController.index}');
    // 可以在这里执行一些依赖于当前标签页的逻辑
    if (_tabController.index == 1) {
      print('您切换到了设置标签页!');
    }
  }

  
  void dispose() {
    _tabController.removeListener(_handleTabChange); // 移除监听器
    _tabController.dispose();
    super.dispose();
  }

  
  Widget build(BuildContext context) {
    return CupertinoApp(
      theme: const CupertinoThemeData(brightness: Brightness.light),
      home: CupertinoTabScaffold(
        controller: _tabController,
        tabBar: CupertinoTabBar(
          items: const <BottomNavigationBarItem>[
            BottomNavigationBarItem(
              icon: Icon(CupertinoIcons.square_list),
              label: '列表',
            ),
            BottomNavigationBarItem(
              icon: Icon(CupertinoIcons.info),
              label: '关于',
            ),
          ],
        ),
        tabBuilder: (BuildContext context, int index) {
          return CupertinoTabView(
            builder: (BuildContext context) {
              return CupertinoPageScaffold(
                navigationBar: CupertinoNavigationBar(
                  middle: Text('Tab ${index + 1}'),
                ),
                child: Center(
                  child: Text('这是Tab ${index + 1} 的内容'),
                ),
              );
            },
          );
        },
      ),
    );
  }
}

void main() {
  runApp(const TabListenerApp());
}

结合路由实现深度链接

import 'package:flutter/cupertino.dart';

// 模拟一个外部参数,用于决定初始标签页
const bool _shouldStartOnSettings = true; // 假设从深度链接或其他条件判断而来

class DeepLinkTabApp extends StatefulWidget {
  const DeepLinkTabApp({super.key});

  
  State<DeepLinkTabApp> createState() => _DeepLinkTabAppState();
}

class _DeepLinkTabAppState extends State<DeepLinkTabApp> {
  late CupertinoTabController _tabController;

  
  void initState() {
    super.initState();
    // 根据外部条件设置初始索引
    final int initialTabIndex = _shouldStartOnSettings ? 1 : 0;
    _tabController = CupertinoTabController(initialIndex: initialTabIndex);
    print('应用启动,初始标签页索引: $initialTabIndex');
  }

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

  
  Widget build(BuildContext context) {
    return CupertinoApp(
      theme: const CupertinoThemeData(brightness: Brightness.light),
      home: CupertinoTabScaffold(
        controller: _tabController,
        tabBar: CupertinoTabBar(
          items: const <BottomNavigationBarItem>[
            BottomNavigationBarItem(
              icon: Icon(CupertinoIcons.bag_badge_plus),
              label: '商品',
            ),
            BottomNavigationBarItem(
              icon: Icon(CupertinoIcons.person_alt),
              label: '个人',
            ),
          ],
        ),
        tabBuilder: (BuildContext context, int index) {
          return CupertinoTabView(
            builder: (BuildContext context) {
              return CupertinoPageScaffold(
                navigationBar: CupertinoNavigationBar(
                  middle: Text('Tab ${index + 1}'),
                ),
                child: Center(
                  child: Text('这是Tab ${index + 1} 的内容'),
                ),
              );
            },
          );
        },
      ),
    );
  }
}

void main() {
  runApp(const DeepLinkTabApp());
}

注意点

  1. 生命周期管理: CupertinoTabController是一个ChangeNotifier,必须在不再使用时通过调用dispose()方法释放资源,以避免内存泄漏。通常在管理它的StatefulWidgetdispose方法中进行。
  2. CupertinoTabScaffold配合: CupertinoTabController通常是与CupertinoTabScaffoldcontroller属性一起使用的。虽然也可以用于控制独立的CupertinoTabBar,但与CupertinoTabScaffold结合使用时,它会自动处理页面内容的切换。
  3. initialIndex的设置: initialIndex只能在控制器创建时设置一次。如果需要在运行时更改初始索引,需要重新创建CupertinoTabController或者使用index属性进行设置。
  4. 程序化与用户交互: CupertinoTabControllerindex属性可以直接(程序化地)修改,也可以通过用户点击CupertinoTabBar自动更新。两者都会触发监听器通知。
  5. 嵌套导航: 在CupertinoTabView中使用Navigator进行页面的推入和弹出时,每个CupertinoTabView都维护自己的导航栈。CupertinoTabController管理的是顶层标签页的切换,不会影响每个标签页内部的导航栈状态。

构造函数

CupertinoTabController({
  super.debugLabel, // 可选参数,用于调试时识别controller
  int initialIndex = 0, // 初始选中的标签页索引
})

属性

属性名称属性类型说明
indexint当前选中的标签页的索引。修改此属性会触发所有监听器,并导致 CupertinoTabScaffold 或其他监听者更新其UI以显示新的标签页内容。这是一个可读写的属性。
initialIndexint(只读) 控制器创建时指定的初始选中标签页的索引。此值在控制器生命周期内是固定的,不能在创建后修改。
animationAnimation<double>一个动画对象,通常用于表示在标签页切换过渡期间的动画进度。此动画通常由 CupertinoTabScaffold 内部管理,当切换标签页时,它会在0.0到1.0之间进行动画。开发者可以在高级场景下使用这个动画来同步自定义的过渡效果。
vsyncTickerProvider(内部使用) 为控制器内部的 AnimationController 提供 TickerProvider。通常不需要直接与此属性交互,它由 CupertinoTabScaffold 自动提供。

关键属性详解

  • index: 这是CupertinoTabController中最核心的属性。它不仅表示当前激活的标签页,而且是一个可变的属性,可以被程序化地设置。当index值通过_tabController.index = newIndex;改变时,所有已注册的监听器(包括CupertinoTabScaffold自身)都会收到通知,从而触发UI的重新构建,显示对应新索引的标签页内容。这使得开发者能够完全控制标签页的切换,例如在用户完成某个操作后自动跳转到确认页所在的标签页。
  • animation: 尽管不常用,但animation属性在需要实现更精细的自定义标签页切换动画时非常有用。例如,如果你想让某个子组件的动画与标签页的滑动过渡同步,你可以监听这个动画对象的value变化。通常情况下,CupertinoTabScaffold会自行处理标签页内容的滑动动画,无需我们手动干预。