flutter 封装一个 tab
flutter版本:3.35.4
demo 链接:
https://github.com/yhtqw/FrontEndDemo/tree/main/flutter_demo/lib/pages/customize_tab
在flutter中使用 TabBar + TabBarView 就能很好的实现,为了使用的便捷性和通用性,所以进行一些封装。
需要看最终效果和最终的封装代码,直接拉到最底部即可~
简单的使用一下,对一些基本的结构限制做出解释。
import 'package:flutter/material.dart';
class Page19 extends StatefulWidget {
const Page19({super.key});
@override
State<Page19> createState() => _Page19State();
}
class _Page19State extends State<Page19> with SingleTickerProviderStateMixin {
// 必须,使用TabController作为桥梁连接TabBar和TabBarView
late TabController _controller;
@override
void initState() {
super.initState();
_controller = TabController(
length: 2,
vsync: this,
initialIndex: 0,
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: Column(
children: [
const SizedBox(height: 100,),
TabBar(
controller: _controller,
// tab bar选项,数量得和下面一致
tabs: const [
Text('1111'),
Text('2222'),
],
),
// 使用TabBarView时,必须指定容器的限制范围。
// 这里直接撑满剩余空间
Expanded(
child: TabBarView(
controller: _controller,
// tab bar每个选项对应的tab页面,数量得很上面一致
children: const [
Text('1'),
Text('2'),
],
),
),
],
),
);
}
}
效果如下:

简单的场景当然也可以不使用TabController,但需要DefaultTabController包裹,这样无需手动创建控制器自动就关联上了,大致使用如下:
// ... 其他省略
class _Page19State extends State<Page19> with SingleTickerProviderStateMixin {
@override
Widget build(BuildContext context) {
return const DefaultTabController(
// 数量得和下面的一致
length: 2,
child: Scaffold(
backgroundColor: Colors.white,
body: Column(
children: [
SizedBox(height: 100,),
TabBar(
tabs: [
Text('1111'),
Text('2222'),
],
),
Expanded(
child: TabBarView(
children: [
Text('1'),
Text('2'),
],
),
),
],
),
),
);
}
}
效果和上面一致。
从上面可以看出 flutter 已经实现了滑动的动画效果,我们只需要对结构样式进行封装,下面我们去体验一下其他的属性,为封装做准备。
// 其他省略...
class _Page19State extends State<Page19> with SingleTickerProviderStateMixin {
late TabController _controller;
@override
void initState() {
super.initState();
_controller = TabController(
length: 3,
vsync: this,
initialIndex: 0,
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: Column(
children: [
const SizedBox(height: 100,),
TabBar(
controller: _controller,
// 注意:选项文本颜色就通过下面设置,不通过选项自身的TextStyle来设置
// tab bar 选项文本选中时的颜色
labelColor: Colors.blue,
// tab bar 选项文本未选中时的颜色
unselectedLabelColor: Colors.black,
// 选中指示器相关的属性
// 设置tab bar选中指示器的颜色
indicatorColor: Colors.blue,
// 设置tab bar选中指示器的大小,默认和选项的文本宽度一致。
// 这里设置的指示器宽度和选项的宽度一致
// 这样设置后切换tab的时候蠕虫蠕动效果就没有了
indicatorSize: TabBarIndicatorSize.tab,
// 设置tab bar选中指示器的高度
indicatorWeight: 10,
// 移除tab bar选项点击时的高亮和水波纹反馈效果
overlayColor: WidgetStateProperty.all(Colors.transparent),
// 设置tab bar选项的内边距
// 在indicatorSize设置为label的时候再设置这个值比较好
// labelPadding: const EdgeInsets.symmetric(horizontal: 40),
// 设置tab bar底部的那条分割线高度,也可以通过dividerColor设置其颜色
dividerHeight: 0,
tabs: ['1111','2222','3333'].map(
(item) => Text(item,),
).toList(),
),
Expanded(
child: TabBarView(
controller: _controller,
children: ['1','2','3'].map(
(item) =>Text(item,),
).toList(),
),
),
],
),
);
}
}
效果如下:

可以看到,上面我们使用了许多选中和选中指示器的效果,也是在tab bar个数少的时候展示的效果,接下来我们加几个再看看效果:

可以看到选项被挤压,这个时候我们就可以通过 TabBar 的 isScrollable 属性来设置是否可以滚动:
// 其他省略...
TabBar(
// 其他省略...
// 设置tab bar选项个数过多超过限制的宽度的时候,是否允许滚动
isScrollable: true,
),
// 其他省略...

我们又发现了允许滚动后,第一个的排列距离容器左边有一些边距,如果想让选项在最左边开始显示,就需要设置tabAlignment属性:
// 其他省略...
TabBar(
// 其他省略...
// 设置tab bar选项个数过多超过限制的宽度的时候,是否允许滚动
isScrollable: true,
// 设置tab bar选项的显示位置
tabAlignment: TabAlignment.start,
),
// 其他省略...
效果如下:

关于TabBar的一些常用的属性就体验得差不多了,如果我们要设置整个TabBar容器的样式,例如背景色,圆角,高度等属性时,就需要对TabBar进行包裹,使用外层包裹部件来设置样式:
// 其他省略...
Container(
height: 60,
padding: const EdgeInsets.symmetric(horizontal: 20),
decoration: const BoxDecoration(
color: Colors.grey,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20),
topRight: Radius.circular(20),
)
),
child: TabBar(
// 其他省略...
),
),
// 其他省略...
效果如下:

这下关于TabBar的样式就设置的差不多了,那么关于TabBarView的样式就自行通过传染的children部件自行封装实现。
我们从上面的效果中可以看到tab bar选中样式通过属性去自定义可能有些不好看,如果我们想用让指示器为整个背景,并且设置部分样式效果,我们就需要自定义TabBar的indicator属性。

可以看到,如果我们要自定义指示器,需要实现Decoration,并且如果设置了indicator属性,则会忽略indicatorColor和indicatorWeight属性。


为了实现自定义的indicator,我们需要继承Decoration去实现绘制的方法,接下来我们就去实现一个简单的圆角矩形样式(这里不深入展开绘制相关的知识,后面有时间再单独写绘制相关的知识):
import 'package:flutter/material.dart';
class CustomizeTabIndicator extends Decoration {
const CustomizeTabIndicator({
required this.color,
this.radius = BorderRadius.zero,
});
/// 指示器颜色
final Color color;
/// 指示器的圆角属性
final BorderRadius radius;
// 需要重写绘制方法
@override
BoxPainter createBoxPainter([VoidCallback? onChanged]) =>
_RoundedPainter(this, onChanged);
}
// 自定义绘制方法
class _RoundedPainter extends BoxPainter {
_RoundedPainter(this.decoration, VoidCallback? onChanged) : super(onChanged);
final CustomizeTabIndicator decoration;
// 重写绘制的方法,这个方法会传给我们绘制区域的信息
// 我们利用这些信息就可以实现自定义的绘制
@override
void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) {
// 获取绘制区域的大小,整个变换过程中都会更新
double width = configuration.size!.width;
double height = configuration.size!.height;
// 获取绘制区域的偏移量(距离最边上的距离)
Offset baseOffset = Offset(offset.dx, offset.dy,);
// 设置要绘制的圆角矩形
final RRect indicatorRRect = _buildRRect(
baseOffset,
width,
height,
);
// 设置画笔属性
final Paint paint = Paint()
..color = decoration.color
..style = PaintingStyle.fill;
// 绘制圆角矩形
canvas.drawRRect(indicatorRRect, paint);
}
/// 绘制圆角指示器
RRect _buildRRect(
Offset offset,
double width,
double height,
) {
return RRect.fromRectAndCorners(
// 圆角矩形的绘制中心
Rect.fromCenter(
center: Offset(
offset.dx + width / 2,
offset.dy + height / 2,
),
width: width,
height: height,
),
topLeft: decoration.radius.topLeft,
topRight: decoration.radius.topRight,
bottomRight: decoration.radius.bottomRight,
bottomLeft: decoration.radius.bottomLeft,
);
}
}
使用:
// 其他省略...
class _Page19State extends State<Page19> with SingleTickerProviderStateMixin {
// 其他省略...
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: Column(
children: [
const SizedBox(height: 100,),
Container(
height: 60,
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 20),
decoration: const BoxDecoration(
color: Colors.grey,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20),
topRight: Radius.circular(20),
)
),
child: TabBar(
controller: _controller,
// 注意:选项文本颜色就通过下面设置,不通过选项自身的TextStyle来设置
// tab bar 选项文本选中时的颜色
labelColor: Colors.blue,
// tab bar 选项文本未选中时的颜色
unselectedLabelColor: Colors.black,
// 选中指示器相关的属性
// 自定义指示器
indicator: const CustomizeTabIndicator(
color: Colors.amber,
),
// 设置tab bar选项个数过多超过限制的宽度的时候,是否允许滚动
isScrollable: true,
// 设置tab bar选项的显示位置
tabAlignment: TabAlignment.start,
// 设置tab bar选项的内边距
// 在indicatorSize设置为label的时候再设置这个值比较好
labelPadding: const EdgeInsets.only(right: 30),
// 设置tab bar底部的那条分割线高度,也可以通过dividerColor设置其颜色
dividerHeight: 0,
tabs: ['1111','2222','3333','4444','5555','6666','7777'].map(
(item) => Padding(
padding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 5,
),
child: Text(item,)
),
).toList(),
),
),
// 其他省略...
],
),
);
}
}
效果如下:

通过上面的一系列的使用,接下来封装就很明了了,属性主要分为:
- tab bar相关
- tab views相关
- 其他属性
接下来基于上述的开始封装:
import 'package:flutter/material.dart';
import 'customize_tab_indicator.dart';
/// 抽取一些默认的属性
BorderRadius ctDefaultIndicatorBorderRadius = BorderRadius.circular(10);
class CustomizeTab extends StatefulWidget {
const CustomizeTab({
super.key,
this.tabBarHeight = kToolbarHeight,
this.tabBarBackgroundColor,
this.tabBarPadding,
this.tabBarBorderRadius,
required this.tabs,
this.unselectedColor,
this.selectedColor,
this.tabBarOptionMargin = const EdgeInsets.only(right: 10),
this.tabBarOptionPadding = EdgeInsets.zero,
this.indicatorColor = Colors.blue,
this.indicatorBorderRadius,
required this.tabViews,
this.initialIndex,
this.onChangeTabIndex,
});
/// tab bar 容器的高度,默认为AppBar工具栏组件的高度
final double tabBarHeight;
/// tab bar 容器的背景颜色
final Color? tabBarBackgroundColor;
/// tab bar 容器的内边距
final EdgeInsetsGeometry? tabBarPadding;
/// tab bar 容器的圆角属性
final BorderRadiusGeometry? tabBarBorderRadius;
/// tab 选项
final List<Widget> tabs;
/// tab 选项未选中时的颜色
final Color? unselectedColor;
/// tab 选项选中时的颜色
final Color? selectedColor;
/// tab bar 选项每项的margin
final EdgeInsetsGeometry tabBarOptionMargin;
/// tab bar 选项每项的padding(因为大概率每项的padding是一致的,所以进行抽取)
final EdgeInsetsGeometry tabBarOptionPadding;
/// 指示器的颜色
final Color indicatorColor;
/// 指示器的圆角属性
final BorderRadius? indicatorBorderRadius;
/// tab 页面
final List<Widget> tabViews;
/// 初始化显示tab的索引
final int? initialIndex;
/// 当tab索引发生改变时的回调函数
final Function(int)? onChangeTabIndex;
@override
State<CustomizeTab> createState() => _CustomizeTabState();
}
class _CustomizeTabState extends State<CustomizeTab> with SingleTickerProviderStateMixin {
late TabController _controller;
@override
void initState() {
super.initState();
_controller = TabController(
length: widget.tabs.length,
vsync: this,
initialIndex: widget.initialIndex ?? 0,
)..addListener(() {
// indexIsChanging主要作用就是标识TabController是否正处于索引切换过程中。
// 点击切换,在执行动画期间为true,用户手势操作结束后且动画完成为false
// 滑动切换为false
// 使用indexIsChanging来判断当前tab变化是否完成,完成了就执行回调
if (!_controller.indexIsChanging) {
widget.onChangeTabIndex?.call(_controller.index);
}
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
/// 对每个tab加内边距
Widget _buildTab(Widget tab) {
return Padding(
padding: widget.tabBarOptionPadding,
child: tab,
);
}
@override
Widget build(BuildContext context) {
// 通过父容器的约束动态构建子部件
return LayoutBuilder(
builder: (_, BoxConstraints boxConstraints) => Column(
children: [
Container(
width: double.infinity,
height: widget.tabBarHeight,
padding: widget.tabBarPadding,
decoration: BoxDecoration(
color: widget.tabBarBackgroundColor,
borderRadius: widget.tabBarBorderRadius,
),
child: TabBar(
controller: _controller,
indicator: CustomizeTabIndicator(
color: widget.indicatorColor,
radius: widget.indicatorBorderRadius ??
ctDefaultIndicatorBorderRadius,
),
isScrollable: true,
dividerHeight: 0,
labelPadding: widget.tabBarOptionMargin,
tabAlignment: TabAlignment.start,
unselectedLabelColor: widget.unselectedColor,
labelColor: widget.selectedColor,
tabs: widget.tabs.map((tab) => _buildTab(tab)).toList(),
),
),
// 因为TabBarView必须要约束
// 所以定义它的高度就为外界的约束高度减去TabBar的高度
SizedBox(
width: double.infinity,
height: boxConstraints.maxHeight - widget.tabBarHeight,
child: TabBarView(
controller: _controller,
children: widget.tabViews,
),
),
],
),
);
}
}
使用:
import 'package:flutter/material.dart';
import '../widgets/page19/customize_tab.dart';
class Page19 extends StatefulWidget {
const Page19({super.key});
@override
State<Page19> createState() => _Page19State();
}
class _Page19State extends State<Page19> with SingleTickerProviderStateMixin {
Widget _buildTab(String txt) {
return Text(
txt,
style: const TextStyle(
fontSize: 14,
),
);
}
Widget _buildTabView(String txt) {
return Container(
padding: const EdgeInsets.all(20),
color: Colors.grey,
child: Text(txt),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: Column(
children: [
const SizedBox(height: 100,),
Expanded(
child: CustomizeTab(
tabBarBackgroundColor: Colors.amber,
tabBarPadding: const EdgeInsets.symmetric(
horizontal: 20,
),
tabBarBorderRadius: const BorderRadius.only(
topRight: Radius.circular(20),
topLeft: Radius.circular(20),
),
unselectedColor: Colors.black,
selectedColor: Colors.white,
tabBarOptionPadding: const EdgeInsets.symmetric(
horizontal: 15,
vertical: 5,
),
indicatorColor: Colors.orange,
onChangeTabIndex: (index) {
print('当前的索引为:$index');
},
tabs: ['tab1', 'tab2', 'tab3', 'tab4', 'tab5'].map(
(item) => _buildTab(item),
).toList(),
tabViews: ['1', '2', '3', '4', '5'].map(
(item) => _buildTabView(item),
).toList(),
),
),
],
),
);
}
}
效果如下:

上面我们就完成了一个简单的tab封装,但是目前还不算很通用,例如我想要TabBar在下面,TabView在上面,这个就不能直接使用了,得更改内部代码,所以继续继续封装,允许用户自定义方向。
接下来进行TabBar位置的分析:
- 在顶部(top)
不用具体分析了,因为上面就是基于在顶部封装的。
- 在底部(bottom)
与在顶部相比,只是结构倒序一下就行了。
- 在左边(left)
在左边,结构从上下结构(Column)变成左右结构(Row),而且TabBar不支持直接转成上下结构,所以只有另辟蹊径。从左右结构变成上下结构旋转90度也能实现效果,基于此在做调整,让整个容器旋转90度的时候,TabBar的选项则也被旋转了90度,从视觉效果上看就是倒置的,大致效果如下:

所以我们对每个子项向反方向旋转90度即可还原显示。
对于TabView,原先的交互方式为左右滑动切换,现在变成左右结构了,那么交互方式得从左右变成上下,TabView不支持直接变成上下滑动,所以有两种方式解决:
一种是使用PageView,这个天生就支持上下滑动,但是如果使用这个,得维护PageController,同步TabBar的切换,有兴趣的可自行试一下。
另一种方式依然是旋转,我们将其旋转90度后,自然而然滑动手势从左右变成了上下,不过需要注意的是,子项依然要反方向旋转90度还原。
- 在右边(right)
与在左边相比,只是结构倒序一下就行了。
简单的分析了如何实现,那么接下来就开始编写:
import 'package:flutter/material.dart';
import 'customize_tab_indicator.dart';
/// tab bar 位置的枚举
enum TabBarPosition { top, bottom, left, right }
/// 抽取一些默认的属性
BorderRadius ctDefaultIndicatorBorderRadius = BorderRadius.circular(10);
class CustomizeTab extends StatefulWidget {
const CustomizeTab({
super.key,
this.tabBarHeight = kToolbarHeight,
this.tabBarBackgroundColor,
this.tabBarPadding = EdgeInsets.zero,
this.tabBarBorderRadius,
required this.tabs,
this.unselectedColor,
this.selectedColor,
this.tabBarOptionMargin = const EdgeInsets.only(right: 10),
this.tabBarOptionPadding = EdgeInsets.zero,
this.indicatorColor = Colors.blue,
this.indicatorBorderRadius,
required this.tabViews,
this.initialIndex,
this.onChangeTabIndex,
this.position = TabBarPosition.top,
});
/// tab bar 容器的高度,默认为AppBar工具栏组件的高度
final double tabBarHeight;
/// tab bar 容器的背景颜色
final Color? tabBarBackgroundColor;
/// tab bar 容器的内边距
final EdgeInsetsGeometry tabBarPadding;
/// tab bar 容器的圆角属性
final BorderRadiusGeometry? tabBarBorderRadius;
/// tab 选项
final List<Widget> tabs;
/// tab 选项未选中时的颜色
final Color? unselectedColor;
/// tab 选项选中时的颜色
final Color? selectedColor;
/// tab bar 选项每项的margin
final EdgeInsetsGeometry tabBarOptionMargin;
/// tab bar 选项每项的padding(因为大概率每项的padding是一致的,所以进行抽取)
final EdgeInsetsGeometry tabBarOptionPadding;
/// 指示器的颜色
final Color indicatorColor;
/// 指示器的圆角属性
final BorderRadius? indicatorBorderRadius;
/// tab 页面
final List<Widget> tabViews;
/// 初始化显示tab的索引
final int? initialIndex;
/// 当tab索引发生改变时的回调函数
final Function(int)? onChangeTabIndex;
/// tab bar所在位置,默认为top
final TabBarPosition position;
@override
State<CustomizeTab> createState() => _CustomizeTabState();
}
class _CustomizeTabState extends State<CustomizeTab> with SingleTickerProviderStateMixin {
late TabController _controller;
@override
void initState() {
super.initState();
_controller = TabController(
length: widget.tabs.length,
vsync: this,
initialIndex: widget.initialIndex ?? 0,
)..addListener(() {
// indexIsChanging主要作用就是标识TabController是否正处于索引切换过程中。
// 点击切换,在执行动画期间为true,用户手势操作结束后且动画完成为false
// 滑动切换为false
// 使用indexIsChanging来判断当前tab变化是否完成,完成了就执行回调
if (!_controller.indexIsChanging) {
widget.onChangeTabIndex?.call(_controller.index);
}
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
/// 对每个tab加内边距
Widget _buildTabOption(Widget tab) {
final Widget tabOption = Padding(
padding: widget.tabBarOptionPadding,
child: tab,
);
// 如果是left和right,因为外层的TabBar容器旋转了90度
// 那Tab选项就旋转-90度还原,达到视觉的统一
if (widget.position == TabBarPosition.left || widget.position == TabBarPosition.right) {
return RotatedBox(
quarterTurns: -1,
child: tabOption,
);
} else {
return tabOption;
}
}
/// 构建TabBar
Widget _buildTabBar() {
final Widget tabBar = Container(
width: double.infinity,
height: widget.tabBarHeight,
padding: widget.tabBarPadding,
decoration: BoxDecoration(
color: widget.tabBarBackgroundColor,
borderRadius: widget.tabBarBorderRadius,
),
child: TabBar(
controller: _controller,
indicator: CustomizeTabIndicator(
color: widget.indicatorColor,
radius: widget.indicatorBorderRadius
?? ctDefaultIndicatorBorderRadius,
),
isScrollable: true,
dividerHeight: 0,
labelPadding: widget.tabBarOptionMargin,
tabAlignment: TabAlignment.start,
unselectedLabelColor: widget.unselectedColor,
labelColor: widget.selectedColor,
tabs: widget.tabs.map((tab) => _buildTabOption(tab)).toList(),
),
);
if (widget.position == TabBarPosition.left || widget.position == TabBarPosition.right) {
// 如果是left和right,则旋转90度,
return RotatedBox(
quarterTurns: 1,
child: tabBar,
);
} else {
return tabBar;
}
}
Widget _buildTabView(BoxConstraints boxConstraints) {
final EdgeInsets tabBarPadding = widget.tabBarPadding.resolve(TextDirection.ltr);
return widget.position == TabBarPosition.top || widget.position == TabBarPosition.bottom ? SizedBox(
width: double.infinity,
height: boxConstraints.maxHeight -
widget.tabBarHeight -
tabBarPadding.top -
tabBarPadding.bottom,
child: TabBarView(
controller: _controller,
children: widget.tabViews,
),
) : SizedBox(
// 如果是left或者right,则宽高设置交换,并且将TabBarView旋转90度
width: boxConstraints.maxWidth -
widget.tabBarHeight -
tabBarPadding.top -
tabBarPadding.bottom,
height: double.infinity,
child: RotatedBox(
quarterTurns: 1,
child: TabBarView(
controller: _controller,
// 因为TabBarView旋转了90度,对应的Tab项要旋转-90度还原
children: widget.tabViews.map((tabView) => RotatedBox(
quarterTurns: -1,
child: tabView,
)).toList(),
),
),
);
}
/// 构建tab
Widget _buildTab(BoxConstraints boxConstraints) {
List<Widget> children = [
_buildTabBar(),
_buildTabView(boxConstraints),
];
// 如果是bottom和right,则渲染的结构会倒置
if (widget.position == TabBarPosition.bottom
|| widget.position == TabBarPosition.right) {
children = children.reversed.toList();
}
// 如果是top或者bottom,则是上下结构
// 如果是left或者right,则是左右结构
return widget.position == TabBarPosition.top || widget.position == TabBarPosition.bottom
? Column(
children: children,
)
: Row(
children: children,
);
}
@override
Widget build(BuildContext context) {
// 通过父容器的约束动态构建子部件
return LayoutBuilder(
builder: (_, BoxConstraints boxConstraints) => _buildTab(boxConstraints),
);
}
}
完成编码后就开始测试使用:
import 'package:flutter/material.dart';
import '../widgets/page19/customize_tab.dart';
class Page19 extends StatefulWidget {
const Page19({super.key});
@override
State<Page19> createState() => _Page19State();
}
class _Page19State extends State<Page19> with SingleTickerProviderStateMixin {
// 用于动态切换tab bar的位置
TabBarPosition _tabBarPosition = TabBarPosition.top;
/// 构建tab项
Widget _buildTab(String txt) {
return Text(
txt,
style: const TextStyle(
fontSize: 14,
),
);
}
/// 构建tab view
Widget _buildTabView(String txt) {
return Container(
padding: const EdgeInsets.all(20),
color: Colors.grey,
child: Column(
children: [
Container(
width: double.infinity,
height: 60,
decoration: BoxDecoration(
color: Colors.amber,
borderRadius: BorderRadius.circular(20),
),
alignment: Alignment.center,
child: Text(txt),
)
],
),
);
}
/// 改变tab bar的位置
void _onChangePosition(TabBarPosition tabBarPosition) {
setState(() {
_tabBarPosition = tabBarPosition;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: Column(
children: [
const SizedBox(height: 100,),
Expanded(
child: CustomizeTab(
tabBarHeight: 100,
tabBarBackgroundColor: Colors.amber,
tabBarPadding: const EdgeInsets.symmetric(
horizontal: 20,
),
unselectedColor: Colors.black,
selectedColor: Colors.white,
tabBarOptionPadding: const EdgeInsets.symmetric(
horizontal: 15,
vertical: 5,
),
indicatorColor: Colors.orange,
position: _tabBarPosition,
onChangeTabIndex: (index) {
print('当前的索引为:$index');
},
tabs: ['tab1', 'tab2', 'tab3', 'tab4', 'tab5'].map(
(item) => _buildTab(item),
).toList(),
tabViews: ['1', '2', '3', '4', '5'].map(
(item) => _buildTabView(item),
).toList(),
),
),
Container(
height: 200,
color: Colors.black,
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
ElevatedButton(
onPressed: () => _onChangePosition(TabBarPosition.left),
child: const Text('左'),
),
ElevatedButton(
onPressed: () => _onChangePosition(TabBarPosition.right),
child: const Text('右'),
),
ElevatedButton(
onPressed: () => _onChangePosition(TabBarPosition.top),
child: const Text('上'),
),
ElevatedButton(
onPressed: () => _onChangePosition(TabBarPosition.bottom),
child: const Text('下'),
),
],
),
),
],
),
);
}
}
效果如下:

至此我们就简单封装了一个tab,极大的方便了使用。
感兴趣的也可以关注我的微信公众号【前端学习小营地】,不定时会分享一些小功能~
文章到此就结束了,感谢阅读,拜拜~