flutter-底部TabBar与顶部TabBarView
前言
下面就分别介绍这两种怎么使用的,以及怎么保存状态
底部tabbar
底部 tabbar
就是我们常见的微信底部的切换功能效果,如下所示
其主要是依靠 BottomNavgationBar
、PageView
、PageController
,如下所示
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
功能类似,只不过出了另外一个组件,花不说了,效果如下所示
其有两种方案,一种使用系统默认的,另外一种使用自定义的
默认顶部tabbar
实现主要通过 DefaultTabController
、TabBar
、TabBarView
,如下所示
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
实现,如下所示(由于测试子页面多出来一个返回,所以居中有异常,正常不会有这这情况,根据情况自行调整即可)
其实现主要通过 TabController
、TabBarView
,而 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();
}
}
最后
这边文章能新手带来一些方便,老手忘了也可以参考一下,大家一起学习进步哈