前言

就以咱们平时比较常见的微信运用参阅,咱们比较常用的组件便是底部 tabbar 了,除此之外,便是顶部的类似于 tabbar 的组件了(有些当地叫顶部叫PageView),两者吞并的,就以 app 主页为例,上下两个部分一望而知

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();
    }
}

最后

这边文章能新手带来一些便利,内行忘了也能够参阅一下,我们一起学习前进哈