likes
comments
collection
share

flutter-底部TabBar与顶部TabBarView

作者站长头像
站长
· 阅读数 4

前言

flutter-底部TabBar与顶部TabBarView

案例demo地址(Tabbar文件夹)

下面就分别介绍这两种怎么使用的,以及怎么保存状态

底部tabbar

底部 tabbar 就是我们常见的微信底部的切换功能效果,如下所示

flutter-底部TabBar与顶部TabBarView

其主要是依靠 BottomNavgationBarPageViewPageController,如下所示

class NormalTabBar extends StatefulWidget {
  const NormalTabBar({Key? key}) : super(key: key);

  @override
  State<NormalTabBar> createState() => _NormalTabBarState();
}

class _NormalTabBarState extends State<NormalTabBar> {

  //用于协调tabbar和内容的联动
  final PageController _controller = PageController(
    initialPage: 0 //默认为0,可以不填写
  );

  //存放bottombar信息,避免编写过多重复ui代码
  final List<TabBarItem> items = [
    TabBarItem(title: '聊天', norImage: "images/tabbar_chat.png", selImage: "images/tabbar_chat_hl.png"),
    TabBarItem(title: '联系人', norImage: "images/tabbar_contact.png", selImage: "images/tabbar_contact_hl.png"),
    TabBarItem(title: '发现', norImage: "images/tabbar_discover.png", selImage: "images/tabbar_discover_hl.png"),
    TabBarItem(title: '我的', norImage: "images/tabbar_mine.png", selImage: "images/tabbar_mine_hl.png")
  ];

  int _pageIndex = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _pageIndex,
        onTap: (int page) {
          setState(() {
            _pageIndex = page;
          });
          _controller.jumpToPage(page);
        },
        backgroundColor: Colors.white,
        type: BottomNavigationBarType.fixed,
        unselectedItemColor: Colors.black,
        selectedItemColor: Colors.cyanAccent,
        unselectedFontSize: 12,
        selectedFontSize: 12,
        items: items.map((e) {
          return BottomNavigationBarItem(
            label: e.title,
            icon: Image.asset(e.norImage,  width: 20, height: 20,),
            activeIcon: Image.asset(e.selImage, width: 20, height: 20,),
          );
        }).toList(),
      ),
      body: PageView(
        controller: _controller,
        //不设置默认可以左右活动,如果不想左右滑动如下设置,可以根据ios或者android来设置
        physics: Platform.isAndroid ? const PageScrollPhysics() : const NeverScrollableScrollPhysics(),
        children: const [
          //设置内容页面即可,要和 bottomNavigationBar 数量一致
          TabbarContainerView(color: Colors.yellow, name: '1',),
          TabbarItemInAppBarView(color: Colors.cyanAccent, name: "2",),
          TabbarContainerView(color: Colors.green, name: "3",),
          TabbarItemInAppBarView(color: Colors.blueAccent, name: "4",),
        ],
      ),
    );
  }
}

顶部tabbar

顶部tabbar虽然没有底部那么常用,但是在不少app的一些场景也是用的不少,下面介绍下其使用,本质和tabbar功能类似,只不过出了另外一个组件,花不说了,效果如下所示

flutter-底部TabBar与顶部TabBarView

其有两种方案,一种使用系统默认的,另外一种使用自定义的

默认顶部tabbar

实现主要通过 DefaultTabControllerTabBarTabBarView,如下所示

class TabbarContainerView extends StatefulWidget {
  final Color color;
  final String name;
  const TabbarContainerView({Key? key, required this.color, required this.name}) : super(key: key);

  @override
  State<TabbarContainerView> createState() => _TabbarContainerViewState();
}

//继承maxin AutomaticKeepAliveClientMixin 同时重写 wantKeepAlive 可以对当前页面状态保存,避免重新渲染初始化参数
class _TabbarContainerViewState extends State<TabbarContainerView> with AutomaticKeepAliveClientMixin  {
  @override
  bool get wantKeepAlive => true;

  final List<String> tabNames = ["推荐", "订阅"];

  @override
  void initState() {
    super.initState();
    print(widget.name);
  }

  @override
  Widget build(BuildContext context) {
    super.build(context);
    //通过 DefaultTabController 可自动调节内部tabbar联动
    return DefaultTabController(
      length: tabNames.length,
      child: Scaffold(
        appBar: AppBar(
          title: const Text("带Tabs的tabbar子页面"),
          //默认在下面放一排 Tab
          bottom: TabBar(
            //这个参数比较特殊,默认为false,不出屏元素多了会被挤压内容
            //如果想支持滚动,边内部挤压,可以将此参数设置为true
            //isScrollable: true,
            //设置tabs标签
            tabs: tabNames.map((e) => Tab(text: e,)).toList(),
          ),
        ),
        //使用 TabBarView 来声明我们的内容组件
        body: TabBarView(
          children: tabNames.map((e) {
            return TabsContainer(
              name: widget.name,
              color: widget.color,
            );
          }).toList(),
        ),
      ),
    );
  }
}

自定义顶部tabbar

除了上面默认的一横框的 tabbar 之外,还有类似与淘宝首页的那种效果,这种也可以通过自定义tabbar实现,如下所示(由于测试子页面多出来一个返回,所以居中有异常,正常不会有这这情况,根据情况自行调整即可)

flutter-底部TabBar与顶部TabBarView

其实现主要通过 TabControllerTabBarView,而 bar 我们自定义,通过 TabController 调节联动即可

其中 TabController比较特殊,需要集成自 SingleTickerProviderStateMixin(由于多继承mixin需要with),然后延迟初始化 late 避免编译错误即可

class TabInfosItem {
  final String name;
  final int index;
  bool selected;

  TabInfosItem({
    required this.name,
    required this.selected,
    required this.index
});
}

class TabbarItemInAppBarView extends StatefulWidget {
  final Color color;
  final String name;
  const TabbarItemInAppBarView({Key? key, required this.color, required this.name}) : super(key: key);

  @override
  State<TabbarItemInAppBarView> createState() => _TabbarItemInAppBarViewState();
}

//继承 SingleTickerProviderStateMixin
class _TabbarItemInAppBarViewState extends State<TabbarItemInAppBarView> with AutomaticKeepAliveClientMixin, SingleTickerProviderStateMixin  {
  @override
  bool get wantKeepAlive => true;

  List<TabInfosItem> tabs = [
    TabInfosItem(name: "推荐", selected: true, index: 0),
    TabInfosItem(name: "订阅", selected: false, index: 1),
  ];

  //late 延迟初始化,避免报错,默认初始化是会出现 this 指向不是对象的错误问题
  late TabController _tabController;

  @override
  void initState() {
    super.initState();
    //初始化TabController
    _tabController = TabController(length: tabs.length, vsync: this);
    print(widget.name);
  }

  @override
  Widget build(BuildContext context) {
    super.build(context);
    return Scaffold(
      //因为有返回键,所以居中有问题这里就不多设置了
      appBar: AppBar(
        title: Container(
          alignment: Alignment.center,
          child: Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: tabs.map((TabInfosItem item) {
              return 
              //这里就不多介绍了,添加额外参数,自定根据点击状态调整颜色线条等
              //通过 tabController联动
                TextButton(
                  onPressed: () {
                    _tabController.animateTo(item.index);
                    var tab = tabs.map((e) {
                      if (e.index == item.index) {
                        e.selected = true;
                      }else {
                        e.selected = false;
                      }
                      return e;
                    }).toList();
                    setState(() {});

                  },
                  child: Container(
                    width: 60,
                    alignment: Alignment.center,
                    child: Text(item.name, style: TextStyle(color: item.selected ? Colors.white : Colors.grey),),
                  ),
                );
            }).toList(),
          ),
        ),
      ),
      //TabBarView与默认的一样
      body: TabBarView(
        controller: _tabController,
        children: tabs.map((e) {
          return TabsContainer(
            name: widget.name,
            color: widget.color,
          );
        }).toList(),
      ),
    );
  }
}

看到上面是不是感觉底部tabbar也可以通过其定制了呢,没错,可以的,但为了减少代码和避免一些其他问题,还是使用系统的好一些,除非这里默认的功能无法满足你的需求

保存状态 AutomaticKeepAliveClientMixin

前面的组件会看到很多都继承了 AutomaticKeepAliveClientMixin,其就是一个保存状态的多继承 mixin 基类,由于默认使用 tabbar 会丢失子组件状态,因此需要 AutomaticKeepAliveClientMixin 来进行保存状态,避免重新初始化,至于为什么会丢失,跟系统实现有关系了

使用如下所示,继承后需要重写 wantKeepAlive 并且在 build 调用父类方法

class _TabbarItemInAppBarViewState extends State<TabbarItemInAppBarView> with AutomaticKeepAliveClientMixin, SingleTickerProviderStateMixin {
    //需要重写该方法,返回为 true
    @override
    bool get wantKeepAlive => true;

    @override
    Widget build(BuildContext context) {
        //还需要调用super.build方法
        super.build(context);
        return container();
    }
}

最后

这边文章能新手带来一些方便,老手忘了也可以参考一下,大家一起学习进步哈